Php 在单个查询中对多个字段进行筛选查询
我的设置是Symfony 5,最新的API平台版本在PHP7.3上运行。 所以我希望能够查询姓名和用户名,甚至是电子邮件。 我需要编写自定义解析器吗 这是我到目前为止尝试过的,但这会导致一个WHERE name=$name和username=$name 查询搜索用户$name:String!{ usersname:$name,username:$name{ 边缘{ 光标 节点{ 身份证件 用户名 电子邮件 阿凡达 } } } } 我的实体: /** *@ApiResource *@ApiFilterSearchFilter::类,属性={ *姓名:ipartial, *用户名:ipartial, *电子邮件:ipartial, * } * *@ORM\Tablename=用户 *@ORM\EntityrepositoryClass=Domain\Repository\UserRepository *@ORM\HasLifecycleCallbacks */ 类用户 { 私人$name; 私有$username; 私人电子邮件; //…代码省略。。。 } 默认情况下,API平台不会处理搜索筛选器中的或条件,您需要自定义筛选器来执行此操作 另请参见:。在API平台中,默认情况下不会处理搜索筛选器中的OR条件,您需要自定义筛选器来执行此操作 另见:.我为我的报告第6章作了这样的评论。我将其代码包括在下面 您可以配置它在ApiFilter标记中搜索的属性。在您的情况下,这将是:Php 在单个查询中对多个字段进行筛选查询,php,symfony,graphql,api-platform.com,Php,Symfony,Graphql,Api Platform.com,我的设置是Symfony 5,最新的API平台版本在PHP7.3上运行。 所以我希望能够查询姓名和用户名,甚至是电子邮件。 我需要编写自定义解析器吗 这是我到目前为止尝试过的,但这会导致一个WHERE name=$name和username=$name 查询搜索用户$name:String!{ usersname:$name,username:$name{ 边缘{ 光标 节点{ 身份证件 用户名 电子邮件 阿凡达 } } } } 我的实体: /** *@ApiResource *@ApiFilt
* @ApiFilter(SimpleSearchFilter::class, properties={"name", "username", "email"})
它将搜索字符串拆分为单词,并搜索每个单词的每个不区分大小写的属性,因此查询字符串如下:
?simplesearch=Katch sQuash
将在所有指定的属性中搜索。。如“%katch%”或更低。。例如“%squash%”
限制:它可能仅限于字符串属性,具体取决于数据库,并且不按相关性排序
守则:
// api/src/Filter/SimpleSearchFilter.php
namespace App\Filter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use ApiPlatform\Core\Exception\InvalidArgumentException;
/**
* Selects entities where each search term is found somewhere
* in at least one of the specified properties.
* Search terms must be separated by spaces.
* Search is case insensitive.
* All specified properties type must be string.
* @package App\Filter
*/
class SimpleSearchFilter extends AbstractContextAwareFilter
{
private $searchParameterName;
/**
* Add configuration parameter
* {@inheritdoc}
* @param string $searchParameterName The parameter whose value this filter searches for
*/
public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null, string $searchParameterName = 'simplesearch')
{
parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter);
$this->searchParameterName = $searchParameterName;
}
/** {@inheritdoc} */
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = [])
{
if (null === $value || $property !== $this->searchParameterName) {
return;
}
$words = explode(' ', $value);
foreach ($words as $word) {
if (empty($word)) continue;
$this->addWhere($queryBuilder, $word, $queryNameGenerator->generateParameterName($property));
}
}
private function addWhere($queryBuilder, $word, $parameterName)
{
$alias = $queryBuilder->getRootAliases()[0];
// Build OR expression
$orExp = $queryBuilder->expr()->orX();
foreach ($this->getProperties() as $prop => $ignoored) {
$orExp->add($queryBuilder->expr()->like('LOWER('. $alias. '.' . $prop. ')', ':' . $parameterName));
}
$queryBuilder
->andWhere('(' . $orExp . ')')
->setParameter($parameterName, '%' . strtolower($word). '%');
}
/** {@inheritdoc} */
public function getDescription(string $resourceClass): array
{
$props = $this->getProperties();
if (null===$props) {
throw new InvalidArgumentException('Properties must be specified');
}
return [
$this->searchParameterName => [
'property' => implode(', ', array_keys($props)),
'type' => 'string',
'required' => false,
'swagger' => [
'description' => 'Selects entities where each search term is found somewhere in at least one of the specified properties',
]
]
];
}
}
该服务需要在api/config/services.yaml中进行配置
'App\Filter\SimpleSearchFilter':
arguments:
$searchParameterName: 'ignoored'
$searchParameterName实际上可以通过@ApiFilter注释进行配置我为我的第6章做了这样一个注释。我将其代码包括在下面
您可以配置它在ApiFilter标记中搜索的属性。在您的情况下,这将是:
* @ApiFilter(SimpleSearchFilter::class, properties={"name", "username", "email"})
它将搜索字符串拆分为单词,并搜索每个单词的每个不区分大小写的属性,因此查询字符串如下:
?simplesearch=Katch sQuash
将在所有指定的属性中搜索。。如“%katch%”或更低。。例如“%squash%”
限制:它可能仅限于字符串属性,具体取决于数据库,并且不按相关性排序
守则:
// api/src/Filter/SimpleSearchFilter.php
namespace App\Filter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use ApiPlatform\Core\Exception\InvalidArgumentException;
/**
* Selects entities where each search term is found somewhere
* in at least one of the specified properties.
* Search terms must be separated by spaces.
* Search is case insensitive.
* All specified properties type must be string.
* @package App\Filter
*/
class SimpleSearchFilter extends AbstractContextAwareFilter
{
private $searchParameterName;
/**
* Add configuration parameter
* {@inheritdoc}
* @param string $searchParameterName The parameter whose value this filter searches for
*/
public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null, string $searchParameterName = 'simplesearch')
{
parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter);
$this->searchParameterName = $searchParameterName;
}
/** {@inheritdoc} */
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = [])
{
if (null === $value || $property !== $this->searchParameterName) {
return;
}
$words = explode(' ', $value);
foreach ($words as $word) {
if (empty($word)) continue;
$this->addWhere($queryBuilder, $word, $queryNameGenerator->generateParameterName($property));
}
}
private function addWhere($queryBuilder, $word, $parameterName)
{
$alias = $queryBuilder->getRootAliases()[0];
// Build OR expression
$orExp = $queryBuilder->expr()->orX();
foreach ($this->getProperties() as $prop => $ignoored) {
$orExp->add($queryBuilder->expr()->like('LOWER('. $alias. '.' . $prop. ')', ':' . $parameterName));
}
$queryBuilder
->andWhere('(' . $orExp . ')')
->setParameter($parameterName, '%' . strtolower($word). '%');
}
/** {@inheritdoc} */
public function getDescription(string $resourceClass): array
{
$props = $this->getProperties();
if (null===$props) {
throw new InvalidArgumentException('Properties must be specified');
}
return [
$this->searchParameterName => [
'property' => implode(', ', array_keys($props)),
'type' => 'string',
'required' => false,
'swagger' => [
'description' => 'Selects entities where each search term is found somewhere in at least one of the specified properties',
]
]
];
}
}
该服务需要在api/config/services.yaml中进行配置
'App\Filter\SimpleSearchFilter':
arguments:
$searchParameterName: 'ignoored'
$searchParameterName实际上可以从@ApiFilter注释配置也许您希望让客户端选择如何组合筛选条件和逻辑。这可以通过在和或中嵌套筛选条件来实现,如:
/users/?or[username]=super&or[name]=john
这将返回用户名为super或名称为john的所有用户。或者,如果同一属性需要更复杂的逻辑和多个条件:
/users/?and[name]=john&and[or][][email]=microsoft.com&and[or][][email]=apple.com
这将返回所有用户,其姓名中包含john,电子邮件地址中包含microsoft.com或apple.com。由于嵌套或描述的条件通过名称标准结合在一起,名称标准必须始终为真,而只有电子邮件的一个条件需要为真才能返回用户
要在应用程序中实现此功能,请在api src/Filter文件夹中创建一个文件FilterLogic.php
如果您还没有包含以下内容的文件夹,请创建此文件夹:
<?php
namespace App\Filter;
use ApiPlatform\Core\Api\FilterCollection;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\FilterInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Query\Expr;
use Doctrine\Persistence\ManagerRegistry;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* Combines existing API Platform ORM Filters with AND and OR.
* For usage and limitations see https://gist.github.com/metaclass-nl/790a5c8e9064f031db7d3379cc47c794
* Copyright (c) MetaClass, Groningen, 2021. MIT License
*/
class FilterLogic extends AbstractContextAwareFilter
{
/** @var ResourceMetadataFactoryInterface */
private $resourceMetadataFactory;
/** @var ContainerInterface|FilterCollection */
private $filterLocator;
/** @var string Filter classes must match this to be applied with logic */
private $classExp;
/**
* @param ResourceMetadataFactoryInterface $resourceMetadataFactory
* @param ContainerInterface|FilterCollection $filterLocator
* @param $regExp string Filter classes must match this to be applied with logic
* {@inheritdoc}
*/
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, $filterLocator, string $classExp='//', ManagerRegistry $managerRegistry, RequestStack $requestStack=null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null)
{
parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter);
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->filterLocator = $filterLocator;
$this->classExp = $classExp;
}
/** {@inheritdoc } */
public function getDescription(string $resourceClass): array
{
// No description
return [];
}
/**
* {@inheritdoc}
* @throws ResourceClassNotFoundException
* @throws \LogicException if assumption proves wrong
*/
protected function filterProperty(string $parameter, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = [])
{
$filters = $this->getFilters($resourceClass, $operationName);
if ($parameter == 'and') {
$newWhere = $this->applyLogic($filters, 'and', $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
$queryBuilder->andWhere($newWhere);
}
if ($parameter == 'or') {
$newWhere = $this->applyLogic($filters, 'or', $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
$queryBuilder->orWhere($newWhere);
}
}
/**
* Applies filters in compound logic context
* @param FilterInterface[] $filters to apply in context of $operator
* @param string $operator 'and' or 'or
* @return mixed Valid argument for Expr\Andx::add and Expr\Orx::add
* @throws \LogicException if assumption proves wrong
*/
private function applyLogic($filters, $operator, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context)
{
$oldWhere = $queryBuilder->getDQLPart('where');
// replace by marker expression
$marker = new Expr\Func('NOT', []);
$queryBuilder->add('where', $marker);
$subFilters = $context['filters'][$operator];
// print json_encode($subFilters, JSON_PRETTY_PRINT);
$assoc = [];
$logic = [];
foreach ($subFilters as $key => $value) {
if (ctype_digit((string) $key)) {
// allows the same filter to be applied several times, usually with different arguments
$subcontext = $context; //copies
$subcontext['filters'] = $value;
$this->applyFilters($filters, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $subcontext);
// apply logic seperately
if (isset($value['and'])) {
$logic[]['and'] = $value['and'];
}if (isset($value['or'])) {
$logic[]['or'] = $value['or'];
}
} elseif (in_array($key, ['and', 'or'])) {
$logic[][$key] = $value;
} else {
$assoc[$key] = $value;
}
}
// Process $assoc
$subcontext = $context; //copies
$subcontext['filters'] = $assoc;
$this->applyFilters($filters, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $subcontext);
$newWhere = $queryBuilder->getDQLPart('where');
$queryBuilder->add('where', $oldWhere); //restores old where
// force $operator logic upon $newWhere
if ($operator == 'and') {
$adaptedPart = $this->adaptWhere(Expr\Andx::class, $newWhere, $marker);
} else {
$adaptedPart = $this->adaptWhere(Expr\Orx::class, $newWhere, $marker);
}
// Process logic
foreach ($logic as $eachLogic) {
$subcontext = $context; //copies
$subcontext['filters'] = $eachLogic;
$newWhere = $this->applyLogic($filters, key($eachLogic), $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $subcontext);
$adaptedPart->add($newWhere); // empty expressions are ignored by ::add
}
return $adaptedPart; // may be empty
}
private function applyFilters($filters, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context)
{
foreach ($filters as $filter) {
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
}
}
/**
* ASSUMPTION: filters do not use QueryBuilder::where or QueryBuilder::add
* and create semantically complete expressions in the sense that expressions
* added to the QueryBundle through ::andWhere or ::orWhere do not depend
* on one another so that the intended logic is not compromised if they are
* recombined with the others by either Doctrine\ORM\Query\Expr\Andx
* or Doctrine\ORM\Query\Expr\Orx.
*
* Replace $where by an instance of $expClass.
* andWhere and orWhere allways add their args at the end of existing or
* new logical expressions, so we started with a marker expression
* to become the deepest first part. The marker should not be returned
* @param string $expClass
* @param Expr\Andx | Expr\Orx $where Result from applying filters
* @param Expr\Func $marker Marks the end of logic resulting from applying filters
* @return Expr\Andx | Expr\Orx Instance of $expClass
* @throws \LogicException if assumption proves wrong
*/
private function adaptWhere($expClass, $where, $marker)
{
if ($where === $marker) {
// Filters did nothing
return new $expClass([]);
}
if (!$where instanceof Expr\Andx && !$where instanceof Expr\Orx) {
// A filter used QueryBuilder::where or QueryBuilder::add or otherwise
throw new \LogicException("Assumpion failure, unexpected Expression: ". $where);
}
$parts = $where->getParts();
if (empty($parts)) {
// A filter used QueryBuilder::where or QueryBuilder::add or otherwise
throw new \LogicException("Assumpion failure, marker not found");
}
if ($parts[0] === $marker) {
// Marker found, recursion ends here
array_shift($parts);
} else {
$parts[0] = $this->adaptWhere($expClass, $parts[0], $marker);
}
return new $expClass($parts);
}
/**
* @param string $resourceClass
* @param string $operationName
* @return FilterInterface[] From resource except $this and OrderFilters
* @throws ResourceClassNotFoundException
*/
protected function getFilters($resourceClass, $operationName)
{
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
$resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
$result = [];
foreach ($resourceFilters as $filterId) {
$filter = $this->filterLocator->has($filterId)
? $this->filterLocator->get($filterId)
: null;
if ($filter instanceof FilterInterface
&& !($filter instanceof OrderFilter)
&& $filter !== $this
&& preg_match($this->classExp, get_class($filter))
) {
$result[$filterId] = $filter;
}
}
return $result;
}
}
最后,像这样调整实体:
use App\Filter\FilterLogic;
/**
* @ApiResource
* @ApiFilter(SearchFilter::class, properties={
* "name": "ipartial",
* "username": "ipartial",
* "email": "ipartial",
* })
* @ApiFilter(FilterLogic.class)
*
您也可以通过添加@ApiFilter注释将其应用到其他类中
具有FilterLogic类的注释是最后一个@ApiFilter注释,这一点很重要。正常的过滤仍将照常工作:过滤器决定如何将自己应用于QueryBuilder。如果所有人都使用::andWhere,就像Api平台的内置过滤器一样,ApiFilter属性/注释的顺序并不重要,但是如果一些人使用其他方法,不同的顺序可能会产生不同的结果。FilterLogic使用orWhere表示顺序的重要性。如果它是最后一个过滤器,它的逻辑表达式将成为最上面的表达式,因此定义了主逻辑
局限性
适用于Api平台的内置过滤器,但带有EXCLUDE_NULL的DateFilter除外。
子类可以修复它
假设过滤器创建语义完整的表达式,即
通过::andWhere或::orWhere添加到查询绑定的表达式不依赖于
以确保在重新组合时不会影响预期逻辑
通过条令\ORM\Query\Expr\Andx或条令\ORM\Query\Expr\Orx与其他人进行交互
如果筛选器使用QueryBuilder::where或::add,则可能会失败
建议您检查所有自定义和第三方过滤器的代码,以及
不要将使用QueryBuilder::where或::add的组件与FilterLogic组合
或者产生语义不完整的复杂逻辑。为了
语义完整和不完整表达式示例
我明白了
通过配置classExp,可以按类名输入/排除筛选器。例如:
*@ApiFilterFilterLogic::class,参数={classExp=/apipplatform\\Core\\Bridge\\Doctrine\\Orm\\Filter\\+/}
将仅在逻辑上下文中应用API平台ORM筛选器。也许您希望让客户端选择如何组合筛选器条件和逻辑。这可以通过在和或中嵌套筛选条件来实现,如:
/users/?or[username]=super&or[name]=john
这将返回用户名为super或名称为john的所有用户。或者,如果同一属性需要更复杂的逻辑和多个条件:
/users/?and[name]=john&and[or][][email]=microsoft.com&and[or][][email]=apple.com
这将返回所有用户,其姓名中包含john,电子邮件地址中包含microsoft.com或apple.com。由于嵌套或描述的条件通过名称标准结合在一起,名称标准必须始终为真,而只有电子邮件的一个条件需要为真才能返回用户
要在应用程序中实现此功能,请在api src/Filter文件夹中创建一个文件FilterLogic.php
如果您还没有包含以下内容的文件夹,请创建此文件夹:
<?php
namespace App\Filter;
use ApiPlatform\Core\Api\FilterCollection;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\FilterInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Query\Expr;
use Doctrine\Persistence\ManagerRegistry;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* Combines existing API Platform ORM Filters with AND and OR.
* For usage and limitations see https://gist.github.com/metaclass-nl/790a5c8e9064f031db7d3379cc47c794
* Copyright (c) MetaClass, Groningen, 2021. MIT License
*/
class FilterLogic extends AbstractContextAwareFilter
{
/** @var ResourceMetadataFactoryInterface */
private $resourceMetadataFactory;
/** @var ContainerInterface|FilterCollection */
private $filterLocator;
/** @var string Filter classes must match this to be applied with logic */
private $classExp;
/**
* @param ResourceMetadataFactoryInterface $resourceMetadataFactory
* @param ContainerInterface|FilterCollection $filterLocator
* @param $regExp string Filter classes must match this to be applied with logic
* {@inheritdoc}
*/
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, $filterLocator, string $classExp='//', ManagerRegistry $managerRegistry, RequestStack $requestStack=null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null)
{
parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter);
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->filterLocator = $filterLocator;
$this->classExp = $classExp;
}
/** {@inheritdoc } */
public function getDescription(string $resourceClass): array
{
// No description
return [];
}
/**
* {@inheritdoc}
* @throws ResourceClassNotFoundException
* @throws \LogicException if assumption proves wrong
*/
protected function filterProperty(string $parameter, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = [])
{
$filters = $this->getFilters($resourceClass, $operationName);
if ($parameter == 'and') {
$newWhere = $this->applyLogic($filters, 'and', $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
$queryBuilder->andWhere($newWhere);
}
if ($parameter == 'or') {
$newWhere = $this->applyLogic($filters, 'or', $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
$queryBuilder->orWhere($newWhere);
}
}
/**
* Applies filters in compound logic context
* @param FilterInterface[] $filters to apply in context of $operator
* @param string $operator 'and' or 'or
* @return mixed Valid argument for Expr\Andx::add and Expr\Orx::add
* @throws \LogicException if assumption proves wrong
*/
private function applyLogic($filters, $operator, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context)
{
$oldWhere = $queryBuilder->getDQLPart('where');
// replace by marker expression
$marker = new Expr\Func('NOT', []);
$queryBuilder->add('where', $marker);
$subFilters = $context['filters'][$operator];
// print json_encode($subFilters, JSON_PRETTY_PRINT);
$assoc = [];
$logic = [];
foreach ($subFilters as $key => $value) {
if (ctype_digit((string) $key)) {
// allows the same filter to be applied several times, usually with different arguments
$subcontext = $context; //copies
$subcontext['filters'] = $value;
$this->applyFilters($filters, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $subcontext);
// apply logic seperately
if (isset($value['and'])) {
$logic[]['and'] = $value['and'];
}if (isset($value['or'])) {
$logic[]['or'] = $value['or'];
}
} elseif (in_array($key, ['and', 'or'])) {
$logic[][$key] = $value;
} else {
$assoc[$key] = $value;
}
}
// Process $assoc
$subcontext = $context; //copies
$subcontext['filters'] = $assoc;
$this->applyFilters($filters, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $subcontext);
$newWhere = $queryBuilder->getDQLPart('where');
$queryBuilder->add('where', $oldWhere); //restores old where
// force $operator logic upon $newWhere
if ($operator == 'and') {
$adaptedPart = $this->adaptWhere(Expr\Andx::class, $newWhere, $marker);
} else {
$adaptedPart = $this->adaptWhere(Expr\Orx::class, $newWhere, $marker);
}
// Process logic
foreach ($logic as $eachLogic) {
$subcontext = $context; //copies
$subcontext['filters'] = $eachLogic;
$newWhere = $this->applyLogic($filters, key($eachLogic), $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $subcontext);
$adaptedPart->add($newWhere); // empty expressions are ignored by ::add
}
return $adaptedPart; // may be empty
}
private function applyFilters($filters, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context)
{
foreach ($filters as $filter) {
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
}
}
/**
* ASSUMPTION: filters do not use QueryBuilder::where or QueryBuilder::add
* and create semantically complete expressions in the sense that expressions
* added to the QueryBundle through ::andWhere or ::orWhere do not depend
* on one another so that the intended logic is not compromised if they are
* recombined with the others by either Doctrine\ORM\Query\Expr\Andx
* or Doctrine\ORM\Query\Expr\Orx.
*
* Replace $where by an instance of $expClass.
* andWhere and orWhere allways add their args at the end of existing or
* new logical expressions, so we started with a marker expression
* to become the deepest first part. The marker should not be returned
* @param string $expClass
* @param Expr\Andx | Expr\Orx $where Result from applying filters
* @param Expr\Func $marker Marks the end of logic resulting from applying filters
* @return Expr\Andx | Expr\Orx Instance of $expClass
* @throws \LogicException if assumption proves wrong
*/
private function adaptWhere($expClass, $where, $marker)
{
if ($where === $marker) {
// Filters did nothing
return new $expClass([]);
}
if (!$where instanceof Expr\Andx && !$where instanceof Expr\Orx) {
// A filter used QueryBuilder::where or QueryBuilder::add or otherwise
throw new \LogicException("Assumpion failure, unexpected Expression: ". $where);
}
$parts = $where->getParts();
if (empty($parts)) {
// A filter used QueryBuilder::where or QueryBuilder::add or otherwise
throw new \LogicException("Assumpion failure, marker not found");
}
if ($parts[0] === $marker) {
// Marker found, recursion ends here
array_shift($parts);
} else {
$parts[0] = $this->adaptWhere($expClass, $parts[0], $marker);
}
return new $expClass($parts);
}
/**
* @param string $resourceClass
* @param string $operationName
* @return FilterInterface[] From resource except $this and OrderFilters
* @throws ResourceClassNotFoundException
*/
protected function getFilters($resourceClass, $operationName)
{
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
$resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
$result = [];
foreach ($resourceFilters as $filterId) {
$filter = $this->filterLocator->has($filterId)
? $this->filterLocator->get($filterId)
: null;
if ($filter instanceof FilterInterface
&& !($filter instanceof OrderFilter)
&& $filter !== $this
&& preg_match($this->classExp, get_class($filter))
) {
$result[$filterId] = $filter;
}
}
return $result;
}
}
最后,像这样调整实体:
use App\Filter\FilterLogic;
/**
* @ApiResource
* @ApiFilter(SearchFilter::class, properties={
* "name": "ipartial",
* "username": "ipartial",
* "email": "ipartial",
* })
* @ApiFilter(FilterLogic.class)
*
您也可以通过添加@ApiFilter注释将其应用到其他类中
具有FilterLogic类的注释是最后一个@ApiFilter注释,这一点很重要。正常的过滤仍将照常工作:过滤器决定如何将自己应用于QueryBuilder。如果所有人都使用::andWhere,就像Api平台的内置过滤器一样,ApiFilter属性/注释的顺序并不重要,但是如果一些人使用其他方法,不同的顺序可能会产生不同的结果。FilterLogic使用orWhere表示顺序的重要性。如果它是最后一个过滤器,它的逻辑表达式将成为最上面的表达式,因此定义了主逻辑
局限性
适用于Api平台的内置过滤器,但带有EXCLUDE_NULL的DateFilter除外。
子类可以修复它
假设过滤器创建语义完整的表达式,即
通过::andWhere或::orWhere添加到查询绑定的表达式不依赖于
以确保在重新组合时不会影响预期逻辑
通过条令\ORM\Query\Expr\Andx或条令\ORM\Query\Expr\Orx与其他人进行交互
如果筛选器使用QueryBuilder::where或::add,则可能会失败
建议您检查所有自定义和第三方过滤器的代码,以及
不要将使用QueryBuilder::where或::add的组件与FilterLogic组合
或者产生语义不完整的复杂逻辑。为了
语义完整和不完整表达式的示例请参见
通过配置classExp,可以按类名输入/排除筛选器。例如:
*@ApiFilterFilterLogic::class,参数={classExp=/apipplatform\\Core\\Bridge\\Doctrine\\Orm\\Filter\\+/}
将只在逻辑上下文中应用API平台ORM过滤器。这正是我所要寻找的。谢谢这正是我想要的。谢谢这看起来很好@MetaClass!我将在单元测试中尝试并报告。我暂时共享了@Katch,其中包含我自己的单元itestssuper真棒!谢谢/贝丹克!:这看起来非常好@元类!我将在单元测试中尝试并报告。我暂时共享了@Katch,其中包含我自己的单元itestssuper真棒!谢谢/贝丹克!: