|
<?php |
|
|
|
declare(strict_types=1); |
|
|
|
namespace App\Doctrine\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 function count; |
|
use function explode; |
|
use function implode; |
|
use function in_array; |
|
|
|
final class DistinctFilter extends AbstractContextAwareFilter |
|
{ |
|
/** |
|
* @param array<mixed> $properties |
|
*/ |
|
public function __construct( |
|
ManagerRegistry $managerRegistry, |
|
?RequestStack $requestStack = null, |
|
public string $distinctParameterName = 'distinct', |
|
LoggerInterface $logger = null, |
|
array $properties = null |
|
) { |
|
parent::__construct($managerRegistry, $requestStack, $logger, $properties); |
|
} |
|
|
|
/** |
|
* @param array<mixed> $context |
|
*/ |
|
public function apply( |
|
QueryBuilder $queryBuilder, |
|
QueryNameGeneratorInterface $queryNameGenerator, |
|
string $resourceClass, |
|
string $operationName = null, |
|
array $context = [] |
|
): void { |
|
if (!isset($context['filters'][$this->distinctParameterName])) { |
|
return; |
|
} |
|
|
|
$value = $context['filters'][$this->distinctParameterName]; |
|
$allowed = []; |
|
if (isset($this->properties[$this->distinctParameterName])) { |
|
$allowed = (array) $this->properties[$this->distinctParameterName]; |
|
} |
|
if (!in_array($value, $allowed, true)) { |
|
return; |
|
} |
|
|
|
$this->filterProperty( |
|
$this->distinctParameterName, |
|
$value, |
|
$queryBuilder, |
|
$queryNameGenerator, |
|
$resourceClass, |
|
$operationName, |
|
$context |
|
); |
|
} |
|
|
|
/** |
|
* @param array<mixed> $context |
|
* @param string|null $value |
|
*/ |
|
protected function filterProperty( |
|
string $property, |
|
$value, |
|
QueryBuilder $queryBuilder, |
|
QueryNameGeneratorInterface $queryNameGenerator, |
|
string $resourceClass, |
|
string $operationName = null, |
|
array $context = [] |
|
): void { |
|
if ($value === null) { |
|
return; |
|
} |
|
|
|
$lastAlias = $queryBuilder->getRootAliases()[0]; |
|
$joins = explode('.', $value); |
|
$count = count($joins); |
|
$groupBy = null; |
|
|
|
foreach ($joins as $index => $joinPart) { |
|
$currentAlias = $joinPart; |
|
if ($index === $count - 1) { |
|
$groupBy = "{$lastAlias}.{$currentAlias}"; |
|
} else { |
|
$join = "{$lastAlias}.{$currentAlias}"; |
|
if (!in_array($currentAlias, $queryBuilder->getAllAliases(), true)) { |
|
$queryBuilder->leftJoin($join, $currentAlias); |
|
} |
|
} |
|
$lastAlias = $currentAlias; |
|
} |
|
|
|
if ($groupBy) { |
|
$queryBuilder->select($groupBy)->groupBy($groupBy)->setMaxResults(10); |
|
} |
|
} |
|
|
|
/** |
|
* @return array<mixed> |
|
*/ |
|
public function getDescription(string $resourceClass): array |
|
{ |
|
$fields = $this->properties[$this->distinctParameterName] ?? []; |
|
return [ |
|
$this->distinctParameterName => [ |
|
'property' => $this->distinctParameterName, |
|
'type' => 'string', |
|
'required' => false, |
|
'swagger' => [ |
|
'description' => 'Distinct filter on possible fields: ' . implode(', ', (array) $fields) |
|
], |
|
] |
|
]; |
|
} |
|
} |