En bref sur les spécifications:
Une spécification est un modèle de conception avec lequel vous pouvez refléter les règles de la logique métier sous la forme d'une chaîne d'objets connectés par des opérations de logique booléenne. Les spécifications vous permettent de vous débarrasser des méthodes similaires en double dans le référentiel et de la duplication de la logique métier.
Aujourd'hui, il existe deux (si vous connaissez d'autres projets, veuillez écrire dans les commentaires) des projets PHP réussis et populaires qui vous permettent de décrire les règles métier dans les spécifications et de filtrer les ensembles de données. Ce sont les spécifications RulerZ et Happyr Doctrine . Les deux projets sont des outils puissants avec leurs avantages et leurs inconvénients. La comparaison de ces projets permettra de dessiner un article entier. Ici, je veux vous dire ce que la nouvelle version de la spécification de doctrine nous a apporté.
En bref sur la spécification de la doctrine
Ceux qui connaissent plus ou moins le projet peuvent sauter cette section en toute sécurité.
Avec l'aide de ce projet, vous pouvez décrire des spécifications sous la forme d'objets, les composer à partir de la composition et, ainsi, créer des règles commerciales complexes. Les compositions résultantes peuvent être librement réutilisées et combinées en compositions encore plus complexes et faciles à tester. La spécification de doctrine est utilisée pour créer des requêtes de doctrine. En substance, la spécification Doctrine est le niveau d'abstraction par rapport à Doctrine ORM QueryBuilder et Doctrine ORM Query.
Les spécifications s'appliquent via le référentiel Doctrine:
$result = $em->getRepository(MyEntity::class)->match($spec);
La spécification peut être appliquée manuellement, mais elle n'est pas particulièrement pratique et, à long terme, est inutile. $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();
Il existe plusieurs méthodes dans le référentiel:
match
- obtenir tous les résultats correspondant à la spécification;matchSingleResult
- équivalent de Query::getSingleResult()
;matchOneOrNullResult
- équivalent à matchSingleResult
, mais autorise null
;getQuery
- crée un QueryBuilder en lui appliquant une spécification et en renvoie un objet Query.
Récemment, la méthode getQueryBuilder
a été ajoutée, ce qui crée un QueryBuilder et, en lui appliquant la spécification, le renvoie.
Le projet identifie plusieurs types de spécifications:
Spécifications logiques
Les orX
et orX
servent également de collection de spécifications.
Spec::andX()
Spec::orX()
Spec::not()
Il est habituel d'installer des objets de spécifications de bibliothèque à travers la façade Spec
, mais ce n'est pas nécessaire. Vous pouvez instancier explicitement l'objet de spécification:
new AndX(); new OrX(): new Not();
Spécifications du filtre
En effet, les spécifications de filtrage constituent les règles de la logique métier et sont utilisées dans la requête WHERE
. Il s'agit notamment d'opérations de comparaison:
isNull
- SQL IS NULL
équivalentisNotNull
- SQL IS NOT NULL
équivalent à IS NOT NULL
in
- équivalent à IN ()
notIn
- NOT IN ()
équivalenteq
- test d'égalité =
neq
- vérifier l'inégalité !=
lt
- moins de <
lte
- inférieur ou égal à <=
gt
- plus que >
gte
- supérieur ou égal à >=
like
- équivalent SQL LIKE
instanceOfX
- équivalent de DQL INSTANCE OF
Un exemple d'utilisation des spécifications de filtrage:
$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')) ) ) );
Modificateurs de requête
Les modificateurs de requête n'ont rien à voir avec la logique métier et les règles métier. Comme son nom l'indique, ils ne modifient que QueryBuilder. Le nom et l'objectif des modificateurs prédéfinis correspondent à des méthodes similaires dans QueryBuilder.
join
leftJoin
innerJoin
limit
offset
orderBy
groupBy
having
Je veux noter séparément le modificateur de slice
. Il combine les fonctions limit
et offset
et calcule le décalage en fonction de la taille de la tranche et de son numéro de série. Dans la mise en œuvre de ce modificateur, nous étions en désaccord avec l'auteur du projet. En créant un modificateur, j'ai poursuivi l'objectif de simplifier la configuration des spécifications lors de la pagination. Dans ce contexte, la première page avec le numéro de série 1 aurait dû être équivalente à la première tranche avec le numéro de série 1. Mais l'auteur du projet a jugé bon de démarrer le compte à rebours dans un style de programmation, c'est-à-dire à partir de 0. Par conséquent, il convient de se rappeler que si vous avez besoin de la première tranche, vous devez spécifier 0 comme numéro de série.
Modificateurs de résultats
Les modificateurs de résultats existent légèrement en dehors des spécifications. Ils s'appliquent à Doctrine Query. Les modificateurs suivants contrôlent l'hydratation des données ( Query::setHydrationMode()
):
asArray
asSingleScalar
asScalar
Le modificateur de cache
contrôle la mise en cache du résultat de la requête.
Il faut également mentionner le modificateur roundDateTimeParams
. Il permet de résoudre les problèmes de mise en cache lorsque vous devez travailler avec des règles métier qui nécessitent de comparer certaines valeurs avec l'heure actuelle. Ce sont des règles commerciales normales, mais en raison du fait que le temps n'est pas une constante, la mise en cache pendant plus d'une seconde ne fonctionnera pas pour vous. Le modificateur roundDateTimeParams
est conçu pour résoudre ce problème. Il passe en revue tous les paramètres de la demande, y recherche la date et l'arrondit à la valeur spécifiée, ce qui nous donne des valeurs de date qui sont toujours des multiples d'une valeur et nous n'obtiendrons pas la date à l'avenir. Autrement dit, si nous voulons mettre en cache la demande pendant 10 minutes, nous utilisons Spec::cache(600)
et Spec::roundDateTimeParams(600)
. Initialement, il a été proposé de combiner ces deux modificateurs pour plus de commodité, mais il a été décidé de les séparer pour SRP.
Spécifications intégrées
Happyr Doctrine-Specification a une interface séparée pour les spécifications qui combine un filtre et un modificateur de demande. La seule spécification prédéfinie est countOf
qui vous permet d'obtenir le nombre d'entités correspondant à la spécification. Pour créer vos propres spécifications, il est habituel d'étendre la classe abstraite BaseSpecification
.
Innovations
De nouvelles méthodes ont été ajoutées au référentiel:
matchSingleScalarResult
- équivalent de Query::getSingleScalarResult()
;matchScalarResult
- équivalent à Query::getScalarResult()
;iterate
est l'équivalent de Query::iterate()
.
La spécification MemberOfX
est MemberOfX
- l'équivalent DQL de MEMBER OF
et le modificateur de requête indexBy
sont indexBy
- l' QueryBuilder::indexBy()
.
Opérandes
La nouvelle version introduit le concept d' opérande . Toutes les conditions dans les filtres sont constituées d'opérandes gauche et droite et d'un opérateur entre elles.
<left_operand> <operator> <right_operand>
Dans les versions précédentes, l'opérande gauche ne pouvait être qu'un champ d'entité et l'opérande droit ne pouvait être qu'une valeur. Il s'agit d'un mécanisme simple et efficace qui suffit pour la plupart des tâches. En même temps, il impose certaines restrictions:
- Impossible d'utiliser les fonctions;
- Impossible d'utiliser des alias pour les champs;
- Il est impossible de comparer deux champs;
- Il est impossible de comparer deux valeurs;
- Impossible d'utiliser des opérations arithmétiques;
- Impossible de spécifier le type de données pour la valeur.
Dans la nouvelle version, les objets opérande sont passés aux filtres en arguments et leur transformation en DQL est déléguée aux opérandes eux-mêmes. Cela ouvre de nombreuses possibilités et facilite les filtres.
Champ et valeur
Pour conserver la compatibilité descendante, le premier argument des filtres est converti en opérande de champ, s'il ne s'agit pas d'un opérande, et le dernier argument est converti en opérande de valeur. Par conséquent, vous ne devriez pas avoir de problèmes de mise à jour.
Vous pouvez comparer 2 champs:
Vous pouvez comparer 2 champs d'entités différentes:
Opérations arithmétiques
Ajout de la prise en charge des opérations arithmétiques standard -
, +
, *
, /
, %
. Par exemple, considérons le calcul des points utilisateurs:
Les opérations arithmétiques peuvent être imbriquées les unes dans les autres:
Les fonctions
La nouvelle version a ajouté des opérandes avec des fonctions. Ils peuvent être utilisés comme méthodes statiques de la classe Spec
, ou via la méthode Spec::fun()
.
Les fonctions peuvent être imbriquées les unes dans les autres:
Les arguments pour les fonctions peuvent être passés en tant qu'arguments séparés, ou en les passant dans un tableau:
Gestion de l'échantillonnage
Parfois, vous devez gérer une liste de valeurs de retour. Par exemple:
- Ajoutez une autre entité au résultat afin de ne pas faire de sous-requêtes pour obtenir les liens;
- Pour renvoyer non pas l'entité entière, mais seulement un ensemble de champs séparés;
- Utilisez des alias;
- Utilisez des alias cachés avec des conditions de tri (cela nécessite Doctrine, mais ils promettent de le réparer ).
Avant la version 0.8.0, ces tâches nécessitaient la création de spécifications pour ces besoins. À partir de la version 0.8.0, vous pouvez utiliser la méthode getQueryBuilder()
et gérer la sélection via l'interface QueryBuilder.
La nouvelle version 1.0.0 ajoute select
addSelect
demande select
et addSelect
. select
remplace complètement la liste des valeurs sélectionnables et addSelect
ajoute de nouvelles valeurs à la liste. En tant que valeur, vous pouvez utiliser un objet qui implémente l'interface de Selection
ou un filtre. Ainsi, vous pouvez étendre les capacités de la bibliothèque pour répondre à vos besoins. Considérez les opportunités qui existent déjà.
Vous pouvez sélectionner un champ:
Vous pouvez ajouter un champ à la sélection:
Vous pouvez sélectionner plusieurs champs:
Vous pouvez ajouter une entité aux valeurs renvoyées:
Vous pouvez utiliser des alias pour les champs sélectionnables:
Vous pouvez ajouter des champs masqués à la sélection:
Vous pouvez utiliser des expressions, par exemple, pour obtenir une remise sur un produit:
Vous pouvez utiliser des alias dans les spécifications:
C’est essentiellement tout. C'est là que s'arrêtent les innovations. La nouvelle version a apporté de nombreuses fonctionnalités intéressantes et utiles. J'espère qu'ils vous ont intéressé.
PS: Je peux utiliser un exemple pour analyser l'utilisation des spécifications et montrer les avantages et les inconvénients de leur utilisation. Si cela vous intéresse, écrivez dans les commentaires ou en PM.