Skip to content

Commit 90f0d55

Browse files
refactor(state): replace :property placeholder with properties (#7547)
fixes #7478
1 parent ef31270 commit 90f0d55

33 files changed

+654
-267
lines changed

src/Doctrine/Common/Filter/PropertyAwareFilterInterface.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414
namespace ApiPlatform\Doctrine\Common\Filter;
1515

1616
/**
17+
* TODO: 5.x uncomment method.
18+
*
1719
* @author Antoine Bluchet <soyuka@gmail.com>
1820
*
21+
* @method ?array getProperties()
22+
*
1923
* @experimental
2024
*/
2125
interface PropertyAwareFilterInterface
@@ -24,4 +28,9 @@ interface PropertyAwareFilterInterface
2428
* @param string[] $properties
2529
*/
2630
public function setProperties(array $properties): void;
31+
32+
// /**
33+
// * @return string[]
34+
// */
35+
// public function getProperties(): ?array;
2736
}

src/Doctrine/Common/Filter/PropertyPlaceholderOpenApiParameterTrait.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,6 @@ trait PropertyPlaceholderOpenApiParameterTrait
2323
*/
2424
public function getOpenApiParameters(Parameter $parameter): ?array
2525
{
26-
if (str_contains($parameter->getKey(), ':property')) {
27-
$parameters = [];
28-
$key = str_replace('[:property]', '', $parameter->getKey());
29-
foreach (array_keys($parameter->getExtraProperties()['_properties'] ?? []) as $property) {
30-
$parameters[] = new OpenApiParameter(name: \sprintf('%s[%s]', $key, $property), in: 'query');
31-
}
32-
33-
return $parameters;
34-
}
35-
36-
return null;
26+
return [new OpenApiParameter(name: $parameter->getKey(), in: 'query')];
3727
}
3828
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Doctrine\Common;
15+
16+
use ApiPlatform\Doctrine\Common\Filter\LoggerAwareInterface;
17+
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
18+
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
19+
use ApiPlatform\Metadata\Parameter;
20+
use Doctrine\Persistence\ManagerRegistry;
21+
use Psr\Container\ContainerInterface;
22+
use Psr\Log\LoggerInterface;
23+
24+
trait ParameterExtensionTrait
25+
{
26+
use ParameterValueExtractorTrait;
27+
28+
protected ContainerInterface $filterLocator;
29+
protected ?ManagerRegistry $managerRegistry = null;
30+
protected ?LoggerInterface $logger = null;
31+
32+
/**
33+
* @param object $filter the filter instance to configure
34+
* @param Parameter $parameter the operation parameter associated with the filter
35+
*/
36+
private function configureFilter(object $filter, Parameter $parameter): void
37+
{
38+
if ($this->managerRegistry && $filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) {
39+
$filter->setManagerRegistry($this->managerRegistry);
40+
}
41+
42+
if ($this->logger && $filter instanceof LoggerAwareInterface && !$filter->hasLogger()) {
43+
$filter->setLogger($this->logger);
44+
}
45+
46+
if ($filter instanceof PropertyAwareFilterInterface) {
47+
$properties = [];
48+
// Check if the filter has getProperties method (e.g., if it's an AbstractFilter)
49+
if (method_exists($filter, 'getProperties')) { // @phpstan-ignore-line todo 5.x remove this check @see interface
50+
$properties = $filter->getProperties() ?? [];
51+
}
52+
53+
$propertyKey = $parameter->getProperty() ?? $parameter->getKey();
54+
foreach ($parameter->getProperties() ?? [$propertyKey] as $property) {
55+
if (!isset($properties[$property])) {
56+
$properties[$property] = $parameter->getFilterContext();
57+
}
58+
}
59+
60+
$filter->setProperties($properties);
61+
}
62+
}
63+
}

src/Doctrine/Common/ParameterValueExtractorTrait.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ trait ParameterValueExtractorTrait
2323
private function extractParameterValue(Parameter $parameter, mixed $value): array
2424
{
2525
$key = $parameter->getProperty() ?? $parameter->getKey();
26-
if (!str_contains($key, ':property')) {
27-
return [$key => $value];
28-
}
2926

30-
return [str_replace('[:property]', '', $key) => $value];
27+
return [$key => $value];
3128
}
3229
}

src/Doctrine/Odm/Extension/ParameterExtension.php

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@
1313

1414
namespace ApiPlatform\Doctrine\Odm\Extension;
1515

16-
use ApiPlatform\Doctrine\Common\Filter\LoggerAwareInterface;
17-
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
18-
use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
19-
use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter;
20-
use ApiPlatform\Doctrine\Odm\Filter\FilterInterface;
16+
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
17+
use ApiPlatform\Doctrine\Common\ParameterExtensionTrait;
18+
use ApiPlatform\Doctrine\Odm\Filter\FilterInterface; // Explicitly import PropertyAwareFilterInterface
2119
use ApiPlatform\Metadata\Operation;
2220
use ApiPlatform\State\ParameterNotFound;
2321
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
@@ -32,13 +30,16 @@
3230
*/
3331
final class ParameterExtension implements AggregationCollectionExtensionInterface, AggregationItemExtensionInterface
3432
{
35-
use ParameterValueExtractorTrait;
33+
use ParameterExtensionTrait;
3634

3735
public function __construct(
38-
private readonly ContainerInterface $filterLocator,
39-
private readonly ?ManagerRegistry $managerRegistry = null,
40-
private readonly ?LoggerInterface $logger = null,
36+
ContainerInterface $filterLocator,
37+
?ManagerRegistry $managerRegistry = null,
38+
?LoggerInterface $logger = null,
4139
) {
40+
$this->filterLocator = $filterLocator;
41+
$this->managerRegistry = $managerRegistry;
42+
$this->logger = $logger;
4243
}
4344

4445
/**
@@ -66,28 +67,7 @@ private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass
6667
continue;
6768
}
6869

69-
if ($this->managerRegistry && $filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) {
70-
$filter->setManagerRegistry($this->managerRegistry);
71-
}
72-
73-
if ($this->logger && $filter instanceof LoggerAwareInterface && !$filter->hasLogger()) {
74-
$filter->setLogger($this->logger);
75-
}
76-
77-
if ($filter instanceof AbstractFilter && !$filter->getProperties()) {
78-
$propertyKey = $parameter->getProperty() ?? $parameter->getKey();
79-
80-
if (str_contains($propertyKey, ':property')) {
81-
$extraProperties = $parameter->getExtraProperties()['_properties'] ?? [];
82-
foreach (array_keys($extraProperties) as $property) {
83-
$properties[$property] = $parameter->getFilterContext();
84-
}
85-
} else {
86-
$properties = [$propertyKey => $parameter->getFilterContext()];
87-
}
88-
89-
$filter->setProperties($properties ?? []);
90-
}
70+
$this->configureFilter($filter, $parameter);
9171

9272
$context['filters'] = $values;
9373
$context['parameter'] = $parameter;

src/Doctrine/Odm/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
],
2626
"require": {
2727
"php": ">=8.2",
28-
"api-platform/doctrine-common": "^4.2",
28+
"api-platform/doctrine-common": "^4.2.9",
2929
"api-platform/metadata": "^4.2",
3030
"api-platform/state": "^4.2.4",
3131
"doctrine/mongodb-odm": "^2.10",

src/Doctrine/Orm/Extension/ParameterExtension.php

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@
1313

1414
namespace ApiPlatform\Doctrine\Orm\Extension;
1515

16-
use ApiPlatform\Doctrine\Common\Filter\LoggerAwareInterface;
17-
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
18-
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
19-
use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
20-
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
16+
use ApiPlatform\Doctrine\Common\ParameterExtensionTrait;
2117
use ApiPlatform\Doctrine\Orm\Filter\FilterInterface;
2218
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
2319
use ApiPlatform\Metadata\Operation;
@@ -34,13 +30,16 @@
3430
*/
3531
final class ParameterExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
3632
{
37-
use ParameterValueExtractorTrait;
33+
use ParameterExtensionTrait;
3834

3935
public function __construct(
40-
private readonly ContainerInterface $filterLocator,
41-
private readonly ?ManagerRegistry $managerRegistry = null,
42-
private readonly ?LoggerInterface $logger = null,
36+
ContainerInterface $filterLocator,
37+
?ManagerRegistry $managerRegistry = null,
38+
?LoggerInterface $logger = null,
4339
) {
40+
$this->filterLocator = $filterLocator;
41+
$this->managerRegistry = $managerRegistry;
42+
$this->logger = $logger;
4443
}
4544

4645
/**
@@ -68,30 +67,7 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter
6867
continue;
6968
}
7069

71-
if ($this->managerRegistry && $filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) {
72-
$filter->setManagerRegistry($this->managerRegistry);
73-
}
74-
75-
if ($this->logger && $filter instanceof LoggerAwareInterface && !$filter->hasLogger()) {
76-
$filter->setLogger($this->logger);
77-
}
78-
79-
if ($filter instanceof PropertyAwareFilterInterface) {
80-
$properties = [];
81-
$propertyKey = $parameter->getProperty() ?? $parameter->getKey();
82-
if ($filter instanceof AbstractFilter) {
83-
$properties = $filter->getProperties() ?? [];
84-
85-
if (str_contains($propertyKey, ':property')) {
86-
$extraProperties = $parameter->getExtraProperties()['_properties'] ?? [];
87-
foreach (array_keys($extraProperties) as $property) {
88-
$properties[$property] = $parameter->getFilterContext();
89-
}
90-
}
91-
}
92-
93-
$filter->setProperties($properties + [$propertyKey => $parameter->getFilterContext()]);
94-
}
70+
$this->configureFilter($filter, $parameter);
9571

9672
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation,
9773
['filters' => $values, 'parameter' => $parameter] + $context

src/Doctrine/Orm/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
],
2525
"require": {
2626
"php": ">=8.2",
27-
"api-platform/doctrine-common": "^4.2.0-alpha.3@alpha",
27+
"api-platform/doctrine-common": "^4.2.9",
2828
"api-platform/metadata": "^4.2",
2929
"api-platform/state": "^4.2.4",
3030
"doctrine/orm": "^2.17 || ^3.0"

src/GraphQl/Type/FieldsBuilder.php

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
use ApiPlatform\Metadata\GraphQl\Query;
2323
use ApiPlatform\Metadata\GraphQl\Subscription;
2424
use ApiPlatform\Metadata\InflectorInterface;
25-
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
2625
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2726
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2827
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
@@ -356,6 +355,8 @@ private function parameterToObjectType(array $flattenFields, string $name): Inpu
356355
if (isset($fields[$key])) {
357356
if ($type instanceof ListOfType) {
358357
$key .= '_list';
358+
} elseif ($fields[$key]['type'] instanceof InputObjectType && !$type instanceof InputObjectType) {
359+
continue;
359360
}
360361
}
361362

@@ -497,56 +498,64 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
497498
*/
498499
private function getParameterArgs(Operation $operation, array $args = []): array
499500
{
501+
$groups = [];
502+
500503
foreach ($operation->getParameters() ?? [] as $parameter) {
501504
$key = $parameter->getKey();
502505

503-
if (!str_contains($key, ':property')) {
504-
$args[$key] = ['type' => GraphQLType::string()];
505-
506-
if ($parameter->getRequired()) {
507-
$args[$key]['type'] = GraphQLType::nonNull($args[$key]['type']);
506+
if (str_contains($key, '[')) {
507+
$key = str_replace('.', $this->nestingSeparator, $key);
508+
parse_str($key, $values);
509+
$rootKey = key($values);
510+
511+
$leafs = $values[$rootKey];
512+
$name = key($leafs);
513+
514+
$filterLeafs = [];
515+
if (($filterId = $parameter->getFilter()) && $this->filterLocator->has($filterId)) {
516+
$filter = $this->filterLocator->get($filterId);
517+
518+
if ($filter instanceof FilterInterface) {
519+
$property = $parameter->getProperty() ?? $name;
520+
$property = str_replace('.', $this->nestingSeparator, $property);
521+
$description = $filter->getDescription($operation->getClass());
522+
523+
foreach ($description as $descKey => $descValue) {
524+
$descKey = str_replace('.', $this->nestingSeparator, $descKey);
525+
parse_str($descKey, $descValues);
526+
if (isset($descValues[$property]) && \is_array($descValues[$property])) {
527+
$filterLeafs = array_merge($filterLeafs, $descValues[$property]);
528+
}
529+
}
530+
}
508531
}
509532

510-
continue;
511-
}
533+
if ($filterLeafs) {
534+
$leafs[$name] = $filterLeafs;
535+
}
512536

513-
if (!($filterId = $parameter->getFilter()) || !$this->filterLocator->has($filterId)) {
537+
$groups[$rootKey][] = [
538+
'name' => $name,
539+
'leafs' => $leafs[$name],
540+
'required' => $parameter->getRequired(),
541+
'description' => $parameter->getDescription(),
542+
'type' => 'string',
543+
];
514544
continue;
515545
}
516546

517-
$filter = $this->filterLocator->get($filterId);
518-
$parsedKey = explode('[:property]', $key);
519-
$flattenFields = [];
547+
$args[$key] = ['type' => GraphQLType::string()];
520548

521-
if ($filter instanceof FilterInterface) {
522-
foreach ($filter->getDescription($operation->getClass()) as $name => $value) {
523-
$values = [];
524-
parse_str($name, $values);
525-
if (isset($values[$parsedKey[0]])) {
526-
$values = $values[$parsedKey[0]];
527-
}
528-
529-
$name = key($values);
530-
$flattenFields[] = ['name' => $name, 'required' => $value['required'] ?? null, 'description' => $value['description'] ?? null, 'leafs' => $values[$name], 'type' => $value['type'] ?? 'string'];
531-
}
532-
533-
$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0]);
549+
if ($parameter->getRequired()) {
550+
$args[$key]['type'] = GraphQLType::nonNull($args[$key]['type']);
534551
}
552+
}
535553

536-
if ($filter instanceof OpenApiParameterFilterInterface) {
537-
foreach ($filter->getOpenApiParameters($parameter) as $value) {
538-
$values = [];
539-
parse_str($value->getName(), $values);
540-
if (isset($values[$parsedKey[0]])) {
541-
$values = $values[$parsedKey[0]];
542-
}
543-
544-
$name = key($values);
545-
$flattenFields[] = ['name' => $name, 'required' => $value->getRequired(), 'description' => $value->getDescription(), 'leafs' => $values[$name], 'type' => $value->getSchema()['type'] ?? 'string'];
546-
}
547-
548-
$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0].$operation->getShortName().$operation->getName());
549-
}
554+
foreach ($groups as $key => $flattenFields) {
555+
$name = $key.$operation->getShortName().$operation->getName();
556+
$inputObject = $this->parameterToObjectType($flattenFields, $name);
557+
$this->typesContainer->set($name, $inputObject);
558+
$args[$key] = $inputObject;
550559
}
551560

552561
return $args;

0 commit comments

Comments
 (0)