
后端【免费下载链接】collectionsCollections Abstraction Library项目地址https://gitcode.com/gh_mirrors/co/collections点击查看免费下载导读本文聚焦 Doctrine\Common\Collections本仓库 Collections Abstraction Library中用于声明式筛选集合的表达式Expression体系Comparison比较表达式、CompositeExpression组合表达式与Criteria查询条件三者协同工作构成一套与后端无关in-memory 数组集合 / ORM / ODM 均可实现的查询描述 API。读完本文你将掌握如何构造单条比较条件、如何用 AND/OR/NOT 组合多条条件以及如何通过Criteria的完整链式 APIwhere、andWhere、orWhere、orderBy、setFirstResult、setMaxResults完成排序、分页与匹配并理解这些表达式在底层如何被ClosureExpressionVisitor翻译成可执行的 PHP 闭包。一、表达式体系概览三个类如何协作Doctrine\Common\Collections的筛选能力建立在三个核心类之上它们全部位于Doctrine\Common\Collections\Expr与Doctrine\Common\Collections命名空间类命名空间职责ComparisonExpr描述某个字段与某个值之间的单个比较CompositeExpressionExpr用 AND / OR / NOT 把多个表达式组合成一个表达式树Criteria根命名空间聚合表达式 排序 分页的完整查询条件对象三者共同的类型契约是 src/Expr/Expression.php 中定义的Expression接口——它只要求一个方法visit(ExpressionVisitor $visitor): mixed即接受访问者从而让表达式树可以被不同的访问者翻译成不同的目标查询语言闭包、SQL、ODM 查询等。从源码结构看Comparison、CompositeExpression和Value都实现了该接口构成了一个经典的访问者模式Visitor Pattern表达式树。在集合层面Criteria被设计为传给Selectable::matching(Criteria $criteria)的参数见 src/Selectable.phpmatching()会筛选出所有满足该Criteria的元素并返回一个保留键的新集合。这套 API 的设计目标正如接口注释所强调的——与后端无关Expression的构造方式使得无论是内存集合ArrayCollection还是数据库背书的集合ORM 的 SQL 查询都能基于同一套表达式实现查询应用层无需感知底层实现差异。二、Comparison13 个比较操作符Doctrine\Common\Collections\Expr\Comparison用于创建将要交给Criteria使用的比较表达式。查看其源码 src/Expr/Comparison.php它定义如下操作符常量常量字符串值语义Comparison::EQ字段值等于给定值Comparison::NEQ字段值不等于给定值Comparison::LT字段值小于给定值Comparison::LTE字段值小于等于给定值Comparison::GT字段值大于给定值Comparison::GTE字段值大于等于给定值Comparison::IS等于与 EQ 无差别见源码注释Comparison::ININ字段值在给定值列表中Comparison::NINNIN字段值不在给定值列表中Comparison::CONTAINSCONTAINS字段值包含给定字符串子串Comparison::MEMBER_OFMEMBER_OF字段值数组/集合中包含给定元素Comparison::STARTS_WITHSTARTS_WITH字段值以给定字符串开头Comparison::ENDS_WITHENDS_WITH字段值以给定字符串结尾值得一提的细节Comparison::IS与Comparison::EQ的常量值都是源码中明确注释 no difference with EQ。这是为兼容其他实现如 ORM/ODM保留的语义别名。2.1 构造器签名与 Value 包装Comparison的构造器签名为public function __construct(private string $field, private string $op, mixed $value)三个参数分别是字段名$field、操作符$op传入上面任一常量、比较值$value。构造器内部有一个关键行为如果传入的$value不是Value实例会自动包装成Doctrine\Common\Collections\Expr\Value见 src/Expr/Value.php。Value同样是Expression的实现它只负责承载一个原始值供访问者通过walkValue()取出。一个最小可用的比较表达式use Doctrine\Common\Collections\Expr\Comparison; $expr new Comparison(key, Comparison::EQ, value);2.2 各操作符的底层求值语义表达式本身只是描述真正执行求值的是访问者。在本仓库的内存集合实现中ClosureExpressionVisitorsrc/Expr/ClosureExpressionVisitor.php会把每个比较操作符翻译成一个返回布尔值的 PHP 闭包。其walkComparison()方法用match语句一一对应EQ/NEQ使用严格比较/!LT/LTE/GT/GTE使用 PHP 原生的、、、比较运算符IN/NIN对字段值调用in_array($fieldValue, $value, is_scalar($fieldValue))——注意第三个严格参数取决于字段值是否为标量即字段值是标量时走严格模式非标量时走宽松模式CONTAINS将字段值与给定值都转成字符串后调用str_contains()判断子串关系STARTS_WITH/ENDS_WITH同样转字符串后调用str_starts_with()/str_ends_with()MEMBER_OF将字段值视为数组若不是数组则用iterator_to_array()转换再以严格模式in_array($value, $fieldValues, true)判断给定值是否为其中一员未知操作符会抛出RuntimeException(Unknown comparison operator: ...)。字段值提取统一走静态方法ClosureExpressionVisitor::getObjectFieldValue($object, $field)支持点号分隔的嵌套字段如address.city支持数组按键取值对对象则用反射沿继承链向上查找属性属性不存在时抛RuntimeException最后通过Property::getRawValue()取值。这意味着比较表达式的字段路径在内存集合中既可以指向数组键也可以指向对象的含继承的属性。2.3 用 ExpressionBuilder 免去样板代码直接new Comparison(...)略显冗长。Criteria::expr()返回一个共享的ExpressionBuilder单例见 src/Criteria.php 与 src/ExpressionBuilder.php为每个操作符提供了语义化工厂方法use Doctrine\Common\Collections\Criteria; $exprBuilder Criteria::expr(); $eq $exprBuilder-eq(price, 100); $gt $exprBuilder-gt(price, 50); $in $exprBuilder-in(status, [open, pending]); $notIn $exprBuilder-notIn(status, [archived]); $like $exprBuilder-contains(title, Doctrine); $starts $exprBuilder-startsWith(name, Do); $ends $exprBuilder-endsWith(name, ine); $member $exprBuilder-memberOf(tags, php); $null $exprBuilder-isNull(deletedAt); $notNull $exprBuilder-isNotNull(deletedAt);其中isNull()/isNotNull()是eq(..., null)/neq(..., null)的语义化封装。ExpressionBuilder还提供组合方法andX(...)、orX(...)、not(...)稍后介绍。三、CompositeExpressionAND / OR / NOT 组合表达式Doctrine\Common\Collections\Expr\CompositeExpression用于创建可交给Criteria的组合表达式。它的操作符常量如下CompositeExpression::TYPE_ANDCompositeExpression::TYPE_ORCompositeExpression::TYPE_NOT构造器接受两个参数组合类型与表达式数组use Doctrine\Common\Collections\Expr\CompositeExpression; use Doctrine\Common\Collections\Expr\Comparison; $expr1 new Comparison(key, Comparison::EQ, value1); $expr2 new Comparison(key, Comparison::EQ, value2); $and new CompositeExpression(CompositeExpression::TYPE_AND, [$expr1, $expr2]);3.1 构造期的严格校验源码级查看 src/Expr/CompositeExpression.php构造器内部会逐项校验违反规则直接抛RuntimeExceptionValue不能作为 AND/OR 的子表达式如果子表达式中出现Value实例抛Values are not supported expressions as children of and/or expressions.——因为Value只是裸值包装不是真正的布尔条件每个子表达式必须是Expression实例否则抛No expression given to CompositeExpression.NOT只允许一个子表达式当类型为TYPE_NOT时校验通过后的表达式数量必须恰好为 1否则抛Not expression only allows one expression as child.。这与原文档的说明一致使用TYPE_OR和TYPE_AND时CompositeExpression接受多个表达式作为参数使用NOT时只能提供一个表达式。同时CompositeExpression是final readonly类表达式列表通过getExpressionList()读取、类型通过getType()读取。3.2 NOT 的合法用法示例$notExpr new CompositeExpression( CompositeExpression::TYPE_NOT, [new Comparison(key, Comparison::EQ, value)], );3.3 组合表达式的求值ClosureExpressionVisitor::walkCompositeExpression()会把组合表达式翻译成组合闭包TYPE_AND所有子闭包都返回true时整体为truePHP 的array_allTYPE_OR任一子闭包返回true即为truePHP 的array_anyTYPE_NOT对唯一的子闭包结果取反。对应实现见 src/Expr/ClosureExpressionVisitor.php。另外ExpressionVisitorsrc/Expr/ExpressionVisitor.php是抽象访问者基类定义了walkComparison、walkValue、walkCompositeExpression三个抽象方法与统一的dispatch(Expression $expr)入口——任何新的后端如 ORM 的 SQL 生成器都可以继承它把同一棵表达式树翻译成自己的查询语言这正是与后端无关的架构基石。四、Criteria API表达式 排序 分页的完整查询条件Doctrine\Common\Collections\Criteria是表达式的最终宿主。它既可以通过构造器一次性配置也可以通过链式方法逐步构建。先看构造器src/Criteria.phpnew Criteria( Expression|null $expression null, // 初始 where 表达式 array|null $orderings null, // 排序形如 [name Order::Ascending] int $firstResult 0, // 起始偏移 int|null $maxResults null, // 最大返回条数 );以下按原文档的 API 清单逐项展开。4.1 where()设置 where 表达式设置本次Criteria被搜索时要评估的 where 表达式use Doctrine\Common\Collections\Expr\Comparison; $expr new Comparison(key, Comparison::EQ, value); $criteria-where($expr);源码中where()直接替换内部表达式返回$this支持链式。当后续调用andWhere/orWhere时若当前表达式为null则直接委托给where()完成首次设置见下方源码逻辑。4.2 andWhere()用 AND 追加条件将新表达式与之前的表达式以AND方式组合$expr new Comparison(key, Comparison::EQ, value); $criteria-andWhere($expr);源码实现如果已有表达式则构造new CompositeExpression(TYPE_AND, [当前表达式, 新表达式])替换当前表达式若还没有表达式则等价于where($expr)。因此连续多次andWhere()会构建出一棵逐层嵌套的 AND 表达式树语义等价于所有条件同时成立。4.3 orWhere()用 OR 追加条件将新表达式与之前的表达式以OR方式组合$expr1 new Comparison(key, Comparison::EQ, value1); $expr2 new Comparison(key, Comparison::EQ, value2); $criteria-where($expr1); $criteria-orWhere($expr2);源码实现与andWhere()对称new CompositeExpression(TYPE_OR, [当前表达式, 新表达式])。上述代码最终等价于key等于value1或key等于value2。4.4 链式组合 一次性构造的两种等价写法use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Order; // 写法一链式方法 $criteria Criteria::create() -where(Criteria::expr()-eq(price, 100)) -andWhere(Criteria::expr()-gt(stock, 0)) -orderBy([name Order::Ascending]) -setFirstResult(0) -setMaxResults(20); // 写法二构造器一次性传入 $criteria new Criteria( new CompositeExpression( CompositeExpression::TYPE_AND, [Criteria::expr()-eq(price, 100), Criteria::expr()-gt(stock, 0)], ), [name Order::Ascending], 0, 20, );两种写法得到的Criteria等价。实际使用中Criteria::create()是静态工厂方法不带参数而Criteria::expr()返回共享的表达式构建器。4.5 orderBy() / getOrderings()结果排序设置本次Criteria结果的排序方式use Doctrine\Common\Collections\Order; $criteria-orderBy([name Order::Ascending]);orderBy()接收一个字段 排序方向的关联数组。排序方向在本仓库中有两个枚举来源Doctrine\Common\Collections\Ordersrc/Order.phpOrder::Ascending ASC、Order::Descending DESC。该枚举被标记为deprecated推荐改用全局SortDirection枚举SortDirectionSortDirection::Ascending/SortDirection::Descending是当前推荐写法测试用例 tests/CollectionTest.php 中new Criteria(null, [foo SortDirection::Descending])即为此用法。源码中Criteria内部以Order|SortDirection混存排序配置getOrderings()读取时会统一归一化为SortDirection返回旧的orderings()方法自 3.1 起Deprecated则反向归一化为Order。在ArrayCollection::matching()的排序实现src/ArrayCollection.php中每个排序字段会通过ClosureExpressionVisitor::sortByField($field, $orientation, $next)生成比较闭包升序方向为1、降序方向为-1多个字段时通过$next闭包逐级串联实现按多字段依次排序。sortByField在字段值相等时委托给下一级比较闭包因此排序字段的顺序就是排序优先级顺序。4.6 setFirstResult() / getFirstResult()起始偏移设置并读取本次Criteria应该从第几条结果开始返回$criteria-setFirstResult(0); // 之后 $criteria-setFirstResult(10); echo $criteria-getFirstResult(); // 输出 10setFirstResult()返回$this支持链式调用getFirstResult()返回int|null构造器默认将firstResult初始化为 0。语义等同于 SQL 的OFFSET配合maxResults实现分页。4.7 setMaxResults() / getMaxResults()最大返回条数设置并读取本次Criteria最多返回的结果条数$criteria-setMaxResults(20); echo $criteria-getMaxResults(); // 输出 20setMaxResults(int|null $maxResults)允许传null表示不限条数也是构造器默认值等同于 SQL 的LIMIT。与firstResult组合即为经典分页第 N 页的写法是setFirstResult(($page - 1) * $pageSize)-setMaxResults($pageSize)。4.8 完整分页排序示例use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Order; // 查询 price 50 且 status 为 open 的商品 // 按 price 降序、name 升序取第 2 页每页 20 条 $criteria Criteria::create() -where(Criteria::expr()-gte(price, 50)) -andWhere(Criteria::expr()-eq(status, open)) -orderBy([ price Order::Descending, name Order::Ascending, ]) -setFirstResult(20) // 第二页起始偏移 -setMaxResults(20); // 每页 20 条 $products $collection-matching($criteria);五、落地执行matching() 与测试验证5.1 从 Criteria 到筛选结果Criteria本身不执行筛选它只是查询条件的描述。执行方是实现了Selectable接口的集合src/Selectable.php的matching(Criteria $criteria)方法。以内存集合ArrayCollection为例src/ArrayCollection.php其matching()内部流程为用ClosureExpressionVisitor把Criteria中的表达式树翻译成判定闭包若设置了orderings用ClosureExpressionVisitor::sortByField()按字段与方向构造多级排序比较器并排序应用firstResult/maxResults偏移与截断返回一个新的、保留原键的集合。测试用例 tests/CollectionTest.php 对这条链路给出了可运行的验证例如$this-collection-matching(new Criteria(Criteria::expr()-eq(fooBar, 42)))验证 EQ 筛选new Criteria(null, [fooBar Order::Ascending])验证排序见 tests/ArrayCollectionTestCase.phpnew Criteria(null, null, $firstResult, $maxResult)验证分页参数。这些测试同时覆盖了ArrayCollection与懒加载集合AbstractLazyCollectionTest两条实现路径。5.2 为什么推荐用表达式 API 而不是手写循环Selectable接口的设计目标源码注释原文是提供一种与后端无关的从集合中获取元素的方式Expression被刻意构造为既能在内存集合上实现查询也能在数据库背书集合上实现查询。对数据库背书集合而言matching()可以利用底层查询 API如 ORM 的 SQL高效执行应用层无需直接操作EntityManager或 Repository。也就是说同样的Criteria对象在内存集合上翻译成闭包过滤在 ORM 场景下翻译成 SQL应用代码无需改动。六、常用模式速查需求推荐写法精确匹配Criteria::expr()-eq(field, $value)范围筛选Criteria::expr()-gte(price, 50)-andX(...)或where(gt)-andWhere(lte)多值匹配Criteria::expr()-in(status, [a, b])排除多值Criteria::expr()-notIn(status, [x])模糊包含Criteria::expr()-contains(title, Doctrine)前缀/后缀startsWith(name, Do)/endsWith(name, ine)集合成员判断Criteria::expr()-memberOf(tags, php)空值判断isNull(deletedAt)/isNotNull(deletedAt)多条件 ANDandWhere()连续追加或CompositeExpression::TYPE_AND多条件 ORorWhere()连续追加或CompositeExpression::TYPE_OR取反CompositeExpression::TYPE_NOT仅允许一个子表达式排序orderBy([name Order::Ascending, price SortDirection::Descending])分页setFirstResult($offset)-setMaxResults($limit)七、补充说明与最佳实践保持表达式树扁平andWhere()/orWhere()的连续调用会在内部逐层嵌套CompositeExpression逻辑上等价于同层 AND/OR但如需更清晰的树形结构例如(A AND B) OR C这种混合优先级建议直接构造嵌套的CompositeExpression或使用ExpressionBuilder::andX() / orX() / not()。ExpressionBuilder的跨实现注意其源码注释src/ExpressionBuilder.php明确提醒——为保证可互操作的代码比较应只使用标量值否则 Array、ORM、ODM 各实现之间的比较行为可能不一致。只读语义matching()返回的是新集合保留原键不会修改原集合内容适合在函数式、不可变风格的业务代码中安全使用。遗留 API 注意Order枚举与Criteria::orderings()已标记弃用自 3.1 起新代码请使用SortDirection与getOrderings()。参考资料仓库内可继续深入表达式接口与实现src/Expr/Expression.php、src/Expr/Comparison.php、src/Expr/CompositeExpression.php、src/Expr/Value.php访问者与执行器src/Expr/ExpressionVisitor.php、src/Expr/ClosureExpressionVisitor.php查询条件与构建器src/Criteria.php、src/ExpressionBuilder.php、src/Order.php、src/Selectable.php集合执行实现src/ArrayCollection.php测试用例tests/CollectionTest.php、tests/ArrayCollectionTestCase.php、tests/Expr/ComparisonTest.php、tests/Expr/CompositeExpressionTest.php赞分享后端【免费下载链接】collectionsCollections Abstraction Library项目地址https://gitcode.com/gh_mirrors/co/collections点击查看免费下载相关推荐ScriptCat脚本猫与油猴的终极对比为什么你应该选择脚本猫ScriptCat脚本猫与油猴的终极对比为什么你应该选择脚本猫 在浏览器扩展的世界里用户脚本工具一直是提升网页体验的神器。作为两款主流的用户脚本管理器 S前端开发者工具插件系统Watchman 查询表达式中的 allof 聚合逻辑与AND组合的完整指南Watchman 查询表达式中的 allof 聚合逻辑与AND组合的完整指南 导读 allof 是 Watchman 查询表达式Expression T后端开发工具Doctrine Collections表达式访问者模式ClosureExpressionVisitor深度剖析Doctrine Collections表达式访问者模式ClosureExpressionVisitor深度剖析 Doctrine Collections库是后端上一篇Anteon日志轮转配置避免监控系统自身日志占用过多磁盘空间下一篇Buzz 离线语音转文字三平台安装、实时录音转字幕的 7 个常见问题一次讲清创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考