Searcher é um construtor de consultas de pesquisa independente de estrutura. As consultas de pesquisa são escritas usando critérios e podem ser executadas em MySQL, MongoDB, ElasticSearch, arquivos ou qualquer outra coisa que você desejar. A versão mais recente suporta apenas PHP 7 . Agora testado também com Humbug
Você já viu um código responsável por pesquisar algo com base em vários critérios diferentes? Pode se tornar uma bagunça! Imagine que você tem um formulário com 20 campos e todos eles têm algum impacto nas condições de busca. Não é uma boa ideia passar um formulário inteiro para algum serviço, deixando-o analisar tudo em um só lugar. Graças a esta biblioteca você pode dividir a responsabilidade de construir critérios de consulta em várias classes menores. Uma classe por filtro. Um CriteriaBuilder
por Criteria
. Dessa forma, dentro CriteriaBuilder
você se preocupa apenas com um Criteria
, o que o torna muito mais legível e de fácil manutenção. Posteriormente, você pode usar exatamente os mesmos Criteria
para pesquisas diferentes, com CriteriaBuilder
diferentes e até mesmo SearchingContext
diferentes, que podem usar bancos de dados ainda diferentes. Você pode até usar o buscador para encontrar arquivos em seu sistema graças ao FinderSearchingContext
.
A documentação completa pode ser encontrada em http://searcher.rtfd.io/
Você pode instalar a biblioteca via compositor digitando no terminal:
$ compositor requer krzysztof-gzocha/searcher
A integração com Symfony é feita no SearcherBundle
CriteriaBuilder
- criará novas condições para Criteria
únicos,
Criteria
- modelo que será passado para CriteriaBuilder
. Você só precisa hidratá-lo de alguma forma, para que seja útil. Os critérios podem conter vários campos e todos (ou alguns) deles podem ser usados dentro CriteriaBuilder
,
SearchingContext
- contexto de pesquisa única. Este serviço deve saber como buscar resultados de uma consulta construída e contém algo chamado QueryBuilder
, mas pode ser qualquer coisa que funcione para você - qualquer serviço. Esta é uma camada de abstração entre a pesquisa e o banco de dados. Existem diferentes contextos para ORM, ODM, Elastica, Files do Doctrine e assim por diante. Se não houver contexto para você, você pode implementar um - não deve ser difícil,
Searcher
- contém a coleção do CriteriaBuilder
e passará Criteria
para CriteriaBuilder
apropriado.
Digamos que queremos pesquisar pessoas cuja idade esteja em algum intervalo filtrado. Neste exemplo usaremos o QueryBuilder do Doctrine, então usaremos QueryBuilderSearchingContext
e especificaremos no nosso CriteriaBuidler
que ele deve interagir apenas com DoctrineORMQueryBuilder
, mas lembre-se que não precisamos usar apenas o Doctrine.
Primeiro de tudo, precisaríamos criar AgeRangeCriteria
- a classe que conterá os valores de idade mínima e máxima. Já existem Criteria
padrão implementados aqui.
class AgeRangeCriteria implements CriteriaInterface{private $minimalAge;private $maximalAge;/** * Somente método obrigatório. * Se retornar verdadeiro, então será passado para algum(s) CriteriaBuilder(s) */public function shouldBeApplied(): bool{return null !== $this->minimalAge && null !== $this->maximalAge; }// getters, setters, o que for}
Na segunda etapa gostaríamos de especificar as condições que devem ser impostas para este modelo. É por isso que precisaríamos criar AgeRangeCriteriaBuilder
class AgeRangeCriteriaBuilder implementa CriteriaBuilderInterface{função pública buildCriteria(CriteriaInterface $criteria,SearchingContextInterface $searchingContext) {$searchingContext->getQueryBuilder() ->andWhere('e.age >= :minimalAge') ->andWhere('e.age <= :maximalAge') ->setParameter('minimalAge', $criteria->getMinimalAge()) ->setParameter('maximalAge', $criteria->getMaximalAge()); }função pública permiteCriteria(CriteriaInterface $criteria): bool{return $criteria instanceof AgeRangeCriteria; }/** * Você pode ignorar este método se for estender de AbstractORMCriteriaBuilder. */função pública suportaSearchingContext(SearchingContextInterface $searchingContext): bool{return $searchingContext instanceof QueryBuilderSearchingContext; } }
Nas próximas etapas precisaríamos criar coleções para ambos: Criteria
e CriteriaBuidler
.
$builders = new CriteriaBuilderCollection();$builders->addCriteriaBuilder(new AgeRangeCriteriaBuilder());$builders->addCriteriaBuilder(/** restante dos construtores */);
$ageRangeCriteria = new AgeRangeCriteria();// Temos que preencher o modelo antes de pesquisar$ageRangeCriteria->setMinimalAge(23);$ageRangeCriteria->setMaximalAge(29);$criteria = new CriteriaCollection();$criteria->addCriteria( $ageRangeCriteria);$criteria->addCriteria(/** restante dos critérios */);
Agora gostaríamos de criar nosso SearchingContext
e preenchê-lo com QueryBuilder retirado do Doctrine ORM.
$context = new QueryBuilderSearchingContext($queryBuilder);$searcher = new Searcher($builders, $context);$searcher->search($criteriaCollection); // Oba, temos nossos resultados!
Se houver uma pequena chance de que seu QueryBuilder retorne null
quando você espera um objeto ou array percorrível, você pode usar WrappedResultsSearcher
em vez da classe Searcher
normal. Ele agirá exatamente da mesma forma que Searcher
, mas retornará ResultCollection
, que funcionará apenas com array ou Traversable
e se o resultado for apenas null
seu código ainda funcionará. Aqui está como será:
$searcher = new WrappedResultsSearcher(new Searcher($builders, $context));$resultados = $searcher->search($criteriaCollection); // instância de ResultCollectionforeach ($results as $result) {// funcionará!}foreach ($results->getResults() as $result) {// Como ResultCollection tem o método getResults(), isso também funcionará!}
Para classificar seus resultados você pode usar Criteria
já implementados. Você não precisa implementá-lo do zero. Lembre-se de que você ainda precisa implementar seu CriteriaBuilder
para isso (esse recurso ainda está em desenvolvimento). Digamos que você queira ordenar seus resultados e precise do valor p.id
em seu CriteriaBuidler para fazer isso, mas gostaria de mostrá-lo como pid
para o usuário final. Nada mais simples! É assim que você pode criar OrderByCriteria:
$mappedFields = ['pid' => 'p.id', 'valueForUser' => 'valueForBuilder'];$criteria = new MappedOrderByAdapter(new OrderByCriteria('pid'),$mappedFields);// $criteria->getMappedOrderBy () = 'p.id'// $criteria->getOrderBy() = 'pid'
É claro que você não precisa usar MappedOrderByAdapter
- você pode usar apenas OrderByCriteria
, mas o usuário saberá exatamente quais campos estão sendo usados para classificar.
Criteria
para paginação também estão implementados e você não precisa fazer isso, mas lembre-se que você ainda precisa implementar CriteriaBuilder
que fará uso dele e fará a paginação real (este recurso está em desenvolvimento). Digamos que você queira permitir que seu usuário final altere as páginas, mas não o número de itens por página. Você pode usar este código de exemplo:
$criteria = new ImmutablePaginationAdapter( new PaginationCriteria($page = 1, $itemsPerPage = 50) );// $critério->setItemsPerPage(250); <- o usuário pode tentar alterá-lo // $criteria->getItemsPerPage() = 50 <- mas na verdade ele não pode fazer isso // $criteria->getPage() = 1
Claro, se você quiser permitir que o usuário altere o número de itens por página, você também pode pular ImmutablePaginationAdapter
e usar apenas PaginationCriteria
.
Todas as ideias e solicitações pull são bem-vindas e apreciadas :) Se você tiver qualquer problema de uso, não hesite em criar um problema, podemos resolver seu problema juntos.
Comando para executar o teste: composer test
.
Todos os testes unitários são testados com a biblioteca padric/humbug para testes de mutação, visando manter o Indicador de Pontuação de Mutação igual ou próximo de 100%.
Para executar testes de mutação você precisa instalar o humbug e executar: humbug
no diretório principal. A saída deve ser armazenada em humbuglog.txt
.
Em ordem alfabética
https://github.com/chkris
https://github.com/pawelhertman
https://github.com/ustrugany
https://github.com/wojciech-olszewski
Licença: MIT
Autor: Krzysztof Gzocha