Skip to content

Commit fc2c898

Browse files
committed
optimize detection for incompatible filters
1 parent 15a2530 commit fc2c898

File tree

2 files changed

+69
-18
lines changed

2 files changed

+69
-18
lines changed

src/Model/Validator/FilterValidator.php

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ public function __construct(
6161
'isTransformingFilter' => $filter instanceof TransformingFilterInterface,
6262
// check if the given value has a type matched by the filter
6363
'typeCheck' => !empty($filter->getAcceptedTypes())
64-
? '($value !== null && (' .
64+
? '(' .
6565
implode(' && ', array_map(function (string $type) use ($property): string {
6666
return ReflectionTypeCheckValidator::fromType($type, $property)->getCheck();
6767
}, $this->mapDataTypes($filter->getAcceptedTypes()))) .
68-
'))'
68+
')'
6969
: '',
7070
'filterClass' => $filter->getFilter()[0],
7171
'filterMethod' => $filter->getFilter()[1],
@@ -136,10 +136,17 @@ public function addTransformedCheck(TransformingFilterInterface $filter, Propert
136136
*/
137137
private function validateFilterCompatibilityWithBaseType(FilterInterface $filter, PropertyInterface $property)
138138
{
139-
if (!empty($filter->getAcceptedTypes()) &&
140-
$property->getType() &&
141-
$property->getType()->getName() &&
142-
!in_array($property->getType()->getName(), $this->mapDataTypes($filter->getAcceptedTypes()))
139+
if (empty($filter->getAcceptedTypes()) || !$property->getType()) {
140+
return;
141+
}
142+
143+
if (
144+
(
145+
$property->getType()->getName() &&
146+
!in_array($property->getType()->getName(), $this->mapDataTypes($filter->getAcceptedTypes()))
147+
) || (
148+
$property->getType()->isNullable() && !in_array('null', $filter->getAcceptedTypes())
149+
)
143150
) {
144151
throw new SchemaException(
145152
sprintf(
@@ -174,13 +181,18 @@ private function validateFilterCompatibilityWithTransformedType(
174181
))->getReturnType();
175182

176183
if (!empty($filter->getAcceptedTypes()) &&
177-
!in_array($transformedType->getName(), $this->mapDataTypes($filter->getAcceptedTypes()))
184+
(
185+
!in_array($transformedType->getName(), $this->mapDataTypes($filter->getAcceptedTypes())) ||
186+
($transformedType->allowsNull() && !in_array('null', $filter->getAcceptedTypes()))
187+
)
178188
) {
179189
throw new SchemaException(
180190
sprintf(
181191
'Filter %s is not compatible with transformed property type %s for property %s in file %s',
182192
$filter->getToken(),
183-
$transformedType->getName(),
193+
$transformedType->allowsNull()
194+
? "[null, {$transformedType->getName()}]"
195+
: $transformedType->getName(),
184196
$property->getName(),
185197
$property->getJsonSchema()->getFile()
186198
)

tests/Basic/FilterTest.php

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public function testNonExistingFilterThrowsAnException(): void
8080
protected function getCustomFilter(
8181
array $customFilter,
8282
string $token = 'customFilter',
83-
array $acceptedTypes = ['string']
83+
array $acceptedTypes = ['string', 'null']
8484
): FilterInterface {
8585
return new class ($customFilter, $token, $acceptedTypes) implements FilterInterface {
8686
private $customFilter;
@@ -532,7 +532,7 @@ public function testMultipleTransformingFiltersAppliedToOnePropertyThrowsAnExcep
532532
new class () extends DateTimeFilter {
533533
public function getAcceptedTypes(): array
534534
{
535-
return [DateTime::class];
535+
return [DateTime::class, 'null'];
536536
}
537537

538538
public function getToken(): string
@@ -590,11 +590,12 @@ public static function exceptionFilter(string $value): void
590590
}
591591

592592
/**
593-
* @dataProvider namespaceDataProvider
593+
* @dataProvider implicitNullNamespaceDataProvider
594594
*
595+
* @param bool $implicitNull
595596
* @param string $namespace
596597
*/
597-
public function testTransformingToScalarType(string $namespace)
598+
public function testTransformingToScalarType(bool $implicitNull, string $namespace)
598599
{
599600
$className = $this->generateClassFromFile(
600601
'TransformingScalarFilter.json',
@@ -609,7 +610,9 @@ public function testTransformingToScalarType(string $namespace)
609610
'binary',
610611
['integer']
611612
)
612-
)
613+
),
614+
false,
615+
$implicitNull
613616
);
614617

615618
$fqcn = $namespace . $className;
@@ -621,6 +624,15 @@ public function testTransformingToScalarType(string $namespace)
621624

622625
$this->assertSame(['value' => 11], $object->toArray());
623626
$this->assertSame('{"value":11}', $object->toJSON());
627+
628+
$this->expectException(ErrorRegistryException::class);
629+
$this->expectExceptionMessage(
630+
$implicitNull
631+
? 'Filter binary is not compatible with property type NULL for property value'
632+
: 'Invalid type for value. Requires [string, int], got NULL'
633+
);
634+
635+
new $fqcn(['value' => null]);
624636
}
625637

626638
public static function filterIntToBinary(int $value): string
@@ -637,7 +649,8 @@ public function testInvalidFilterChainWithTransformingFilterThrowsAnException():
637649
{
638650
$this->expectException(SchemaException::class);
639651
$this->expectExceptionMessage(
640-
'Filter trim is not compatible with transformed property type DateTime for property filteredProperty'
652+
'Filter trim is not compatible with transformed property type ' .
653+
'[null, DateTime] for property filteredProperty'
641654
);
642655

643656
$this->generateClassFromFileTemplate('FilterChain.json', ['["dateTime", "trim"]'], null, false);
@@ -654,7 +667,7 @@ public function testFilterChainWithTransformingFilter(): void
654667
$this->getCustomFilter(
655668
[self::class, 'stripTimeFilter'],
656669
'stripTime',
657-
[DateTime::class]
670+
[DateTime::class, 'null']
658671
)
659672
),
660673
false
@@ -692,7 +705,7 @@ public function testFilterChainWithTransformingFilterOnMultiTypeProperty(
692705
$this->getCustomFilter(
693706
[self::class, 'stripTimeFilter'],
694707
'stripTime',
695-
[DateTime::class]
708+
[DateTime::class, 'null']
696709
)
697710
),
698711
false,
@@ -722,6 +735,27 @@ public function implicitNullNamespaceDataProvider(): array
722735
);
723736
}
724737

738+
public function testFilterChainWithIncompatibleFilterAfterTransformingFilterOnMultiTypeProperty(): void
739+
{
740+
$this->expectException(SchemaException::class);
741+
$this->expectExceptionMessage(
742+
'Filter stripTime is not compatible with transformed ' .
743+
'property type [null, DateTime] for property filteredProperty'
744+
);
745+
746+
$this->generateClassFromFile(
747+
'FilterChainMultiType.json',
748+
(new GeneratorConfiguration())
749+
->addFilter(
750+
$this->getCustomFilter(
751+
[self::class, 'stripTimeFilterStrict'],
752+
'stripTime',
753+
[DateTime::class]
754+
)
755+
)
756+
);
757+
}
758+
725759
public function testFilterAfterTransformingFilterIsSkippedIfTransformingFilterFails(): void
726760
{
727761
$this->expectException(ErrorRegistryException::class);
@@ -736,7 +770,7 @@ public function testFilterAfterTransformingFilterIsSkippedIfTransformingFilterFa
736770
$this->getCustomFilter(
737771
[self::class, 'exceptionFilter'],
738772
'stripTime',
739-
[DateTime::class]
773+
[DateTime::class, 'null']
740774
)
741775
),
742776
false
@@ -766,7 +800,7 @@ public function testFilterWhichAppliesToMultiTypePropertyPartiallyThrowsAnExcept
766800
$this->getCustomFilter(
767801
[self::class, 'stripTimeFilter'],
768802
'stripTime',
769-
[DateTime::class]
803+
[DateTime::class, 'null']
770804
)
771805
),
772806
false
@@ -778,6 +812,11 @@ public static function stripTimeFilter(?DateTime $value): ?DateTime
778812
return $value !== null ? $value->setTime(0, 0) : null;
779813
}
780814

815+
public static function stripTimeFilterStrict(DateTime $value): DateTime
816+
{
817+
return $value->setTime(0, 0);
818+
}
819+
781820
/**
782821
* @dataProvider arrayFilterDataProvider
783822
*

0 commit comments

Comments
 (0)