Brevemente sobre as especificações:
Uma especificação é um padrão de design com o qual você pode refletir as regras da lógica de negócios na forma de uma cadeia de objetos conectados por operações da lógica booleana. As especificações permitem livrar-se de métodos semelhantes duplicados no repositório e na duplicação da lógica de negócios.
Hoje existem dois (se você conhece outros projetos, escreva nos comentários) projetos PHP bem-sucedidos e populares que permitem descrever regras de negócios em especificações e filtrar conjuntos de dados. Estes são RulerZ e Happyr Doctrine Specification . Ambos os projetos são ferramentas poderosas com suas vantagens e desvantagens. A comparação desses projetos desenhará um artigo inteiro. Aqui, quero lhe contar o que a nova versão da Especificação de Doutrina nos trouxe.
Brevemente sobre a Especificação de Doutrina
Aqueles que estão mais ou menos familiarizados com o projeto, podem pular esta seção com segurança.
Com a ajuda deste projeto, você pode descrever especificações na forma de objetos, compondo-as a partir da composição e, assim, criar regras comerciais complexas. As composições resultantes podem ser reutilizadas livremente e combinadas em composições ainda mais complexas e fáceis de testar. A Especificação de Doutrina é usada para criar consultas de Doutrina. Em essência, a Especificação de Doutrina é o nível de abstração sobre o Doctrine ORM QueryBuilder e o Doctrine ORM Query.
As especificações se aplicam através do Repositório de Doutrina:
$result = $em->getRepository(MyEntity::class)->match($spec);
A especificação pode ser aplicada manualmente, mas não é particularmente conveniente e, a longo prazo, não faz 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();
Existem vários métodos no repositório:
match
- obtendo todos os resultados correspondentes à especificação;matchSingleResult
- equivalente a Query::getSingleResult()
;matchOneOrNullResult
- equivalente a matchSingleResult
, mas permite null
;getQuery
- cria um QueryBuilder aplicando uma especificação a ele e retorna um objeto Query a partir dele.
Recentemente, o método getQueryBuilder
foi adicionado a eles, o que cria um QueryBuilder e, aplicando a especificação a ele, o retorna.
O projeto identifica vários tipos de especificações:
Especificações lógicas
As orX
e orX
também servem como um conjunto de especificações.
Spec::andX()
Spec::orX()
Spec::not()
É habitual instalar objetos de especificações de biblioteca através da fachada Spec
, mas isso não é necessário. Você pode instanciar explicitamente o objeto de especificação:
new AndX(); new OrX(): new Not();
Especificação do filtro
As especificações de filtragem, de fato, compõem as regras da lógica de negócios e são usadas na solicitação WHERE
. Isso inclui operações de comparação:
isNull
- SQL IS NULL
equivalenteisNotNull
- SQL IS NOT NULL
equivalentein
- equivalente a IN ()
notIn
- NOT IN ()
equivalenteeq
- teste de igualdade =
neq
- verifique se há desigualdade !=
lt
- menor que <
lte
- menor ou igual a <=
gt
- mais de >
gte
- maior ou igual a >=
like
- SQL LIKE
equivalenteinstanceOfX
- equivalente ao DQL INSTANCE OF
Um exemplo do uso de especificações de filtragem:
$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
Modificadores de consulta não têm nada a ver com lógica de negócios e regras de negócios. Como o nome indica, eles modificam apenas o QueryBuilder. O nome e a finalidade dos modificadores predefinidos correspondem a métodos semelhantes no QueryBuilder.
join
leftJoin
innerJoin
limit
offset
orderBy
groupBy
having
Quero anotar separadamente o modificador de slice
. Ele combina as funções limit
e offset
e calcula o deslocamento com base no tamanho da fatia e no número de série. Na implementação desse modificador, discordamos do autor do projeto. Criando um modificador, persegui o objetivo de simplificar a configuração das especificações durante a paginação. Nesse contexto, a primeira página com o número de série 1 deveria ter sido equivalente à primeira fatia com o número de série 1. Mas o autor do projeto considerou correto iniciar a contagem regressiva em um estilo de programação, ou seja, a partir de 0. Portanto, vale lembrar que, se você precisar da primeira fatia, precisará especificar 0 como um número de série.
Modificadores de resultado
Os modificadores de resultado existem um pouco além das especificações. Eles se aplicam à Consulta da Doutrina. Os seguintes modificadores controlam a hidratação dos dados ( Query::setHydrationMode()
):
asArray
asSingleScalar
asScalar
O modificador de cache
controla o armazenamento em cache do resultado da consulta.
Também devemos mencionar o modificador roundDateTimeParams
. Isso ajuda a resolver problemas de armazenamento em cache quando você precisa trabalhar com regras de negócios que exigem a comparação de alguns valores com o horário atual. Essas são regras comerciais normais, mas devido ao fato de o tempo não ser constante, o armazenamento em cache por mais de um segundo não funcionará para você. O modificador roundDateTimeParams
foi projetado para resolver esse problema. Ele percorre todos os parâmetros da solicitação, procura a data neles e a arredonda para o valor especificado, o que fornece valores de data sempre múltiplos de um valor e não obteremos a data no futuro. Ou seja, se queremos armazenar em cache a solicitação por 10 minutos, usamos Spec::cache(600)
e Spec::roundDateTimeParams(600)
. Inicialmente, foi proposto combinar esses dois modificadores por conveniência, mas foi decidido separá-los para o SRP.
Especificações incorporadas
O Happyr Doctrine-Specification possui uma interface separada para especificações que combina um filtro e um modificador de solicitação. A única especificação predefinida é countOf
que permite obter o número de entidades correspondentes à especificação. Para criar suas próprias especificações, é habitual estender a classe abstrata BaseSpecification
.
Inovações
Novos métodos foram adicionados ao repositório:
matchSingleScalarResult
- equivalente a Query::getSingleScalarResult()
;matchScalarResult
- equivalente a Query::getScalarResult()
;iterate
é o equivalente a Query::iterate()
.
A especificação MemberOfX
é MemberOfX
- o equivalente DQL de MEMBER OF
e o modificador de consulta indexBy
é indexBy
- o equivalente a QueryBuilder::indexBy()
.
Operandos
O novo lançamento introduz o conceito de Operando . Todas as condições nos filtros consistem em operandos esquerdo e direito e um operador entre eles.
<left_operand> <operator> <right_operand>
Nas versões anteriores, o operando esquerdo poderia ser apenas um campo de entidade e o operando direito poderia ser apenas um valor. Este é um mecanismo simples e eficaz que é suficiente para a maioria das tarefas. Ao mesmo tempo, impõe certas restrições:
- Incapaz de usar funções;
- Não é possível usar aliases para campos;
- É impossível comparar dois campos;
- É impossível comparar dois valores;
- Não é possível usar operações aritméticas;
- Não é possível especificar o tipo de dados para o valor.
Na nova versão, os objetos operandos são passados para os filtros nos argumentos e sua transformação no DQL é delegada aos próprios operandos. Isso abre muitas possibilidades e facilita os filtros.
Campo e valor
Para manter a compatibilidade com versões anteriores, o primeiro argumento nos filtros é convertido em um operando de campo, se não for um operando, e o último argumento também é convertido em um operando de valor. Portanto, você não deve ter problemas para atualizar.
Você pode comparar 2 campos:
Você pode comparar 2 campos de diferentes entidades:
Operações aritméticas
Adicionado suporte para operações aritméticas padrão -
, +
, *
, /
, %
. Por exemplo, considere o cálculo dos pontos do usuário:
Operações aritméticas podem ser aninhadas uma na outra:
Funções
A nova versão adicionou operandos com funções. Eles podem ser usados como métodos estáticos da classe Spec
ou através do método Spec::fun()
.
As funções podem ser aninhadas uma na outra:
Os argumentos para funções podem ser passados como argumentos separados ou passando-os em uma matriz:
Gerenciamento de amostragem
Às vezes, você precisa gerenciar uma lista de valores de retorno. Por exemplo:
- Adicione outra entidade ao resultado para não fazer subconsultas para obter os links;
- Para retornar não toda a entidade, mas apenas um conjunto de campos separados;
- Use aliases;
- Use aliases ocultos com condições de classificação (requer Doctrine, mas eles prometem corrigi-lo ).
Antes da versão 0.8.0, essas tarefas exigiam a criação de especificações para essas necessidades. A partir da versão 0.8.0, é possível usar o método getQueryBuilder()
e gerenciar a seleção através da interface QueryBuilder.
A nova versão 1.0.0 adiciona addSelect
solicitação select
e addSelect
. select
substitui completamente a lista de valores selecionáveis e addSelect
adiciona novos valores à lista. Você pode usar um objeto que implementa a interface Selection
ou um filtro como valor. Assim, você pode expandir os recursos da biblioteca para atender às suas necessidades. Considere as oportunidades que já estão lá.
Você pode selecionar um campo:
Você pode adicionar um campo à seleção:
Você pode selecionar vários campos:
Você pode adicionar uma entidade aos valores retornados:
Você pode usar aliases para campos selecionáveis:
Você pode adicionar campos ocultos à seleção:
Você pode usar expressões, por exemplo, para obter um desconto em um produto:
Você pode usar aliases nas especificações:
Isso é basicamente tudo. É aqui que as inovações terminam. O novo lançamento trouxe muitos recursos interessantes e úteis. Espero que eles tenham lhe interessado.
PS: Posso usar um exemplo para analisar o uso de especificações e mostrar as vantagens e desvantagens de seu uso. Se isso lhe interessar, escreva nos comentários ou no PM.