在您的学说查询中添加标准
#php #query #doctrine #criteria

在这篇文章中,我想向您展示如何轻松地在学说查询中添加标准。
首先,让我们创建一个持有数据的模型,我们要添加为查询中的标准:

class ListContractsInput
{
    #[Assert\DateTime(message: 'Start at must be a valid datetime')]
    private ?string $startAt = null;

    #[Assert\DateTime(message: 'End at must be a valid datetime')]
    private ?string $endAt = null;

    public function getStartAt(): ?string
    {
        return $this->startAt;
    }

    public function setStartAt(?string $startAt): void
    {
        $this->startAt = $startAt;
    }

    public function getEndAt(): ?string
    {
        return $this->endAt;
    }

    public function setEndAt(?string $endAt): void
    {
        $this->endAt = $endAt;
    }
}

正如我们在此模型中可以看到的那样,它具有 startat endat 值,并且要求它们是有效的数据。有关Symfony验证的更多信息here

现在看看如何处理此输入数据以将其添加为我们的查询中的标准。

创建一个处理输入数据的基类

abstract class QueryBuilderCriteriaManager
{
    private SerializerInterface $serializer;

    public function __construct()
    {
        $this->serializer = new Serializer([new ObjectNormalizer()]);
    }

    /**
     * @throws ExceptionInterface
     */
    public function addCriteria(QueryBuilder $qb, string $alias, iterable|object $filters): void
    {
        if(!is_object($filters)){
            foreach ($filters as $key => $value) {
                $this->addToQb($qb, $alias, $key, $value);
            }
        }
        else{
            $criteriaData = $this->serializer->normalize($filters);
            foreach ($criteriaData as $propName => $value) {
                $this->addToQb($qb, $alias, $propName, $value);
            }
        }
    }

    protected function addToQb(QueryBuilder $qb, string $alias, string $key, mixed $value): void
    {
        $method = u('get_' . $key . 'Criteria')->camel()->toString();

        if( !empty($value) || $value === 0 || $value === '0' || $value === false) {
            if(method_exists($this, $method)){
                $this->$method($qb, $alias, $value);
            }
            else{
                $qb
                    ->andWhere($qb->expr()->eq("{$alias}.{$key}", ':' . $key))
                    ->setParameter($key, $value)
                ;
            }
        }

    }

    /**
     * @throws \Exception
     */
    protected function getAsDateTime(string|\DateTimeImmutable $date): \DateTimeImmutable
    {
        return ($date instanceof \DateTimeImmutable)
            ? $date
            : new \DateTimeImmutable($date)
            ;
    }
}

让我们逐步分析此代码:

private SerializerInterface $serializer;

public function __construct()
{
    $this->serializer = new Serializer([new ObjectNormalizer()]);
}

首先,我们在构造函数中构建一个symfony serializer,因为我们将在下一行中需要它。

public function addCriteria(QueryBuilder $qb, string $alias, iterable|object $filters): void
{
    if(!is_object($filters)){
         foreach ($filters as $key => $value) {
            $this->addToQb($qb, $alias, $key, $value);
         }
    }
    else{
         $criteriaData = $this->serializer->normalize($filters);
         foreach ($criteriaData as $propName => $value) {
            $this->addToQb($qb, $alias, $propName, $value);
         }
    }
}

现在,我们创建方法 addcriteria 接收以下参数:

  • $ QB :我们将添加标准的QueryBuilder对象。
  • $ alias :查询主要实体别名
  • $过滤器:我们将要添加标准。它可以是一个对象(例如我们的模型),也可以是一个值得一提的

如果 $ filters 参数是一个迭代的,它可以通过估计/值循环,并使用方法 addtoqb 添加标准。

如果 $ filters 参数是一个对象,则首先将对象序列到数组,然后将其循环为键/值,还使用 addtoqb 添加标准。 br>

protected function addToQb(QueryBuilder $qb, string $alias, string $key, mixed $value): void
{
    $method = u('get_' . $key . 'Criteria')->camel()->toString();

    if( !empty($value) || $value === 0 || $value === '0' || $value === false) {
        if(method_exists($this, $method)){
            $this->$method($qb, $alias, $value);
        }
        else{
            $qb
               ->andWhere($qb->expr()->eq("{$alias}.{$key}", ':' . $key))
               ->setParameter($key, $value)
            ;
        }
    }

}

方法 addtoqb 接收以下参数:

  • $ qb :我们将添加标准的查询构建器
  • $ alias :查询主要实体别名
  • $ key :field(实体属性)我们将过滤
  • $ value :我们要过滤的值

在解释 addtoqb 之前,我想强调的是,此类 queryBuilderDercriteriaManager 是一个抽象类,它将通过其他类别来扩展,这些类将定义每个标准的逻辑我们要添加。

每个子类(我们将在稍后看到一个示例)将定义遵循此格式的每个条件的方法: get {keyname}标准

例如,根据我们在本文开始时确实显示的模型,我们应该定义方法 getStartatCriteria()和和 getendatCriteria()

知道这一点,让我们看看该方法的工作原理

  • 首先,它按照我们刚刚看到的格式构建方法的名称。
  • 第二,如果值不是null:
    • 如果方法存在于子类中,则执行接收 queryBuider alias value 的方法,最后添加了标准。 /li>
    • 如果方法不存在,则将标准添加为相等的条件。

创建孩子课

现在看看我们的孩子课的样子:

class ContractsCriteriaManager extends QueryBuilderCriteriaManager
{
    /**
     * @throws \Exception
     */
    public function getStartAtCriteria(QueryBuilder $qb, string $alias, string|\DateTimeImmutable $value): void
    {
        $qb
            ->andWhere($qb->expr()->gte("{$alias}.createdAt",':start_at'))
            ->setParameter('start_at', $this->getAsDateTime($value))
        ;
    }

    /**
     * @throws \Exception
     */
    public function getEndAtCriteria(QueryBuilder $qb, string $alias, string|\DateTimeImmutable $value): void
    {
        $qb
            ->andWhere($qb->expr()->lte("{$alias}.createdAt",':end_at'))
            ->setParameter('end_at', $this->getAsDateTime($value))
        ;
    }
}

正如我们所看到的,儿童班级定义了最后一个格式之后的两种方法:

  • getStartatCriteria():它添加了一个标准,以便 createat 必须大于或等于 startat value value
  • getEndatCriteria():它添加了一个标准,以便创建必须小于或等于 endat value value

在存储库中使用它

让我们看看以下存储库方法:

public function getList(array|object $criteria, ?int $limit): array
{
     $criteriaManager = new ContractsCriteriaManager();
     $qb = $this->createQueryBuilder(self::ALIAS);

     if($limit){
         $qb->setMaxResults($limit);
     }

     $qb->orderBy(self::ALIAS . '.createdAt', 'desc');
     $criteriaManager->addCriteria($qb, self::ALIAS, $criteria);
     return $qb->getQuery()->getResult();
}

您可以看到,在添加 limand order 的 中,我们使用Criteria Manager来填充我们的查询构建器使用 $标准中持有的标准数据对象。

如果我们执行此 getList()方法,并且会调试查询,我们会看到以下DQL:

SELECT c FROM App\Entity\Contract c WHERE c.startAt >= :start_at AND c.endAt <= :end_at ORDER BY c.createdAt desc

如果我们要转储查询参数,它们看起来像这样:

Doctrine\Common\Collections\ArrayCollection {#721
  -elements: array:2 [
    0 => Doctrine\ORM\Query\Parameter {#682
      -name: "start_at"
      -value: DateTimeImmutable @1683128700 {#671
        date: 2023-05-03 15:45:00.0 UTC (+00:00)
      }
      -type: "datetime_immutable"
      -typeSpecified: false
    }
    1 => Doctrine\ORM\Query\Parameter {#681
      -name: "end_at"
      -value: DateTimeImmutable @1683304255 {#670
        date: 2023-05-05 16:30:55.0 UTC (+00:00)
      }
      -type: "datetime_immutable"
      -typeSpecified: false
    }
  ]
}

仅此而已,我希望创建您的自定义标准行为并以更脱钩的方式创建复杂的查询是有用的。

您可以从我的github repository

下载此代码