Skip to content

Commit a4c89a5

Browse files
authored
Merge pull request #9 from wol-soft/SkipFilterAfterTransformingFilterIfTransformationFails
Skip filters after a failing transforming filter
2 parents 5f6ed75 + a6134e7 commit a4c89a5

File tree

4 files changed

+50
-3
lines changed

4 files changed

+50
-3
lines changed

docs/source/nonStandardExtensions/filter.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ Filters may change the type of the property. For example the builtin filter **da
8484

8585
As the required check is executed before the filter a filter may transform a required value into a null value. Be aware when writing custom filters which transform values to not break your validation rules by adding filters to a property.
8686

87-
Only one transforming filter per property is allowed. may be positioned anywhere in the filter chain of a single property. If multiple filters are applied and a transforming filter is among them you have to make sure the property types are compatible. If you use a custom filter after the dateTime filter for example the custom filter has to accept a DateTime value. Filters used before a transforming filter must accept the base type of the property the filter is applied to defined in the schema.
87+
Only one transforming filter per property is allowed. may be positioned anywhere in the filter chain of a single property. If multiple filters are applied and a transforming filter is among them you have to make sure the property types are compatible. If you use a custom filter after the dateTime filter for example the custom filter has to accept a DateTime value. Filters used before a transforming filter must accept the base type of the property the filter is applied to defined in the schema. If the transformation of a property fails (the transforming filter throws an exception), subsequent filters won't be executed as their execution would add another error due to incompatible types which is irrelevant for the currently provided value.
8888

8989
If you write a custom transforming filter you must define the return type of your filter function as the implementation uses Reflection methods to determine to which type a value is transformed by a filter.
9090

src/Model/Validator/FilterValidator.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
*/
2222
class FilterValidator extends PropertyTemplateValidator
2323
{
24+
/** @var FilterInterface $filter */
25+
protected $filter;
26+
2427
/**
2528
* FilterValidator constructor.
2629
*
@@ -31,6 +34,7 @@ class FilterValidator extends PropertyTemplateValidator
3134
* @param TransformingFilterInterface|null $transformingFilter
3235
*
3336
* @throws SchemaException
37+
* @throws ReflectionException
3438
*/
3539
public function __construct(
3640
GeneratorConfiguration $generatorConfiguration,
@@ -39,6 +43,8 @@ public function __construct(
3943
array $filterOptions = [],
4044
?TransformingFilterInterface $transformingFilter = null
4145
) {
46+
$this->filter = $filter;
47+
4248
$transformingFilter === null
4349
? $this->validateFilterCompatibilityWithBaseType($filter, $property)
4450
: $this->validateFilterCompatibilityWithTransformedType($filter, $transformingFilter, $property);
@@ -51,7 +57,8 @@ public function __construct(
5157
),
5258
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'Filter.phptpl',
5359
[
54-
'skipTransformedValuesCheck' => false,
60+
'skipTransformedValuesCheck' => $transformingFilter !== null ? '!$transformationFailed' : '',
61+
'isTransformingFilter' => $filter instanceof TransformingFilterInterface,
5562
// check if the given value has a type matched by the filter
5663
'typeCheck' => !empty($filter->getAcceptedTypes())
5764
? '($value !== null && (' .
@@ -73,6 +80,19 @@ public function __construct(
7380
);
7481
}
7582

83+
/**
84+
* Track if a transformation failed. If a transformation fails don't execute subsequent filter as they'd fail with
85+
* an invalid type
86+
*
87+
* @return string
88+
*/
89+
public function getValidatorSetUp(): string
90+
{
91+
return $this->filter instanceof TransformingFilterInterface
92+
? '$transformationFailed = false;'
93+
: '';
94+
}
95+
7696
/**
7797
* Make sure the filter is only executed if a non-transformed value is provided.
7898
* This is required as a setter (eg. for a string property which is modified by the DateTime filter into a DateTime

src/Templates/Validator/Filter.phptpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
{% if skipTransformedValuesCheck %}{{ skipTransformedValuesCheck }} && {% endif %}
22
(
33
{% if typeCheck %}{{ typeCheck }} || {% endif %}
4-
(function (&$value) {
4+
(function (&$value) use (&$transformationFailed): bool {
55
// make sure exceptions from the filter are caught and added to the error handling
66
try {
77
$value = call_user_func_array([\{{ filterClass }}::class, "{{ filterMethod }}"], [$value, {{ filterOptions }}]);
88
} catch (\Exception $e) {
9+
{% if isTransformingFilter %}
10+
$transformationFailed = true;
11+
{% endif %}
12+
913
{{ viewHelper.validationError(transferExceptionMessage) }}
1014
}
1115

tests/Basic/FilterTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,29 @@ public function testFilterChainWithTransformingFilterOnMultiTypeProperty(bool $i
679679
$this->assertSame(['filteredProperty' => '2020-12-12T00:00:00+0000'], $object->toArray());
680680
}
681681

682+
public function testFilterAfterTransformingFilterIsSkippedIfTransformingFilterFails(): void
683+
{
684+
$this->expectException(ErrorRegistryException::class);
685+
$this->expectExceptionMessage(
686+
'Invalid value for property filteredProperty denied by filter dateTime: Invalid Date Time value "Hello"'
687+
);
688+
689+
$className = $this->generateClassFromFile(
690+
'FilterChainMultiType.json',
691+
(new GeneratorConfiguration())
692+
->addFilter(
693+
$this->getCustomFilter(
694+
[self::class, 'exceptionFilter'],
695+
'stripTime',
696+
[DateTime::class]
697+
)
698+
),
699+
false
700+
);
701+
702+
new $className(['filteredProperty' => 'Hello']);
703+
}
704+
682705
public function testFilterWhichAppliesToMultiTypePropertyPartiallyThrowsAnException(): void
683706
{
684707
$this->expectException(SchemaException::class);

0 commit comments

Comments
 (0)