PHP中对LDAP目录分页查询须使用RFC 2696定义的Simple Paged Results Control,而非SQL式offset/limit;核心是调用ldap_search()时传入含oid“1.2.840.113556.1.4.319”及pageSize、cookie的controls参数,循环提取响应中的新cookie直至为空,并确保服务端启用分页支持且配置合理限制。

PHP 中对 LDAP 目录进行分页查询,核心是解决服务器默认限制返回条目数(如 OpenLDAP 默认 maxpagesize=500,AD 通常为 1000)的问题。直接使用普通搜索会截断结果,必须启用 LDAP 协议的 Simple Paged Results Control(RFC 2696),而非传统 SQL 式的 offset/limit。
用 ldap_control_paged_result 启用分页控制
从 PHP 7.3 起支持在 ldap_search() 中传入 controls 参数;PHP 8.0+ 已彻底移除旧式 ldap_control_paged_result_response() 函数,必须走新接口:
- 调用
ldap_search()前,构造分页 control 数组,包含oid("1.2.840.113556.1.4.319")、value(含 pageSize 和 cookie) - 首次查询时
cookie为空字符串;后续请求需携带上一轮返回的 cookie - 每次搜索后,用
ldap_get_entries()获取数据,并调用ldap_control_paged_result_response()提取下一页 cookie(注意:该函数在 PHP 8.0+ 已删除,但ldap_search()的$controls参数返回值中已包含更新后的 cookie)
完整分页循环示例(PHP 8.1+)
以下代码兼容 PHP 8.1 及以上,使用 LDAP\Connection 对象和现代 controls 机制:
- 初始化连接并绑定成功后,设置分页参数(如每页 500 条)
- 进入 while 循环:每次调用
ldap_search(),传入当前 cookie;解析结果;提取新 cookie;若 cookie 为空则退出 - 合并所有批次的
ldap_get_entries()结果,避免内存溢出可逐批处理
slapd.conf 中配置 limits * size.prtotal=unlimited 或具体数值,否则即使客户端分页,服务端仍可能拒绝大结果集。处理超大数据量的实用策略
当目录条目达数十万时,仅靠分页 control 不够,还需配合服务端优化与客户端逻辑:
立即学习“PHP免费学习笔记(深入)”;
-
缩小搜索范围:添加更精确的 filter(如
(objectClass=user)(memberOf=cn=xxx,ou=groups,...)),避免全库扫描 -
限制返回属性:只请求必要字段(
$attributes = ['cn', 'mail', 'uid']),减少网络传输和内存占用 - 异步或后台导出:不阻塞 Web 请求,改用 CLI 脚本执行长时分页任务,结果存文件或队列
- 缓存分页状态:对高频查询(如组织架构树),将分页 cookie 或 DN 范围缓存到 Redis,避免重复初始化
常见错误与绕过方式
遇到 SASL bind in progress、Operations error 或空结果,多因 control 配置不当:
- 确保 LDAP 服务端已启用 paged results(OpenLDAP 编译需含
--enable-slapd,AD 默认开启) - 不要混用
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, LDAP_VERSION_3)—— 分页 control 必须基于 LDAPv3 - 若服务端禁用分页(返回
unavailableCriticalExtension),可退化为 子树范围切分:按 OU 层级或字母前缀(如cn=A*,cn=B*)手动分批搜索



















