Searcher est un générateur de requêtes de recherche indépendant du framework. Les requêtes de recherche sont écrites à l'aide de critères et peuvent être exécutées sur MySQL, MongoDB, ElasticSearch, des fichiers ou tout ce que vous voulez. La dernière version ne prend en charge que PHP 7 . Maintenant testé également avec Humbug
Avez-vous déjà vu un code chargé de rechercher quelque chose en fonction de nombreux critères différents ? Cela peut devenir un vrai désastre ! Imaginez que vous disposez d'un formulaire comportant 20 champs et que tous ont un impact sur les conditions de recherche. Ce n'est pas une bonne idée de transmettre un formulaire entier à un service pour le laisser tout analyser au même endroit. Grâce à cette bibliothèque, vous pouvez répartir la responsabilité de la création des critères de requête entre plusieurs classes plus petites. Une classe par filtre. Un CriteriaBuilder
par Criteria
. De cette façon, dans CriteriaBuilder
vous ne vous souciez que d'un seul Criteria
, ce qui le rend beaucoup plus lisible et maintenable. Vous pouvez ensuite utiliser exactement les mêmes Criteria
pour différentes recherches, avec différents CriteriaBuilder
et même différents SearchingContext
qui peuvent utiliser même des bases de données différentes. Vous pouvez même utiliser le moteur de recherche pour rechercher des fichiers sur votre système grâce à FinderSearchingContext
.
La documentation complète peut être trouvée sur http://searcher.rtfd.io/
Vous pouvez installer la bibliothèque via composer en tapant dans le terminal :
$ compositeur nécessite krzysztof-gzocha/searcher
L'intégration avec Symfony se fait dans SearcherBundle
CriteriaBuilder
- construira de nouvelles conditions pour un seul Criteria
,
Criteria
- modèle qui sera transmis à CriteriaBuilder
. Il vous suffit de l'hydrater d'une manière ou d'une autre, ce sera donc utile. Les critères peuvent contenir plusieurs champs et tous (ou certains) d'entre eux peuvent être utilisés dans CriteriaBuilder
,
SearchingContext
- contexte d'une recherche unique. Ce service doit savoir comment récupérer les résultats d'une requête construite et il contient quelque chose appelé QueryBuilder
, mais il peut s'agir de tout ce qui fonctionne pour vous - n'importe quel service. Il s'agit d'une couche d'abstraction entre la recherche et la base de données. Il existe différents contextes pour ORM, ODM, Elastica, Files de Doctrine, etc. S'il n'y a pas de contexte pour vous, vous pouvez en implémenter un - cela ne devrait pas être difficile,
Searcher
- contient la collection de CriteriaBuilder
et transmettra Criteria
au CriteriaBuilder
approprié.
Supposons que nous souhaitions rechercher des personnes dont l'âge se situe dans une plage filtrée. Dans cet exemple, nous utiliserons QueryBuilder de Doctrine, nous utiliserons donc QueryBuilderSearchingContext
et spécifierons dans notre CriteriaBuidler
qu'il ne doit interagir qu'avec DoctrineORMQueryBuilder
, mais rappelez-vous que nous ne sommes pas obligés d'utiliser uniquement Doctrine.
Tout d’abord, nous aurions besoin de créer AgeRangeCriteria
– la classe qui contiendra les valeurs d’âge minimal et maximal. Il existe déjà Criteria
par défaut implémentés ici.
la classe AgeRangeCriteria implémente CriteriaInterface{private $minimalAge;private $maximalAge;/** * Seule méthode obligatoire. * Si renvoie true, alors il sera transmis à certains des CriteriaBuilder */public function ShouldBeApplied() : bool{return null !== $this->minimalAge && null !== $this->maximalAge; }// getters, setters, peu importe}
Dans un deuxième temps, nous souhaiterions préciser les conditions qui devraient être imposées pour ce modèle. C'est pourquoi nous aurions besoin de créer AgeRangeCriteriaBuilder
la classe AgeRangeCriteriaBuilder implémente CriteriaBuilderInterface{public function buildCriteria(CriteriaInterface $criteria,SearchingContextInterface $searchingContext) {$searchingContext->getQueryBuilder() ->etOù('e.age >= :minimalAge') ->etOù('e.age <= :maximalAge') ->setParameter('minimalAge', $criteria->getMinimalAge()) ->setParameter('maximalAge', $criteria->getMaximalAge()); }fonction publique makesCriteria(CriteriaInterface $criteria): bool{return $criteria instanceof AgeRangeCriteria; }/** * Vous pouvez ignorer cette méthode si vous souhaitez étendre AbstractORMCriteriaBuilder. */fonction publique prend en chargeSearchingContext(SearchingContextInterface $searchingContext) : bool{return $searchingContext instanceof QueryBuilderSearchingContext ; } }
Dans les prochaines étapes, nous devrons créer des collections pour les deux : Criteria
et CriteriaBuidler
.
$builders = new CriteriaBuilderCollection();$builders->addCriteriaBuilder(new AgeRangeCriteriaBuilder());$builders->addCriteriaBuilder(/** reste des constructeurs */);
$ageRangeCriteria = new AgeRangeCriteria();// Nous devons remplir le modèle avant de chercher$ageRangeCriteria->setMinimalAge(23);$ageRangeCriteria->setMaximalAge(29);$criteria = new CriteriaCollection();$criteria->addCriteria( $ageRangeCriteria);$criteria->addCriteria(/** reste des critères */);
Nous aimerions maintenant créer notre SearchingContext
et le remplir avec QueryBuilder extrait de Doctrine ORM.
$context = nouveau QueryBuilderSearchingContext($queryBuilder);$searcher = nouveau Searcher($builders, $context);$searcher->search($criteriaCollection); // Ouais, nous avons nos résultats !
S'il y a ne serait-ce qu'une petite chance que votre QueryBuilder renvoie null
lorsque vous attendez un objet ou un tableau traversable, vous pouvez utiliser WrappedResultsSearcher
au lieu de la classe Searcher
normale. Il agira exactement de la même manière que Searcher
, mais il renverra ResultCollection
, qui ne fonctionnera qu'avec array ou Traversable
et si le résultat sera simplement null
votre code fonctionnera toujours. Voici à quoi cela ressemblera :
$searcher = new WrappedResultsSearcher(new Searcher($builders, $context));$results = $searcher->search($criteriaCollection); // instance de ResultCollectionforeach ($results as $result) {// fonctionnera !}foreach ($results->getResults() as $result) {// Puisque ResultCollection a la méthode getResults() cela fonctionnera également !}
Afin de trier vos résultats, vous pouvez utiliser des Criteria
déjà implémentés. Vous n'avez pas besoin de l'implémenter à partir de zéro. Gardez à l'esprit que vous devez toujours implémenter votre CriteriaBuilder
pour cela (cette fonctionnalité est toujours en cours de développement). Disons que vous souhaitez trier vos résultats et que vous avez besoin de la valeur p.id
dans votre CriteriaBuidler pour le faire, mais que vous souhaitez l'afficher comme pid
à l'utilisateur final. Rien de plus simple ! Voici comment créer OrderByCriteria :
$mappedFields = ['pid' => 'p.id', 'valueForUser' => 'valueForBuilder'];$criteria = new MappedOrderByAdapter(new OrderByCriteria('pid'),$mappedFields);// $criteria->getMappedOrderBy () = 'p.id'// $criteria->getOrderBy() = 'pid'
Bien sûr, vous n'avez pas besoin d'utiliser MappedOrderByAdapter
- vous pouvez utiliser simplement OrderByCriteria
, mais l'utilisateur saura alors exactement quels champs sont utilisés pour trier.
Criteria
de pagination sont également implémentés et vous n'avez pas besoin de le faire, mais gardez à l'esprit que vous devez toujours implémenter CriteriaBuilder
qui l'utilisera et effectuera la pagination réelle (cette fonctionnalité est en cours de développement). Supposons que vous souhaitiez autoriser votre utilisateur final à modifier les pages, mais pas le nombre d'éléments par page. Vous pouvez utiliser cet exemple de code :
$criteria = new ImmutablePaginationAdapter (new PaginationCriteria($page = 1, $itemsPerPage = 50) );// $criteria->setItemsPerPage(250); <- l'utilisateur peut essayer de le changer// $criteria->getItemsPerPage() = 50 <- mais il ne peut pas le faire// $criteria->getPage() = 1
Bien sûr, si vous souhaitez permettre à l'utilisateur de modifier le nombre d'éléments par page, vous pouvez également ignorer ImmutablePaginationAdapter
et utiliser uniquement PaginationCriteria
.
Toutes les idées et demandes d'extraction sont les bienvenues et appréciées :) Si vous rencontrez un problème d'utilisation, n'hésitez pas à créer un problème, nous pouvons résoudre votre problème ensemble.
Commande pour exécuter le test : composer test
.
Tous les tests unitaires sont testés avec la bibliothèque padric/humbug pour les tests de mutation, dans le but de maintenir l'indicateur de score de mutation égal ou proche de 100 %.
Pour exécuter des tests de mutation, vous devez installer humbug et exécuter : humbug
dans le répertoire principal. La sortie doit être stockée dans humbuglog.txt
.
Par ordre alphabétique
https://github.com/chkris
https://github.com/pawelhertman
https://github.com/ustrugany
https://github.com/wojciech-olszewski
Licence : MIT
Auteur : Krzysztof Gzocha