Skip to content

Commit f038684

Browse files
NathanPesneausoyuka
authored andcommitted
refactor(state): replace :property placeholder with properties
fixes #7478
1 parent fa6a5e4 commit f038684

23 files changed

+467
-166
lines changed

src/Doctrine/Common/Filter/PropertyAwareFilterInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
/**
1717
* @author Antoine Bluchet <[email protected]>
1818
*
19+
* @method ?array getProperties()
20+
*
1921
* @experimental
2022
*/
2123
interface PropertyAwareFilterInterface
@@ -24,4 +26,9 @@ interface PropertyAwareFilterInterface
2426
* @param string[] $properties
2527
*/
2628
public function setProperties(array $properties): void;
29+
30+
// /**
31+
// * @return string[]
32+
// */
33+
// public function getProperties(): ?array;
2734
}

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
}

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: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,22 @@ private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass
7474
$filter->setLogger($this->logger);
7575
}
7676

77-
if ($filter instanceof AbstractFilter && !$filter->getProperties()) {
77+
// Repeated inside ORM Move this to common ?
78+
if ($filter instanceof PropertyAwareFilterInterface) {
79+
$properties = [];
7880
$propertyKey = $parameter->getProperty() ?? $parameter->getKey();
7981

80-
if (str_contains($propertyKey, ':property')) {
81-
$extraProperties = $parameter->getExtraProperties()['_properties'] ?? [];
82-
foreach (array_keys($extraProperties) as $property) {
82+
if ($filter instanceof AbstractFilter) {
83+
$properties = $filter->getProperties() ?? [];
84+
}
85+
86+
foreach ($parameter->getProperties() ?? [$propertyKey] as $property) {
87+
if (!isset($properties[$property])) {
8388
$properties[$property] = $parameter->getFilterContext();
8489
}
85-
} else {
86-
$properties = [$propertyKey => $parameter->getFilterContext()];
8790
}
8891

89-
$filter->setProperties($properties ?? []);
92+
$filter->setProperties($properties);
9093
}
9194

9295
$context['filters'] = $values;

src/Doctrine/Orm/Extension/ParameterExtension.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,21 +76,22 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter
7676
$filter->setLogger($this->logger);
7777
}
7878

79+
// Repeated inside ODM Move this to common ?
7980
if ($filter instanceof PropertyAwareFilterInterface) {
8081
$properties = [];
8182
$propertyKey = $parameter->getProperty() ?? $parameter->getKey();
83+
8284
if ($filter instanceof AbstractFilter) {
8385
$properties = $filter->getProperties() ?? [];
86+
}
8487

85-
if (str_contains($propertyKey, ':property')) {
86-
$extraProperties = $parameter->getExtraProperties()['_properties'] ?? [];
87-
foreach (array_keys($extraProperties) as $property) {
88-
$properties[$property] = $parameter->getFilterContext();
89-
}
88+
foreach ($parameter->getProperties() ?? [$propertyKey] as $property) {
89+
if (!isset($properties[$property])) {
90+
$properties[$property] = $parameter->getFilterContext();
9091
}
9192
}
9293

93-
$filter->setProperties($properties + [$propertyKey => $parameter->getFilterContext()]);
94+
$filter->setProperties($properties);
9495
}
9596

9697
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation,

src/GraphQl/Type/FieldsBuilder.php

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -499,57 +499,94 @@ private function getParameterArgs(Operation $operation, array $args = []): array
499499
{
500500
foreach ($operation->getParameters() ?? [] as $parameter) {
501501
$key = $parameter->getKey();
502+
$property = $parameter->getProperty();
502503

503-
if (!str_contains($key, ':property')) {
504-
$args[$key] = ['type' => GraphQLType::string()];
504+
$matchFound = false;
505505

506-
if ($parameter->getRequired()) {
507-
$args[$key]['type'] = GraphQLType::nonNull($args[$key]['type']);
508-
}
506+
if ($filter = $this->getFilterInstance($parameter->getFilter())) {
507+
if ($filter instanceof FilterInterface) {
508+
foreach ($filter->getDescription($operation->getClass()) as $name => $value) {
509+
// Check if this description entry matches the current parameter's property
510+
if ($property && ($value['property'] ?? null) === $property) {
511+
$matchFound = true;
509512

510-
continue;
511-
}
513+
$suffix = '';
514+
if (str_starts_with($name, $property)) {
515+
$suffix = substr($name, \strlen($property));
516+
}
512517

513-
if (!($filterId = $parameter->getFilter()) || !$this->filterLocator->has($filterId)) {
514-
continue;
515-
}
518+
$argName = $key.$suffix;
516519

517-
$filter = $this->filterLocator->get($filterId);
518-
$parsedKey = explode('[:property]', $key);
519-
$flattenFields = [];
520+
$type = \in_array($value['type'] ?? 'string', TypeIdentifier::values(), true) ? Type::builtin($value['type'] ?? 'string') : Type::object($value['type'] ?? 'string');
520521

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-
}
522+
if (!($value['required'] ?? false)) {
523+
$type = Type::nullable($type);
524+
}
525+
526+
$graphQlType = $this->getParameterType($type);
527+
528+
parse_str($argName, $parsed);
529+
array_walk_recursive($parsed, static function (&$v) use ($graphQlType): void {
530+
$v = $graphQlType;
531+
});
528532

529-
$name = key($values);
530-
$flattenFields[] = ['name' => $name, 'required' => $value['required'] ?? null, 'description' => $value['description'] ?? null, 'leafs' => $values[$name], 'type' => $value['type'] ?? 'string'];
533+
$args = $this->mergeFilterArgs($args, $parsed, $operation, $key);
534+
}
535+
}
531536
}
532537

533-
$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0]);
534-
}
538+
if ($filter instanceof OpenApiParameterFilterInterface) {
539+
foreach ($filter->getOpenApiParameters($parameter) as $value) {
540+
$matchFound = true;
541+
$suffix = '';
542+
if ($property && str_starts_with($value->getName(), $property)) {
543+
$suffix = substr($value->getName(), \strlen($property));
544+
}
535545

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]];
546+
$argName = $key.$suffix;
547+
$type = \in_array($value->getSchema()['type'] ?? 'string', TypeIdentifier::values(), true) ? Type::builtin($value->getSchema()['type'] ?? 'string') : Type::object($value->getSchema()['type'] ?? 'string');
548+
if (!$value->getRequired()) {
549+
$type = Type::nullable($type);
550+
}
551+
$graphQlType = $this->getParameterType($type);
552+
parse_str($argName, $parsed);
553+
array_walk_recursive($parsed, static function (&$v) use ($graphQlType): void {
554+
$v = $graphQlType;
555+
});
556+
$args = $this->mergeFilterArgs($args, $parsed, $operation, $key);
542557
}
558+
}
559+
}
543560

544-
$name = key($values);
545-
$flattenFields[] = ['name' => $name, 'required' => $value->getRequired(), 'description' => $value->getDescription(), 'leafs' => $values[$name], 'type' => $value->getSchema()['type'] ?? 'string'];
561+
if (!$matchFound) {
562+
$type = GraphQLType::string();
563+
if ($parameter->getNativeType()) {
564+
$type = $this->getParameterType($parameter->getNativeType());
546565
}
547566

548-
$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0].$operation->getShortName().$operation->getName());
567+
$arg = ['type' => $type];
568+
569+
if ($parameter->getRequired()) {
570+
$arg['type'] = GraphQLType::nonNull($arg['type']);
571+
}
572+
573+
if ($parameter->getDescription()) {
574+
$arg['description'] = $parameter->getDescription();
575+
}
576+
577+
if (str_contains($key, '[')) {
578+
parse_str($key, $parsed);
579+
array_walk_recursive($parsed, static function (&$v) use ($arg): void {
580+
$v = $arg['type'];
581+
});
582+
$args = $this->mergeFilterArgs($args, $parsed, $operation, $key);
583+
} else {
584+
$args[$key] = $arg;
585+
}
549586
}
550587
}
551588

552-
return $args;
589+
return $this->convertFilterArgsToTypes($args);
553590
}
554591

555592
private function getGraphQlPaginationArgs(Operation $queryOperation): array
@@ -742,4 +779,21 @@ private function normalizePropertyName(string $property, string $resourceClass):
742779

743780
return $this->nameConverter->normalize($property, $resourceClass);
744781
}
782+
783+
private function getFilterInstance(object|string|null $filter): ?FilterInterface
784+
{
785+
if (!$filter) {
786+
return null;
787+
}
788+
789+
if (\is_object($filter)) {
790+
return $filter;
791+
}
792+
793+
if (!$this->filterLocator->has($filter)) {
794+
return null;
795+
}
796+
797+
return $this->filterLocator->get($filter);
798+
}
745799
}

src/Hydra/State/Util/SearchHelperTrait.php

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,17 @@ private function getSearchMappingAndKeys(?Operation $operation = null, ?string $
5555
}
5656
}
5757

58-
$params = $operation ? ($operation->getParameters() ?? []) : ($parameters ?? []);
59-
60-
foreach ($params as $key => $parameter) {
58+
foreach ($parameters ?? $operation?->getParameters() ?? [] as $key => $parameter) {
6159
if (!$parameter instanceof QueryParameterInterface || false === $parameter->getHydra()) {
6260
continue;
6361
}
6462

63+
// get possible mapping via the parameter's Filter
6564
if ($getFilter && ($filterId = $parameter->getFilter()) && \is_string($filterId) && ($filter = $getFilter($filterId))) {
6665
$filterDescription = $filter->getDescription($resourceClass);
6766

6867
foreach ($filterDescription as $variable => $description) {
69-
// // This is a practice induced by PHP and is not necessary when implementing URI template
68+
// Skip array definitions often returned by PHP-centric filters
7069
if (str_ends_with((string) $variable, '[]')) {
7170
continue;
7271
}
@@ -75,40 +74,30 @@ private function getSearchMappingAndKeys(?Operation $operation = null, ?string $
7574
continue;
7675
}
7776

77+
// Ensure the filter description matches the property defined in the QueryParameter
7878
if (($prop = $parameter->getProperty()) && $descriptionProperty !== $prop) {
7979
continue;
8080
}
8181

82-
$k = str_replace(':property', $description['property'], $key);
83-
$variable = str_replace($description['property'], $k, $variable);
84-
$keys[] = $variable;
85-
$m = new IriTemplateMapping(variable: $variable, property: $description['property'], required: $description['required']);
86-
if (null !== ($required = $parameter->getRequired())) {
87-
$m->required = $required;
82+
$variableName = $variable;
83+
84+
if ($prop && str_starts_with($variable, $prop)) {
85+
$variableName = substr_replace($variable, $key, 0, \strlen($prop));
8886
}
89-
$mapping[] = $m;
87+
88+
$keys[] = $variableName;
89+
$mapping[] = new IriTemplateMapping(
90+
variable: $variableName,
91+
property: $descriptionProperty,
92+
required: $parameter->getRequired() ?? $description['required'] ?? false
93+
);
9094
}
9195

9296
if ($filterDescription) {
9397
continue;
9498
}
9599
}
96100

97-
if (str_contains($key, ':property') && $parameter->getProperties()) {
98-
$required = $parameter->getRequired();
99-
foreach ($parameter->getProperties() as $prop) {
100-
$k = str_replace(':property', $prop, $key);
101-
$m = new IriTemplateMapping(variable: $k, property: $prop);
102-
$keys[] = $k;
103-
if (null !== $required) {
104-
$m->required = $required;
105-
}
106-
$mapping[] = $m;
107-
}
108-
109-
continue;
110-
}
111-
112101
if (!($property = $parameter->getProperty())) {
113102
continue;
114103
}

src/JsonApi/Filter/SparseFieldsetParameterProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function provide(Parameter $parameter, array $parameters = [], array $con
2626
return null;
2727
}
2828

29-
$allowedProperties = $parameter->getExtraProperties()['_properties'] ?? [];
29+
$allowedProperties = $parameter->getProperties() ?? [];
3030
$value = $parameter->getValue();
3131
$normalizationContext = $operation->getNormalizationContext();
3232

src/Laravel/Eloquent/Filter/OrderFilter.php

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ final class OrderFilter implements FilterInterface, JsonSchemaFilterInterface, O
3131
public function apply(Builder $builder, mixed $values, Parameter $parameter, array $context = []): Builder
3232
{
3333
if (!\is_string($values)) {
34-
$properties = $parameter->getExtraProperties()['_properties'] ?? [];
34+
$properties = $parameter->getProperties() ?? [];
3535

3636
foreach ($values as $key => $value) {
3737
if (!isset($properties[$key])) {
@@ -59,16 +59,6 @@ public function getSchema(Parameter $parameter): array
5959
*/
6060
public function getOpenApiParameters(Parameter $parameter): ?array
6161
{
62-
if (str_contains($parameter->getKey(), ':property')) {
63-
$parameters = [];
64-
$key = str_replace('[:property]', '', $parameter->getKey());
65-
foreach (array_keys($parameter->getExtraProperties()['_properties'] ?? []) as $property) {
66-
$parameters[] = new OpenApiParameter(name: \sprintf('%s[%s]', $key, $property), in: 'query');
67-
}
68-
69-
return $parameters;
70-
}
71-
72-
return null;
62+
return [new new OpenApiParameter(name: $parameter->getKey(), in: 'query')]
7363
}
7464
}

0 commit comments

Comments
 (0)