Happyr/Doctrine-Specification

[RFC] Specification public API

arnolanglade opened this issue · 2 comments

For now, we have two kinds of specifications: query modifiers and filters, they implement their own interfaces.

Modifier returns void and updates the doctrine query builder by reference.
Filter returns the filter as a string and updates the doctrine query builder to set the filter value.

It is not consistent. Why should we rework the filter interface? We could rename getFilter to filter this new method could work like a modifier, it will return void and it will update the doctrine query builder.

Instead of :

  public function getFilter(QueryBuilder $qb, $dqlAlias)
  {
      $qb->setParameter('parameter_name', $this->value);

      return (string) new DoctrineComparison(
          sprintf('%s.%s', $dqlAlias, $this->field),
          $this->operator,
          sprintf(':%s', $paramName)
      );
  }

We could have:

  public function filter(QueryBuilder $qb, $dqlAlias)
  {
        $condition = (string) new DoctrineComparison(
              sprintf('%s.%s', $dqlAlias, $this->field),
              $this->operator,
              sprintf(':%s', $paramName)
         );

         $qb->andWhere(condition);
         $qb->setParameter('parameter_name', $this->value);
  }

Go one step further: Why two interfaces? If we change the getFilter method, we could replace these two interfaces by only one. A specification will only update the doctrine query builder.

interface Specification
{
    apply(QueryBuilder $qb, $dqlAlias);
}

No more need of BaseSpecification and we could replace QueryModifierCollection by a generic collection of specifications.

Other solution would be to do not work by reference but a specification could return a Doctrine\ORM\Query\Expr. Then we could apply them to a query builder.

You have interesting opinion, but it will not work.

If the specifications are applied immediately to the query, it won't be possible to build a composition of specifications. In this case, it won't be possible to use the specifications NOT, OR and OR + AND.

$spec = Spec::andX(
    Spec::orX(
        Spec::lt('endDate', new \DateTime()),
        Spec::andX(
            Spec::isNull('endDate'),
            Spec::lt('startDate', new \DateTime('-4weeks'))
        )
    ),
    Spec::not(Spec::eq('ended', true))
);

Ok, thank you! I close this issue.