PHP规范

快乐主义规范

简要介绍一下规格:


规范是一种设计模式,您可以用它以布尔逻辑操作连接的对象链的形式反映业务逻辑规则。 规范允许您摆脱存储库中的重复,相似方法以及业务逻辑重复。

今天,有两个(如果您了解其他项目,请在评论中写下)成功且流行的PHP项目,这些项目使您可以在规范和过滤数据集中描述业务规则。 这些是RulerZHappyr学说规范 。 这两个项目都是有优势和劣势的强大工具。 比较这些项目将得出整篇文章。 在这里,我想告诉您“ Doctrine规范”中的新版本给我们带来了什么。


简要介绍教义规范


那些或多或少熟悉该项目的人可以安全地跳过此部分。


在该项目的帮助下,您可以以对象的形式描述规范,从组成上将其组成,从而制定复杂的业务规则。 所得的组合物可以自由地重复使用,并且可以组合成甚至更容易测试的复杂组合物。 原则规范用于构建原则查询。 从本质上讲,Doctrine规范是对Doctrine ORM QueryBuilder和Doctrine ORM Query的抽象级别。


规范适用于“教义存储库”:


$result = $em->getRepository(MyEntity::class)->match($spec); 

该规范可以手动应用,但并不是特别方便,并且从长远来看毫无意义。
 $spec = ... $alias = 'e'; $qb = $em->getRepository(MyEntity::class)->createQueryBuilder($alias); $spec->modify($qb, $alias); $filter = (string) $spec->getFilter($qb, $alias); $qb->andWhere($filter); $result = $qb->getQuery()->execute(); 

存储库中有几种方法:


  • match -获取与规范相对应的所有结果;
  • matchSingleResult等效于Query::getSingleResult() ;
  • matchOneOrNullResult等同于matchSingleResult ,但允许为null
  • getQuery通过对其应用规范来创建QueryBuilder并从中返回Query对象。

最近,已经向其中添加了getQueryBuilder方法,该方法创建了一个QueryBuilder并将其应用规范返回。


该项目确定了几种类型的规范:



逻辑规格


andXorX也用作orX的集合。


  • Spec::andX()
  • Spec::orX()
  • Spec::not()

通常通过Spec外观安装库规范的对象,但这不是必需的。 您可以显式实例化规范对象:


 new AndX(); new OrX(): new Not(); 

过滤器规格


实际上,过滤规范构成了业务逻辑规则,并在WHERE请求中使用。 其中包括比较操作:


  • isNull -SQL IS NULL等效
  • isNotNull等效的SQL IS NOT NULL
  • in-相当于IN ()
  • notIn相当于NOT IN ()
  • eq相等测试=
  • neq检查不平等!=
  • lt小于<
  • lte小于或等于<=
  • gt大于>
  • gte大于或等于>=
  • like -SQL LIKE等效
  • instanceOfX等效于DQL INSTANCE OF

使用过滤规范的示例:


 $spec = Spec::andX( Spec::eq('ended', 0), Spec::orX( Spec::lt('endDate', new \DateTime()), Spec::andX( Spec::isNull('endDate'), Spec::lt('startDate', new \DateTime('-4 weeks')) ) ) ); 

查询修饰符


查询修饰符与业务逻辑和业务规则无关。 顾名思义,它们仅修改QueryBuilder。 预定义修饰符的名称和目的与QueryBuilder中的类似方法相对应。


  • join
  • leftJoin
  • innerJoin
  • limit
  • offset
  • orderBy
  • groupBy
  • having

我要单独注意slice修改器。 它结合了limitoffset功能,并根据切片的大小及其序列号计算偏移量。 在实现此修饰符时,我们不同意该项目的作者。 创建修饰符后,我追求的目标是简化分页期间的规格配置。 在这种情况下,序列号为1的第一页应该与序列号为1的第一个切片等效。但是项目作者认为以编程样式即0开始倒计时是正确的。因此,值得记住的是,如果需要第一个切片,则需要指定0作为序列号。


结果修饰符


结果修饰符与规范略有不同。 它们适用于学说查询。 以下修饰符控制数据水合( Query::setHydrationMode() ):


  • asArray
  • asSingleScalar
  • asScalar

cache修改器控制查询结果的缓存。


我们还应该提到修饰符roundDateTimeParams 。 当您需要使用需要将一些值与当前时间进行比较的业务规则时,它有助于解决缓存问题。 这些是正常的业务规则,但是由于时间不是恒定的,因此缓存一秒钟以上对您不起作用。 roundDateTimeParams修饰符旨在解决此问题。 它遍历请求的所有参数,在其中搜索日期,并将其四舍五入为指定的值,这将使我们获得的日期值始终是一个值的倍数,而将来我们将无法获得日期。 也就是说,如果我们想将请求缓存10分钟,则可以使用Spec::cache(600)Spec::roundDateTimeParams(600) 。 最初,为了方便起见,建议将这两个修饰符结合在一起,但决定将它们分开以进行SRP。


嵌入式规格


Happyr Doctrine-Specification具有单独的规范接口,该接口结合了过滤器和请求修饰符。 唯一的预定义规范是countOf它允许您获取与规范相对应的实体数。 要创建自己的规范,通常会扩展抽象BaseSpecification类。


革新性


新方法已添加到存储库:


  • matchSingleScalarResult等效于Query::getSingleScalarResult() ;
  • matchScalarResult等同于Query::getScalarResult() ;
  • iterate等效于Query::iterate()

MemberOfXMemberOfX规范- MemberOfXMEMBER OF的DQL等效项MEMBER OF并且indexByindexBy查询修饰符-了QueryBuilder::indexBy()的等效项。


操作数


新版本引入了Operand的概念。 过滤器中的所有条件均由左右操作数以及它们之间的运算符组成。


 <left_operand> <operator> <right_operand> 

在以前的版本中,左操作数只能是一个实体字段,而右操作数只能是一个值。 这是一种简单有效的机制,足以完成大多数任务。 同时,它施加了某些限制:


  • 无法使用功能;
  • 不能为字段使用别名;
  • 比较两个字段是不可能的。
  • 比较两个值是不可能的。
  • 无法使用算术运算;
  • 无法为值指定数据类型。

在新版本中,将操作数对​​象传递到参数中的过滤器,并将它们在DQL中的转换委派给操作数本身。 这开辟了许多可能性,并使过滤器更容易。


领域与价值


为了保持向后兼容性,如果过滤器中的第一个参数不是操作数,则将其转换为字段操作数,最后一个参数也将转换为值操作数。 因此,您应该没有问题更新。


 // DQL: e.day > :day Spec::gt('day', $day); // or Spec::gt(Spec::field('day'), $day); // or Spec::gt(Spec::field('day', $dqlAlias), $day); 

 // DQL: e.day > :day Spec::gt('day', $day); // or Spec::gt('day', Spec::value($day)); // or Spec::gt('day', Spec::value($day, Type::DATE)); 

您可以比较2个字段:


 // DQL: e.price_current < e.price_old Spec::lt(Spec::field('price_current'), Spec::field('price_old')); 

您可以比较不同实体的2个字段:


 // DQL: a.email = u.email Spec::eq(Spec::field('email', 'a'), Spec::field('email', 'u')); 

算术运算


添加了对标准算术运算符-+*/% 。 例如,考虑用户点的计算:


 // DQL: e.posts_count + e.likes_count > :user_score Spec::gt( Spec::add(Spec::field('posts_count'), Spec::field('likes_count')), $user_score ); 

算术运算可以相互嵌套:


 // DQL: ((e.price_old - e.price_current) / (e.price_current / 100)) > :discount Spec::gt( Spec::div( Spec::sub(Spec::field('price_old'), Spec::field('price_current')), Spec::div(Spec::field('price_current'), Spec::value(100)) ), Spec::value($discount) ); 

功能介绍


新版本添加了带有函数的操作数。 它们可以用作Spec类的静态方法,也可以通过Spec::fun()方法使用。


 // DQL: size(e.products) > 2 Spec::gt(Spec::size('products'), 2); // or Spec::gt(Spec::fun('size', 'products'), 2); // or Spec::gt(Spec::fun('size', Spec::field('products')), 2); 

函数可以彼此嵌套:


 // DQL: trim(lower(e.email)) = :email Spec::eq(Spec::trim(Spec::lower('email')), trim(strtolower($email))); // or Spec::eq( Spec::fun('trim', Spec::fun('lower', Spec::field('email'))), trim(strtolower($email)) ); 

函数的参数可以作为单独的参数传递,也可以通过将其传递给数组来传递:


 // DQL: DATE_DIFF(e.create_at, :date) Spec::DATE_DIFF('create_at', $date); // or Spec::DATE_DIFF(['create_at', $date]); // or Spec::fun('DATE_DIFF', 'create_at', $date); // or Spec::fun('DATE_DIFF', ['create_at', $date]); 

抽样管理


有时您需要管理一个返回值列表。 例如:


  • 在结果中添加另一个实体,以免进行子查询来获取链接;
  • 不返回整个实体,而是仅返回一组单独的字段;
  • 使用别名;
  • 使用带有隐藏条件的别名进行排序(它要求使用Doctrine,但它们承诺会对其进行修复 )。

在0.8.0版之前,这些任务需要创建满足这些需求的规范。 从版本0.8.0开始,可以使用getQueryBuilder()方法并通过QueryBuilder界面管理选择。


新版本1.0.0添加了selectaddSelect请求addSelectselect完全替换了可选值列表,而addSelect将新值添加到列表中。 作为值,可以使用实现Selection接口或过滤器的对象。 因此,您可以根据需要扩展库的功能。 考虑已经存在的机会。


您可以选择一个字段:


 // DQL: SELECT e.email FROM ... Spec::select('email') // or Spec::select(Spec::field('email')) 

您可以将一个字段添加到选择中:


 // DQL: SELECT e, u.email FROM ... Spec::addSelect(Spec::field('email', $dqlAlias)) 

您可以选择几个字段:


 // DQL: SELECT e.title, e.cover, u.name, u.avatar FROM ... Spec::andX( Spec::select('title', 'cover'), Spec::addSelect(Spec::field('name', $dqlAlias), Spec::field('avatar', $dqlAlias)) ) 

您可以将实体添加到返回值:


 // DQL: SELECT e, u FROM ... Spec::addSelect(Spec::selectEntity($dqlAlias)) 

您可以对可选字段使用别名:


 // DQL: SELECT e.name AS author FROM ... Spec::select(Spec::selectAs(Spec::field('name'), 'author')) 

您可以将隐藏字段添加到选择中:


 // DQL: SELECT e, u.name AS HIDDEN author FROM ... Spec::addSelect(Spec::selectHiddenAs(Spec::field('email', $dqlAlias), 'author'))) 

例如,您可以使用表达式来获得产品的折扣:


 // DQL: SELECT (e.price_old is not null and e.price_current < e.price_old) AS discount FROM ... Spec::select(Spec::selectAs( Spec::andX( Spec::isNotNull('price_old'), Spec::lt(Spec::field('price_current'), Spec::field('price_old')) ), 'discount' )) 

您可以在规范中使用别名:


 // DQL: SELECT e.price_current AS price FROM ... WHERE price < :low_cost_limit Spec::andX( Spec::select(Spec::selectAs('price_current', 'price')), Spec::lt(Spec::alias('price'), $low_cost_limit) ) 

基本上就是这些。 这就是创新的终点。 新版本带来了许多有趣且有用的功能。 我希望他们对你感兴趣。


PS:我可以通过一个示例分析规范的使用,并说明使用规范的优缺点。 如果您对此感兴趣,请在评论中或PM中写。

Source: https://habr.com/ru/post/zh-CN455030/


All Articles