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

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

01 Mar 2026 10:10PM UTC coverage: 98.693% (+0.06%) from 98.63%
22553948082

push

github

Enno Woortmann
Fix If-Then without else branch causes fatal error (fixes #113)

1 of 1 new or added line in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

3474 of 3520 relevant lines covered (98.69%)

558.7 hits per line

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

99.44
/src/PropertyProcessor/Property/BaseProcessor.php
1
<?php
2

3
declare(strict_types = 1);
4

5
namespace PHPModelGenerator\PropertyProcessor\Property;
6

7
use PHPMicroTemplate\Exception\FileSystemException;
8
use PHPMicroTemplate\Exception\SyntaxErrorException;
9
use PHPMicroTemplate\Exception\UndefinedSymbolException;
10
use PHPModelGenerator\Exception\Object\MaxPropertiesException;
11
use PHPModelGenerator\Exception\Object\MinPropertiesException;
12
use PHPModelGenerator\Exception\SchemaException;
13
use PHPModelGenerator\Model\Property\BaseProperty;
14
use PHPModelGenerator\Model\Property\Property;
15
use PHPModelGenerator\Model\Property\PropertyInterface;
16
use PHPModelGenerator\Model\Property\PropertyType;
17
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
18
use PHPModelGenerator\Model\Validator;
19
use PHPModelGenerator\Model\Validator\AbstractComposedPropertyValidator;
20
use PHPModelGenerator\Exception\Generic\DeniedPropertyException;
21
use PHPModelGenerator\Model\Validator\AdditionalPropertiesValidator;
22
use PHPModelGenerator\Model\Validator\ComposedPropertyValidator;
23
use PHPModelGenerator\Model\Validator\NoAdditionalPropertiesValidator;
24
use PHPModelGenerator\Model\Validator\PatternPropertiesValidator;
25
use PHPModelGenerator\Model\Validator\PropertyNamesValidator;
26
use PHPModelGenerator\Model\Validator\PropertyTemplateValidator;
27
use PHPModelGenerator\Model\Validator\PropertyValidator;
28
use PHPModelGenerator\PropertyProcessor\ComposedValue\AllOfProcessor;
29
use PHPModelGenerator\PropertyProcessor\ComposedValue\ComposedPropertiesInterface;
30
use PHPModelGenerator\PropertyProcessor\PropertyMetaDataCollection;
31
use PHPModelGenerator\PropertyProcessor\PropertyFactory;
32
use PHPModelGenerator\PropertyProcessor\PropertyProcessorFactory;
33

34
/**
35
 * Class BaseObjectProcessor
36
 *
37
 * @package PHPModelGenerator\PropertyProcessor\Property
38
 */
39
class BaseProcessor extends AbstractPropertyProcessor
40
{
41
    protected const TYPE = 'object';
42

43
    private const COUNT_PROPERTIES =
44
        'count(
45
            array_unique(
46
                array_merge(
47
                    array_keys($this->_rawModelDataInput),
48
                    array_keys($modelData),
49
                )
50
            ),
51
        )';
52

53
    /**
54
     * @inheritdoc
55
     *
56
     * @throws FileSystemException
57
     * @throws SchemaException
58
     * @throws SyntaxErrorException
59
     * @throws UndefinedSymbolException
60
     */
61
    public function process(string $propertyName, JsonSchema $propertySchema): PropertyInterface
1,999✔
62
    {
63
        $this->schema
1,999✔
64
            ->getSchemaDictionary()
1,999✔
65
            ->setUpDefinitionDictionary($this->schemaProcessor, $this->schema);
1,999✔
66

67
        // create a property which is used to gather composed properties validators.
68
        $property = new BaseProperty($propertyName, new PropertyType(static::TYPE), $propertySchema);
1,999✔
69
        $this->generateValidators($property, $propertySchema);
1,999✔
70

71
        $this->addPropertiesToSchema($propertySchema);
1,998✔
72
        $this->transferComposedPropertiesToSchema($property);
1,940✔
73

74
        $this->addPropertyNamesValidator($propertySchema);
1,939✔
75
        $this->addPatternPropertiesValidator($propertySchema);
1,938✔
76
        $this->addAdditionalPropertiesValidator($propertySchema);
1,937✔
77

78
        $this->addMinPropertiesValidator($propertyName, $propertySchema);
1,937✔
79
        $this->addMaxPropertiesValidator($propertyName, $propertySchema);
1,937✔
80

81
        return $property;
1,937✔
82
    }
83

84
    /**
85
     * Add a validator to check all provided property names
86
     *
87
     * @throws SchemaException
88
     * @throws FileSystemException
89
     * @throws SyntaxErrorException
90
     * @throws UndefinedSymbolException
91
     */
92
    protected function addPropertyNamesValidator(JsonSchema $propertySchema): void
1,939✔
93
    {
94
        if (!isset($propertySchema->getJson()['propertyNames'])) {
1,939✔
95
            return;
1,895✔
96
        }
97

98
        $this->schema->addBaseValidator(
44✔
99
            new PropertyNamesValidator(
44✔
100
                $this->schemaProcessor,
44✔
101
                $this->schema,
44✔
102
                $propertySchema->withJson($propertySchema->getJson()['propertyNames']),
44✔
103
            )
44✔
104
        );
44✔
105
    }
106

107
    /**
108
     * Add an object validator to specify constraints for properties which are not defined in the schema
109
     *
110
     * @throws FileSystemException
111
     * @throws SchemaException
112
     * @throws SyntaxErrorException
113
     * @throws UndefinedSymbolException
114
     */
115
    protected function addAdditionalPropertiesValidator(JsonSchema $propertySchema): void
1,937✔
116
    {
117
        $json = $propertySchema->getJson();
1,937✔
118

119
        if (!isset($json['additionalProperties']) &&
1,937✔
120
            $this->schemaProcessor->getGeneratorConfiguration()->denyAdditionalProperties()
1,937✔
121
        ) {
122
            $json['additionalProperties'] = false;
5✔
123
        }
124

125
        if (!isset($json['additionalProperties']) || $json['additionalProperties'] === true) {
1,937✔
126
            return;
1,859✔
127
        }
128

129
        if (!is_bool($json['additionalProperties'])) {
229✔
130
            $this->schema->addBaseValidator(
88✔
131
                new AdditionalPropertiesValidator(
88✔
132
                    $this->schemaProcessor,
88✔
133
                    $this->schema,
88✔
134
                    $propertySchema,
88✔
135
                )
88✔
136
            );
88✔
137

138
            return;
88✔
139
        }
140

141
        $this->schema->addBaseValidator(
141✔
142
            new NoAdditionalPropertiesValidator(
141✔
143
                new Property($this->schema->getClassName(), null, $propertySchema),
141✔
144
                $json,
141✔
145
            )
141✔
146
        );
141✔
147
    }
148

149
    /**
150
     * @throws SchemaException
151
     */
152
    protected function addPatternPropertiesValidator(JsonSchema $propertySchema): void
1,938✔
153
    {
154
        $json = $propertySchema->getJson();
1,938✔
155

156
        if (!isset($json['patternProperties'])) {
1,938✔
157
            return;
1,890✔
158
        }
159

160
        foreach ($json['patternProperties'] as $pattern => $schema) {
48✔
161
            $escapedPattern = addcslashes($pattern, '/');
48✔
162

163
            if (@preg_match("/$escapedPattern/", '') === false) {
48✔
164
                throw new SchemaException(
1✔
165
                    "Invalid pattern '$pattern' for pattern property in file {$propertySchema->getFile()}",
1✔
166
                );
1✔
167
            }
168

169
            $validator = new PatternPropertiesValidator(
47✔
170
                $this->schemaProcessor,
47✔
171
                $this->schema,
47✔
172
                $pattern,
47✔
173
                $propertySchema->withJson($schema),
47✔
174
            );
47✔
175

176
            $this->schema->addBaseValidator($validator);
47✔
177
        }
178
    }
179

180
    /**
181
     * Add an object validator to limit the amount of provided properties
182
     *
183
     * @throws SchemaException
184
     */
185
    protected function addMaxPropertiesValidator(string $propertyName, JsonSchema $propertySchema): void
1,937✔
186
    {
187
        $json = $propertySchema->getJson();
1,937✔
188

189
        if (!isset($json['maxProperties'])) {
1,937✔
190
            return;
1,904✔
191
        }
192

193
        $this->schema->addBaseValidator(
41✔
194
            new PropertyValidator(
41✔
195
                new Property($propertyName, null, $propertySchema),
41✔
196
                sprintf(
41✔
197
                    '%s > %d',
41✔
198
                    self::COUNT_PROPERTIES,
41✔
199
                    $json['maxProperties'],
41✔
200
                ),
41✔
201
                MaxPropertiesException::class,
41✔
202
                [$json['maxProperties']],
41✔
203
            )
41✔
204
        );
41✔
205
    }
206

207
    /**
208
     * Add an object validator to force at least the defined amount of properties to be provided
209
     *
210
     * @throws SchemaException
211
     */
212
    protected function addMinPropertiesValidator(string $propertyName, JsonSchema $propertySchema): void
1,937✔
213
    {
214
        $json = $propertySchema->getJson();
1,937✔
215

216
        if (!isset($json['minProperties'])) {
1,937✔
217
            return;
1,922✔
218
        }
219

220
        $this->schema->addBaseValidator(
23✔
221
            new PropertyValidator(
23✔
222
                new Property($propertyName, null, $propertySchema),
23✔
223
                sprintf(
23✔
224
                    '%s < %d',
23✔
225
                    self::COUNT_PROPERTIES,
23✔
226
                    $json['minProperties'],
23✔
227
                ),
23✔
228
                MinPropertiesException::class,
23✔
229
                [$json['minProperties']],
23✔
230
            )
23✔
231
        );
23✔
232
    }
233

234
    /**
235
     * Add the properties defined in the JSON schema to the current schema model
236
     *
237
     * @throws SchemaException
238
     */
239
    protected function addPropertiesToSchema(JsonSchema $propertySchema): void
1,998✔
240
    {
241
        $json = $propertySchema->getJson();
1,998✔
242

243
        $propertyFactory = new PropertyFactory(new PropertyProcessorFactory());
1,998✔
244
        $propertyMetaDataCollection = new PropertyMetaDataCollection(
1,998✔
245
            $json['required'] ?? [],
1,998✔
246
            $json['dependencies'] ?? [],
1,998✔
247
        );
1,998✔
248

249
        $json['properties'] ??= [];
1,998✔
250
        // setup empty properties for required properties which aren't defined in the properties section of the schema
251
        $json['properties'] += array_fill_keys(
1,998✔
252
            array_diff($json['required'] ?? [], array_keys($json['properties'])),
1,998✔
253
            [],
1,998✔
254
        );
1,998✔
255

256
        foreach ($json['properties'] as $propertyName => $propertyStructure) {
1,998✔
257
            if ($propertyStructure === false) {
1,931✔
258
                if (in_array($propertyName, $json['required'] ?? [], true)) {
13✔
259
                    throw new SchemaException(
1✔
260
                        sprintf(
1✔
261
                            "Property '%s' is denied (schema false) but also listed as required in file %s",
1✔
262
                            $propertyName,
1✔
263
                            $propertySchema->getFile(),
1✔
264
                        ),
1✔
265
                    );
1✔
266
                }
267

268
                $this->schema->addBaseValidator(
12✔
269
                    new PropertyValidator(
12✔
270
                        new Property($propertyName, null, $propertySchema->withJson([])),
12✔
271
                        "array_key_exists('" . addslashes($propertyName) . "', \$modelData)",
12✔
272
                        DeniedPropertyException::class,
12✔
273
                    )
12✔
274
                );
12✔
275
                continue;
12✔
276
            }
277

278
            $this->schema->addProperty(
1,930✔
279
                $propertyFactory->create(
1,930✔
280
                    $propertyMetaDataCollection,
1,930✔
281
                    $this->schemaProcessor,
1,930✔
282
                    $this->schema,
1,930✔
283
                    (string) $propertyName,
1,930✔
284
                    $propertySchema->withJson($propertyStructure),
1,930✔
285
                )
1,930✔
286
            );
1,930✔
287
        }
288
    }
289

290
    /**
291
     * Transfer properties of composed properties to the current schema to offer a complete model including all
292
     * composed properties.
293
     *
294
     * @throws SchemaException
295
     */
296
    protected function transferComposedPropertiesToSchema(PropertyInterface $property): void
1,940✔
297
    {
298
        foreach ($property->getValidators() as $validator) {
1,940✔
299
            $validator = $validator->getValidator();
1,940✔
300

301
            if (!is_a($validator, AbstractComposedPropertyValidator::class)) {
1,940✔
302
                continue;
1,940✔
303
            }
304

305
            // If the transferred validator of the composed property is also a composed property strip the nested
306
            // composition validations from the added validator. The nested composition will be validated in the object
307
            // generated for the nested composition which will be executed via an instantiation. Consequently, the
308
            // validation must not be executed in the outer composition.
309
            $this->schema->addBaseValidator(
211✔
310
                ($validator instanceof ComposedPropertyValidator)
211✔
311
                    ? $validator->withoutNestedCompositionValidation()
193✔
312
                    : $validator,
211✔
313
            );
211✔
314

315
            if (!is_a($validator->getCompositionProcessor(), ComposedPropertiesInterface::class, true)) {
211✔
UNCOV
316
                continue;
×
317
            }
318

319
            foreach ($validator->getComposedProperties() as $composedProperty) {
211✔
320
                $composedProperty->onResolve(function () use ($composedProperty, $property, $validator): void {
211✔
321
                    if (!$composedProperty->getNestedSchema()) {
211✔
322
                        throw new SchemaException(
1✔
323
                            sprintf(
1✔
324
                                "No nested schema for composed property %s in file %s found",
1✔
325
                                $property->getName(),
1✔
326
                                $property->getJsonSchema()->getFile(),
1✔
327
                            )
1✔
328
                        );
1✔
329
                    }
330

331
                    $composedProperty->getNestedSchema()->onAllPropertiesResolved(
210✔
332
                        function () use ($composedProperty, $validator): void {
210✔
333
                            foreach ($composedProperty->getNestedSchema()->getProperties() as $property) {
210✔
334
                                $this->schema->addProperty(
210✔
335
                                    $this->cloneTransferredProperty($property, $validator->getCompositionProcessor()),
210✔
336
                                );
210✔
337

338
                                $composedProperty->appendAffectedObjectProperty($property);
210✔
339
                            }
340
                        },
210✔
341
                    );
210✔
342
                });
211✔
343
            }
344
        }
345
    }
346

347
    /**
348
     * Clone the provided property to transfer it to a schema. Sets the nullability and required flag based on the
349
     * composition processor used to set up the composition
350
     */
351
    private function cloneTransferredProperty(
210✔
352
        PropertyInterface $property,
353
        string $compositionProcessor,
354
    ): PropertyInterface {
355
        $transferredProperty = (clone $property)
210✔
356
            ->filterValidators(static fn(Validator $validator): bool =>
210✔
357
                is_a($validator->getValidator(), PropertyTemplateValidator::class)
210✔
358
            );
210✔
359

360
        if (!is_a($compositionProcessor, AllOfProcessor::class, true)) {
210✔
361
            $transferredProperty->setRequired(false);
124✔
362

363
            if ($transferredProperty->getType()) {
124✔
364
                $transferredProperty->setType(
124✔
365
                    new PropertyType($transferredProperty->getType()->getName(), true),
124✔
366
                    new PropertyType($transferredProperty->getType(true)->getName(), true),
124✔
367
                );
124✔
368
            }
369
        }
370

371
        return $transferredProperty;
210✔
372
    }
373
}
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