• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wol-soft / php-json-schema-model-generator / 23929375043

03 Apr 2026 01:13AM UTC coverage: 98.248% (-0.4%) from 98.654%
23929375043

Pull #125

github

Enno Woortmann
attribute tests
Pull Request #125: attributes

1496 of 1526 new or added lines in 66 files covered. (98.03%)

4 existing lines in 3 files now uncovered.

4374 of 4452 relevant lines covered (98.25%)

620.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.0
/src/Model/Validator/FilterValidator.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace PHPModelGenerator\Model\Validator;
6

7
use PHPModelGenerator\Exception\Filter\IncompatibleFilterException;
8
use PHPModelGenerator\Exception\Filter\InvalidFilterValueException;
9
use PHPModelGenerator\Exception\SchemaException;
10
use PHPModelGenerator\Filter\FilterInterface;
11
use PHPModelGenerator\Filter\TransformingFilterInterface;
12
use PHPModelGenerator\Model\GeneratorConfiguration;
13
use PHPModelGenerator\Model\Property\PropertyInterface;
14
use PHPModelGenerator\Utils\RenderHelper;
15
use ReflectionException;
16
use ReflectionMethod;
17

18
/**
19
 * Class FilterValidator
20
 *
21
 * @package PHPModelGenerator\Model\Validator
22
 */
23
class FilterValidator extends PropertyTemplateValidator
24
{
25
    /**
26
     * FilterValidator constructor.
27
     *
28
     * @throws SchemaException
29
     * @throws ReflectionException
30
     */
31
    public function __construct(
128✔
32
        GeneratorConfiguration $generatorConfiguration,
33
        protected FilterInterface $filter,
34
        PropertyInterface $property,
35
        protected array $filterOptions = [],
36
        ?TransformingFilterInterface $transformingFilter = null,
37
    ) {
38
        $this->isResolved = true;
128✔
39

40
        $transformingFilter === null
128✔
41
            ? $this->validateFilterCompatibilityWithBaseType($this->filter, $property)
128✔
42
            : $this->validateFilterCompatibilityWithTransformedType($this->filter, $transformingFilter, $property);
9✔
43

44
        parent::__construct(
117✔
45
            $property,
117✔
46
            DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'Filter.phptpl',
117✔
47
            [
117✔
48
                'skipTransformedValuesCheck' => $transformingFilter !== null ? '!$transformationFailed' : '',
117✔
49
                'isTransformingFilter' => $this->filter instanceof TransformingFilterInterface,
117✔
50
                // check if the given value has a type matched by the filter
51
                'typeCheck' => !empty($this->filter->getAcceptedTypes())
117✔
52
                    ? '(' .
117✔
53
                        implode(
117✔
54
                            ' && ',
117✔
55
                            array_map(
117✔
56
                                static fn(string $type): string =>
117✔
57
                                    ReflectionTypeCheckValidator::fromType($type, $property)->getCheck(),
117✔
58
                                $this->mapDataTypes($this->filter->getAcceptedTypes()),
117✔
59
                            ),
117✔
60
                        ) .
117✔
61
                      ')'
117✔
62
                    : '',
117✔
63
                'filterClass' => $this->filter->getFilter()[0],
117✔
64
                'filterMethod' => $this->filter->getFilter()[1],
117✔
65
                'filterOptions' => var_export($this->filterOptions, true),
117✔
66
                'filterValueValidator' => new PropertyValidator(
117✔
67
                    $property,
117✔
68
                    '',
117✔
69
                    InvalidFilterValueException::class,
117✔
70
                    [$this->filter->getToken(), '&$filterException'],
117✔
71
                ),
117✔
72
                'viewHelper' => new RenderHelper($generatorConfiguration),
117✔
73
            ],
117✔
74
            IncompatibleFilterException::class,
117✔
75
            [$this->filter->getToken()],
117✔
76
        );
117✔
77
    }
78

79
    /**
80
     * Track if a transformation failed. If a transformation fails don't execute subsequent filter as they'd fail with
81
     * an invalid type
82
     */
83
    public function getValidatorSetUp(): string
111✔
84
    {
85
        return $this->filter instanceof TransformingFilterInterface
111✔
86
            ? '$transformationFailed = false;'
58✔
87
            : '';
111✔
88
    }
89

90
    /**
91
     * Make sure the filter is only executed if a non-transformed value is provided.
92
     * This is required as a setter (eg. for a string property which is modified by the DateTime filter into a DateTime
93
     * object) also accepts a transformed value (in this case a DateTime object).
94
     * If transformed values are provided neither filters defined before the transforming filter in the filter chain nor
95
     * the transforming filter must be executed as they are only compatible with the original value
96
     *
97
     * @throws ReflectionException
98
     */
99
    public function addTransformedCheck(TransformingFilterInterface $filter, PropertyInterface $property): self
63✔
100
    {
101
        $typeAfterFilter = (new ReflectionMethod($filter->getFilter()[0], $filter->getFilter()[1]))->getReturnType();
63✔
102

103
        if (
104
            $typeAfterFilter &&
63✔
105
            $typeAfterFilter->getName() &&
63✔
106
            !in_array($typeAfterFilter->getName(), $this->mapDataTypes($filter->getAcceptedTypes()))
63✔
107
        ) {
108
            $this->templateValues['skipTransformedValuesCheck'] = ReflectionTypeCheckValidator::fromReflectionType(
63✔
109
                $typeAfterFilter,
63✔
110
                $property,
63✔
111
            )->getCheck();
63✔
112
        }
113

114
        return $this;
63✔
115
    }
116

117
    /**
118
     * Check if the given filter is compatible with the base type of the property defined in the schema
119
     *
120
     * @throws SchemaException
121
     */
122
    private function validateFilterCompatibilityWithBaseType(FilterInterface $filter, PropertyInterface $property): void
128✔
123
    {
124
        if (empty($filter->getAcceptedTypes()) || (!$property->getType() && !$property->getNestedSchema())) {
128✔
UNCOV
125
            return;
×
126
        }
127

128
        $typeNames = $property->getNestedSchema() !== null ? ['object'] : $property->getType()->getNames();
128✔
129
        $incompatibleTypes = !empty($typeNames)
128✔
130
            ? array_diff($typeNames, $this->mapDataTypes($filter->getAcceptedTypes()))
128✔
NEW
131
            : [];
×
132

133
        if ($property->getType()?->isNullable() && !in_array('null', $filter->getAcceptedTypes())) {
128✔
134
            $incompatibleTypes[] = 'null';
1✔
135
        }
136

137
        if (!empty($incompatibleTypes)) {
128✔
138
            throw new SchemaException(
11✔
139
                sprintf(
11✔
140
                    'Filter %s is not compatible with property type %s for property %s in file %s',
11✔
141
                    $filter->getToken(),
11✔
142
                    implode('|', array_merge($typeNames, $property->getType()?->isNullable() ? ['null'] : [])),
11✔
143
                    $property->getName(),
11✔
144
                    $property->getJsonSchema()->getFile(),
11✔
145
                )
11✔
146
            );
11✔
147
        }
148
    }
149

150
    /**
151
     * Check if the given filter is compatible with the result of the given transformation filter
152
     *
153
     * @throws ReflectionException
154
     * @throws SchemaException
155
     */
156
    private function validateFilterCompatibilityWithTransformedType(
9✔
157
        FilterInterface $filter,
158
        TransformingFilterInterface $transformingFilter,
159
        PropertyInterface $property,
160
    ): void {
161
        $transformedType = (new ReflectionMethod(
9✔
162
            $transformingFilter->getFilter()[0],
9✔
163
            $transformingFilter->getFilter()[1],
9✔
164
        ))->getReturnType();
9✔
165

166
        if (
167
            !empty($filter->getAcceptedTypes()) &&
9✔
168
            (
169
                !in_array($transformedType->getName(), $this->mapDataTypes($filter->getAcceptedTypes())) ||
9✔
170
                ($transformedType->allowsNull() && !in_array('null', $filter->getAcceptedTypes()))
9✔
171
            )
172
        ) {
173
            throw new SchemaException(
2✔
174
                sprintf(
2✔
175
                    'Filter %s is not compatible with transformed property type %s for property %s in file %s',
2✔
176
                    $filter->getToken(),
2✔
177
                    $transformedType->allowsNull()
2✔
178
                        ? "[null, {$transformedType->getName()}]"
2✔
179
                        : $transformedType->getName(),
2✔
180
                    $property->getName(),
2✔
181
                    $property->getJsonSchema()->getFile(),
2✔
182
                )
2✔
183
            );
2✔
184
        }
185
    }
186

187
    /**
188
     * Map a list of accepted data types to their corresponding PHP types
189
     */
190
    private function mapDataTypes(array $acceptedTypes): array
128✔
191
    {
192
        return array_map(static fn(string $jsonSchemaType): string => match ($jsonSchemaType) {
128✔
193
            'integer' => 'int',
59✔
194
            'number' => 'float',
55✔
195
            'boolean' => 'bool',
22✔
196
            default => $jsonSchemaType,
128✔
197
        }, $acceptedTypes);
128✔
198
    }
199

200
    public function getFilter(): FilterInterface
28✔
201
    {
202
        return $this->filter;
28✔
203
    }
204

205
    public function getFilterOptions(): array
26✔
206
    {
207
        return $this->filterOptions;
26✔
208
    }
209
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc