Especificaciones PHP

Happyr Doctrine Specification

Brevemente sobre las especificaciones:


Una especificación es un patrón de diseño con el que puede reflejar las reglas de la lógica empresarial en forma de una cadena de objetos conectados por operaciones lógicas booleanas. Las especificaciones le permiten deshacerse de métodos duplicados y similares en el repositorio y la duplicación de la lógica empresarial.

Hoy en día hay dos (si conoce otros proyectos, por favor escriba en los comentarios) proyectos PHP exitosos y populares que le permitan describir las reglas comerciales en las especificaciones y filtrar los conjuntos de datos. Estas son RulerZ y Happyr Doctrine Specification . Ambos proyectos son herramientas poderosas con sus ventajas y desventajas. La comparación de estos proyectos dibujará un artículo completo. Aquí quiero contarles lo que nos trajo la nueva versión de la Especificación de Doctrina.


Brevemente sobre la especificación de la doctrina


Aquellos que estén más o menos familiarizados con el proyecto, pueden saltarse esta sección de manera segura.


Con la ayuda de este proyecto, puede describir especificaciones en forma de objetos, componiéndolas a partir de la composición y, por lo tanto, elaborar reglas comerciales complejas. Las composiciones resultantes pueden reutilizarse libremente y combinarse en composiciones aún más complejas que son fáciles de probar. La especificación de Doctrine se usa para construir consultas de Doctrine. En esencia, la Especificación de Doctrine es el nivel de abstracción sobre Doctrine ORM QueryBuilder y Doctrine ORM Query.


Las especificaciones se aplican a través del repositorio de Doctrine:


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

La especificación se puede aplicar manualmente, pero no es particularmente conveniente y, a la larga, no tiene sentido.
 $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(); 

Hay varios métodos en el repositorio:


  • match : obtener todos los resultados correspondientes a la especificación;
  • matchSingleResult - equivalente de Query::getSingleResult() ;
  • matchOneOrNullResult : equivalente a matchSingleResult , pero permite null ;
  • getQuery : crea un QueryBuilder al aplicarle una especificación y le devuelve un objeto Query.

Recientemente, se getQueryBuilder ha agregado el método getQueryBuilder , que crea un QueryBuilder y, al aplicarle la especificación, lo devuelve.


El proyecto identifica varios tipos de especificaciones:



Especificaciones lógicas


Las andX y orX también sirven como una colección de especificaciones.


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

Es habitual instalar objetos de especificaciones de biblioteca a través de la fachada de Spec , pero esto no es necesario. Puede crear una instancia explícita del objeto de especificación:


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

Especificación de filtro


Las especificaciones de filtrado, de hecho, conforman las reglas de la lógica empresarial y se utilizan en la solicitud WHERE . Estas incluyen operaciones de comparación:


  • isNull - SQL IS NULL equivalente
  • isNotNull - SQL IS NOT NULL equivalente
  • in - equivalente a IN ()
  • notIn - NOT IN () equivalente
  • eq - prueba de igualdad =
  • neq - ¡busca desigualdad !=
  • lt - menos de <
  • lte - menor o igual que <=
  • gt - más de >
  • gte - mayor o igual que >=
  • like - equivalente a SQL LIKE
  • instanceOfX - equivalente de DQL INSTANCE OF

Un ejemplo de uso de especificaciones de filtrado:


 $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')) ) ) ); 

Modificadores de consulta


Los modificadores de consulta no tienen nada que ver con la lógica comercial y las reglas comerciales. Como su nombre lo indica, solo modifican QueryBuilder. El nombre y el propósito de los modificadores predefinidos corresponden a métodos similares en QueryBuilder.


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

Quiero señalar por separado el modificador de slice . Combina las funciones limit y offset y calcula el desplazamiento en función del tamaño del segmento y su número de serie. En la implementación de este modificador, no estamos de acuerdo con el autor del proyecto. Al crear un modificador, perseguí el objetivo de simplificar la configuración de las especificaciones durante la paginación. En este contexto, la primera página con el número de serie 1 debería haber sido equivalente al primer segmento con el número de serie 1. Pero el autor del proyecto consideró correcto comenzar la cuenta regresiva en un estilo de programación, es decir, desde 0. Por lo tanto, vale la pena recordar que si necesita el primer segmento, debe especificar 0 como número de serie.


Modificadores de resultados


Los modificadores de resultados existen ligeramente aparte de las especificaciones. Se aplican a Doctrine Query. Los siguientes modificadores controlan la hidratación de datos ( Query::setHydrationMode() ):


  • asArray
  • asSingleScalar
  • asScalar

El modificador de cache controla el almacenamiento en caché del resultado de la consulta.


También debemos mencionar el modificador roundDateTimeParams . Ayuda a resolver problemas de almacenamiento en caché cuando necesita trabajar con reglas comerciales que requieren comparar algunos valores con la hora actual. Estas son reglas comerciales normales, pero debido al hecho de que el tiempo no es constante, el almacenamiento en caché durante más de un segundo no funcionará para usted. El modificador roundDateTimeParams está diseñado para resolver este problema. Revisa todos los parámetros de la solicitud, busca la fecha en ellos y lo redondea al valor especificado, lo que nos da valores de fecha que siempre son múltiplos de un valor y no obtendremos la fecha en el futuro. Es decir, si queremos almacenar en caché la solicitud durante 10 minutos, usamos Spec::cache(600) y Spec::roundDateTimeParams(600) . Inicialmente, se propuso combinar estos dos modificadores por conveniencia, pero se decidió separarlos para SRP.


Especificaciones integradas


Happyr Doctrine-Specification tiene una interfaz separada para especificaciones que combina un filtro y un modificador de solicitud. La única especificación predefinida es countOf que le permite obtener el número de entidades correspondientes a la especificación. Para crear sus propias especificaciones, es costumbre extender la clase abstracta BaseSpecification .


Innovaciones


Se han agregado nuevos métodos al repositorio:


  • matchSingleScalarResult - equivalente de Query::getSingleScalarResult() ;
  • matchScalarResult - equivalente a Query::getScalarResult() ;
  • iterate es el equivalente de Query::iterate() .

Se MemberOfX especificación MemberOfX se MemberOfX el equivalente DQL de MEMBER OF y el modificador de consulta indexBy , el equivalente de QueryBuilder::indexBy() .


Operandos


El nuevo lanzamiento introduce el concepto de Operand . Todas las condiciones en los filtros consisten en operandos izquierdo y derecho y un operador entre ellos.


 <left_operand> <operator> <right_operand> 

En versiones anteriores, el operando izquierdo solo podía ser un campo de entidad, y el operando derecho solo podía ser un valor. Este es un mecanismo simple y efectivo que es suficiente para la mayoría de las tareas. Al mismo tiempo, impone ciertas restricciones:


  • Incapaz de usar funciones;
  • No se pueden usar alias para campos;
  • Es imposible comparar dos campos;
  • Es imposible comparar dos valores;
  • Incapaz de usar operaciones aritméticas;
  • No se puede especificar el tipo de datos para el valor.

En la nueva versión, los objetos de operando se pasan a los filtros en argumentos y su transformación en DQL se delega a los propios operandos. Esto abre muchas posibilidades y facilita los filtros.


Campo y valor


Para mantener la compatibilidad con versiones anteriores, el primer argumento de los filtros se convierte en un operando de campo si no es un operando, y el último argumento también se convierte en un operando de valor. Por lo tanto, no debería tener problemas para actualizar.


 // 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)); 

Puedes comparar 2 campos:


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

Puede comparar 2 campos de diferentes entidades:


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

Operaciones aritméticas


Soporte agregado para operaciones aritméticas estándar - , + , * , / , % . Por ejemplo, considere el cálculo de puntos de usuario:


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

Las operaciones aritméticas se pueden anidar una en otra:


 // 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) ); 

Las funciones


La nueva versión agregó operandos con funciones. Se pueden usar como métodos estáticos de la clase Spec , o mediante el método 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); 

Las funciones se pueden anidar una en otra:


 // 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)) ); 

Los argumentos para las funciones se pueden pasar como argumentos separados, o pasándolos en una matriz:


 // 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]); 

Gestión de muestreo


Algunas veces necesita administrar una lista de valores de retorno. Por ejemplo:


  • Agregue otra entidad al resultado para no hacer subconsultas para obtener los enlaces;
  • Para devolver no toda la entidad, sino solo un conjunto de campos separados;
  • Usa alias;
  • Utilice alias ocultos con condiciones para la clasificación (requiere Doctrine, pero prometen arreglarlo ).

Antes de la versión 0.8.0, estas tareas requerían la creación de especificaciones para estas necesidades. A partir de la versión 0.8.0, puede usar el método getQueryBuilder() y administrar la selección a través de la interfaz QueryBuilder.


La nueva versión 1.0.0 agrega los addSelect solicitud select y addSelect . select reemplaza completamente la lista de valores seleccionables, y addSelect agrega nuevos valores a la lista. Como valor, puede usar un objeto que implemente la interfaz de Selection o un filtro. Por lo tanto, puede ampliar las capacidades de la biblioteca para satisfacer sus necesidades. Considere las oportunidades que ya existen.


Puedes seleccionar un campo:


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

Puede agregar un campo a la selección:


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

Puede seleccionar varios campos:


 // 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)) ) 

Puede agregar una entidad a los valores devueltos:


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

Puede usar alias para campos seleccionables:


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

Puede agregar campos ocultos a la selección:


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

Puede usar expresiones, por ejemplo, para obtener un descuento en un producto:


 // 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' )) 

Puede usar alias en las especificaciones:


 // 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) ) 

Eso es básicamente todo. Aquí es donde terminan las innovaciones. El nuevo lanzamiento ha traído muchas características interesantes y útiles. Espero que te hayan interesado.


PD: Puedo usar un ejemplo para analizar el uso de especificaciones y mostrar las ventajas y desventajas de su uso. Si esto es interesante para usted, escriba en los comentarios o en PM.

Source: https://habr.com/ru/post/455030/


All Articles