PHP-Spezifikationen

Happyr Doctrine Spezifikation

Kurz zu den Spezifikationen:


Eine Spezifikation ist ein Entwurfsmuster, mit dem Sie die Regeln der Geschäftslogik in Form einer Kette von Objekten widerspiegeln können, die durch boolesche Logikoperationen verbunden sind. Mit den Spezifikationen können Sie doppelte, ähnliche Methoden im Repository entfernen und die Geschäftslogik duplizieren.

Heute gibt es zwei erfolgreiche und beliebte PHP-Projekte (wenn Sie andere Projekte kennen, schreiben Sie bitte in die Kommentare) , mit denen Sie Geschäftsregeln in Spezifikationen beschreiben und Datensätze filtern können. Dies sind die Spezifikationen von RulerZ und Happyr Doctrine . Beide Projekte sind leistungsstarke Werkzeuge mit ihren Vor- und Nachteilen. Beim Vergleich dieser Projekte wird ein ganzer Artikel gezeichnet. Hier möchte ich Ihnen sagen, was uns die neue Version in der Doctrine Specification gebracht hat.


Kurz über die Doctrine Specification


Wer mit dem Projekt mehr oder weniger vertraut ist, kann diesen Abschnitt sicher überspringen .


Mit Hilfe dieses Projekts können Sie Spezifikationen in Form von Objekten beschreiben, diese aus der Komposition zusammensetzen und dadurch komplexe Geschäftsregeln erstellen. Die resultierenden Zusammensetzungen können frei wiederverwendet und zu noch komplexeren Zusammensetzungen kombiniert werden, die leicht zu testen sind. Die Doctrine-Spezifikation wird verwendet, um Doctrine-Abfragen zu erstellen. Im Wesentlichen ist die Doctrine-Spezifikation die Abstraktionsebene über den Doctrine ORM QueryBuilder und die Doctrine ORM Query.


Die Spezifikationen gelten über das Doctrine Repository:


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

Die Spezifikation kann manuell angewendet werden, ist jedoch nicht besonders praktisch und auf lange Sicht sinnlos.
 $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(); 

Es gibt verschiedene Methoden im Repository:


  • match - alle Ergebnisse erhalten, die der Spezifikation entsprechen;
  • matchSingleResult - Äquivalent zu Query::getSingleResult() ;
  • matchOneOrNullResult - entspricht matchSingleResult , lässt jedoch null ;
  • getQuery - getQuery einen QueryBuilder, indem eine Spezifikation darauf angewendet wird, und gibt ein Query-Objekt daraus zurück.

Kürzlich wurde ihnen die Methode getQueryBuilder hinzugefügt, mit der ein QueryBuilder erstellt und unter Anwendung der Spezifikation zurückgegeben wird.


Das Projekt identifiziert verschiedene Arten von Spezifikationen:



Logische Spezifikationen


Die andX und orX dienen auch als Sammlung von Spezifikationen.


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

Es ist üblich, Objekte mit Bibliotheksspezifikationen über die Spezifikationsfassade zu installieren, dies ist jedoch nicht erforderlich. Sie können das Spezifikationsobjekt explizit instanziieren:


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

Filterspezifikation


Filterspezifikationen bilden tatsächlich die Regeln der Geschäftslogik und werden in der WHERE Anforderung verwendet. Dazu gehören Vergleichsoperationen:


  • isNull - SQL ist IS NULL äquivalent
  • isNotNull - SQL ist IS NOT NULL äquivalent
  • in - äquivalent zu IN ()
  • notIn - NOT IN () äquivalent
  • eq - Gleichheitstest =
  • neq - auf Ungleichheit prüfen !=
  • lt weniger als <
  • lte - kleiner oder gleich <=
  • gt - mehr als >
  • gte - größer oder gleich >=
  • like - SQL LIKE Äquivalent
  • instanceOfX - Äquivalent zu DQL INSTANCE OF

Ein Beispiel für die Verwendung von Filterspezifikationen:


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

Abfragemodifikatoren


Abfragemodifikatoren haben nichts mit Geschäftslogik und Geschäftsregeln zu tun. Wie der Name schon sagt, ändern sie nur QueryBuilder. Der Name und der Zweck der vordefinierten Modifikatoren entsprechen ähnlichen Methoden in QueryBuilder.


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

Ich möchte den slice Modifikator separat notieren. Es kombiniert die Funktionen limit und offset und berechnet den Offset basierend auf der Größe des Slice und seiner Seriennummer. Bei der Implementierung dieses Modifikators waren wir mit dem Autor des Projekts nicht einverstanden. Mit der Erstellung eines Modifikators verfolgte ich das Ziel, die Konfiguration von Spezifikationen während der Paginierung zu vereinfachen. In diesem Zusammenhang sollte die erste Seite mit der Seriennummer 1 dem ersten Slice mit der Seriennummer 1 entsprechen. Der Autor des Projekts hielt es jedoch für richtig, die Zählung im Programmierstil zu starten, dh 0. Daher sollten Sie angeben, dass Sie angeben müssen, wenn Sie das erste Slice benötigen 0 als Seriennummer.


Ergebnismodifikatoren


Ergebnismodifikatoren existieren geringfügig außerhalb der Spezifikationen. Sie gelten für Doctrine Query. Die folgenden Modifikatoren steuern die Query::setHydrationMode() ):


  • asArray
  • asSingleScalar
  • asScalar

Der cache Modifikator steuert das Caching des Abfrageergebnisses.


Wir sollten auch den Modifikator roundDateTimeParams . Es hilft bei der Lösung von Caching-Problemen, wenn Sie mit Geschäftsregeln arbeiten müssen, bei denen einige Werte mit der aktuellen Zeit verglichen werden müssen. Dies sind normale Geschäftsregeln, aber aufgrund der Tatsache, dass die Zeit keine Konstante ist, funktioniert das Zwischenspeichern für mehr als eine Sekunde für Sie nicht. Der Modifikator roundDateTimeParams entwickelt, um dieses Problem zu lösen. Es durchläuft alle Parameter der Anforderung, sucht nach dem darin enthaltenen Datum und rundet es auf den angegebenen Wert ab. Dadurch erhalten wir Datumswerte, die immer ein Vielfaches eines Werts sind, und wir werden das Datum in Zukunft nicht mehr erhalten. Das heißt, wenn wir die Anforderung 10 Minuten zwischenspeichern möchten, verwenden wir Spec::cache(600) und Spec::roundDateTimeParams(600) . Ursprünglich wurde vorgeschlagen, diese beiden Modifikatoren der Einfachheit halber zu kombinieren, es wurde jedoch beschlossen, sie für SRP zu trennen.


Eingebettete Spezifikationen


Die Happyr Doctrine-Spezifikation verfügt über eine separate Schnittstelle für Spezifikationen, die einen Filter und einen Anforderungsmodifikator kombiniert. Die einzige vordefinierte Spezifikation ist countOf der Sie die Anzahl der Entitäten ermitteln können, die der Spezifikation entsprechen. Um Ihre eigenen Spezifikationen zu erstellen, ist es üblich, die abstrakte BaseSpecification Klasse zu erweitern.


Innovationen


Dem Repository wurden neue Methoden hinzugefügt:


  • matchSingleScalarResult - Äquivalent zu Query::getSingleScalarResult() ;
  • matchScalarResult - entspricht Query::getScalarResult() ;
  • iterate ist das Äquivalent von Query::iterate() .

Die MemberOfX Spezifikation wird MemberOfX - das DQL-Äquivalent von MEMBER OF und der Abfragemodifikator indexBy werden indexBy - das Äquivalent von QueryBuilder::indexBy() .


Operanden


Die neue Version führt das Konzept des Operanden ein . Alle Bedingungen in den Filtern bestehen aus linken und rechten Operanden und einem Operator dazwischen.


 <left_operand> <operator> <right_operand> 

In früheren Versionen konnte der linke Operand nur ein Entitätsfeld und der rechte Operand nur ein Wert sein. Dies ist ein einfacher und effektiver Mechanismus, der für die meisten Aufgaben ausreicht. Gleichzeitig werden bestimmte Einschränkungen auferlegt:


  • Funktionen können nicht verwendet werden;
  • Aliase können nicht für Felder verwendet werden.
  • Es ist unmöglich, zwei Felder zu vergleichen.
  • Es ist unmöglich, zwei Werte zu vergleichen;
  • Arithmetische Operationen können nicht verwendet werden.
  • Datentyp für Wert kann nicht angegeben werden.

In der neuen Version werden Operandenobjekte in Argumenten an die Filter übergeben und ihre Transformation in DQL an die Operanden selbst delegiert. Dies eröffnet viele Möglichkeiten und erleichtert Filter.


Feld und Wert


Um die Abwärtskompatibilität aufrechtzuerhalten, wird das erste Argument in den Filtern in einen Feldoperanden konvertiert, wenn es kein Operand ist, und das letzte Argument wird in einen Wertoperanden konvertiert. Daher sollten Sie keine Probleme beim Aktualisieren haben.


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

Sie können 2 Felder vergleichen:


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

Sie können 2 Felder verschiedener Entitäten vergleichen:


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

Arithmetische Operationen


Unterstützung für Standard-Arithmetikoperationen hinzugefügt - , + , * , / , % . Betrachten Sie beispielsweise die Berechnung von Benutzerpunkten:


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

Arithmetische Operationen können ineinander verschachtelt werden:


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

Funktionen


Die neue Version fügte Operanden mit Funktionen hinzu. Sie können als statische Methoden der Spec Klasse oder über die Spec::fun() -Methode verwendet werden.


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

Funktionen können ineinander verschachtelt werden:


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

Argumente für Funktionen können als separate Argumente oder durch Übergabe in einem Array übergeben werden:


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

Probenahmeverwaltung


Manchmal müssen Sie eine Liste von Rückgabewerten verwalten. Zum Beispiel:


  • Fügen Sie dem Ergebnis eine weitere Entität hinzu, um keine Unterabfragen zum Abrufen der Links durchzuführen.
  • Nicht die gesamte Entität zurückgeben, sondern nur eine Reihe separater Felder;
  • Verwenden Sie Aliase.
  • Verwenden Sie versteckte Aliase mit Sortierbedingungen (es erfordert Doctrine, aber sie versprechen, es zu beheben ).

Vor Version 0.8.0 mussten für diese Aufgaben Spezifikationen für diese Anforderungen erstellt werden. Ab Version 0.8.0 können Sie die Methode getQueryBuilder() verwenden und die Auswahl über die QueryBuilder-Oberfläche verwalten.


Die neue Version 1.0.0 fügt select Anforderungsmodifikatoren select und addSelect hinzu. select ersetzt die Liste der auswählbaren Werte vollständig und addSelect fügt der Liste neue Werte hinzu. Als Wert können Sie ein Objekt verwenden, das die Selection oder einen Filter implementiert. Auf diese Weise können Sie die Funktionen der Bibliothek an Ihre Anforderungen anpassen. Betrachten Sie die Möglichkeiten, die bereits vorhanden sind.


Sie können ein Feld auswählen:


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

Sie können der Auswahl ein Feld hinzufügen:


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

Sie können mehrere Felder auswählen:


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

Sie können den zurückgegebenen Werten eine Entität hinzufügen:


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

Sie können Aliase für auswählbare Felder verwenden:


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

Sie können der Auswahl ausgeblendete Felder hinzufügen:


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

Sie können beispielsweise Ausdrücke verwenden, um einen Rabatt auf ein Produkt zu erhalten:


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

Sie können Aliase in den Spezifikationen verwenden:


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

Das ist im Grunde alles. Hier enden die Innovationen. Die neue Version hat viele interessante und nützliche Funktionen gebracht. Ich hoffe sie haben dich interessiert.


PS: Ich kann anhand eines Beispiels die Verwendung von Spezifikationen analysieren und die Vor- und Nachteile ihrer Verwendung aufzeigen. Wenn dies für Sie interessant ist, schreiben Sie in die Kommentare oder in PM.

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


All Articles