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

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

01 Jun 2026 08:41AM UTC coverage: 98.867% (+0.002%) from 98.865%
26748446430

push

github

wol-soft
Underscore-prefix all framework infrastructure methods (Phase 2)

All private/protected framework methods in generated classes and in
SerializableTrait now carry a leading underscore, making the dynamic
per-property namespace (get*/set*) and the framework infrastructure
namespace (_process*/_validate*/_serialize*/_executeBaseValidators/…)
disjoint by construction.

Generator-side renames:
  executeBaseValidators           -> _executeBaseValidators
  process{X}                      -> _process{X}
  validate{X}                     -> _validate{X}
  validateComposition_{i}         -> _validateComposition_{i}
  validate{X}_{Validator}_{hash}  -> _validate{X}_{Validator}_{hash}
  resolveSerializationHook        -> _resolveSerializationHook
  serialize{X} (transforming)     -> _serialize{X}
  serializeAdditionalProperties   -> _serializeAdditionalProperties
  serializePatternProperties      -> _serializePatternProperties

Trait-call sites in templates updated to match. SerializableTrait
method renames are in the companion production-library commit
(2566823).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

8 of 8 new or added lines in 3 files covered. (100.0%)

5 existing lines in 1 file now uncovered.

5934 of 6002 relevant lines covered (98.87%)

587.93 hits per line

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

96.58
/src/Model/Schema.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace PHPModelGenerator\Model;
6

7
use PHPModelGenerator\Attributes\Deprecated;
8
use PHPModelGenerator\Attributes\JsonPointer;
9
use PHPModelGenerator\Attributes\JsonSchema as JsonSchemaAttribute;
10
use PHPModelGenerator\Attributes\Source;
11
use PHPModelGenerator\Exception\SchemaException;
12
use PHPModelGenerator\Interfaces\JSONModelInterface;
13
use PHPModelGenerator\Model\Attributes\AttributesTrait;
14
use PHPModelGenerator\Model\Attributes\PhpAttribute;
15
use PHPModelGenerator\Model\Property\PropertyInterface;
16
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
17
use PHPModelGenerator\Model\SchemaDefinition\JsonSchemaTrait;
18
use PHPModelGenerator\Model\SchemaDefinition\SchemaDefinitionDictionary;
19
use PHPModelGenerator\Model\Validator\AbstractComposedPropertyValidator;
20
use PHPModelGenerator\Model\Validator\PropertyValidatorInterface;
21
use PHPModelGenerator\Model\Validator\SchemaDependencyValidator;
22
use PHPModelGenerator\Model\Validator\Factory\Composition\AllOfValidatorFactory;
23
use PHPModelGenerator\PropertyProcessor\Decorator\SchemaNamespaceTransferDecorator;
24
use PHPModelGenerator\SchemaProcessor\Hook\SchemaHookInterface;
25
use PHPModelGenerator\Utils\PropertyMerger;
26

27
/**
28
 * Class Schema
29
 *
30
 * @package PHPModelGenerator\Model
31
 */
32
class Schema
33
{
34
    use JsonSchemaTrait;
35
    use AttributesTrait;
36

37
    /** @var string */
38
    protected $description;
39

40
    /** @var string[] */
41
    protected $traits = [];
42
    /** @var string[] */
43
    protected $interfaces = [];
44
    /** @var PropertyInterface[] The properties which are part of the class */
45
    protected $properties = [];
46
    /** @var MethodInterface[] */
47
    protected $methods = [];
48

49
    /** @var PropertyValidatorInterface[] A Collection of validators which must be applied
50
     *                                    before adding properties to the model
51
     */
52
    protected $baseValidators = [];
53
    /** @var string[] */
54
    protected $usedClasses = [];
55
    /** @var SchemaNamespaceTransferDecorator[] */
56
    protected $namespaceTransferDecorators = [];
57
    /** @var SchemaHookInterface[] */
58
    protected $schemaHooks = [];
59

60
    protected SchemaDefinitionDictionary $schemaDefinitionDictionary;
61

62
    private int $resolvedProperties = 0;
63
    /** @var callable[] */
64
    private array $onAllPropertiesResolvedCallbacks = [];
65

66
    /** @var string[] Maps normalized attribute → raw property name; used to detect property-vs-property collisions */
67
    private array $attributeIndex = [];
68

69
    private PropertyMerger $propertyMerger;
70

71
    /**
72
     * Schema constructor.
73
     */
74
    public function __construct(
2,562✔
75
        protected string $targetFileName,
76
        protected string $classPath,
77
        protected string $className,
78
        JsonSchema $schema,
79
        ?SchemaDefinitionDictionary $dictionary = null,
80
        protected bool $initialClass = false,
81
        ?GeneratorConfiguration $generatorConfiguration = null,
82
    ) {
83
        $this->jsonSchema = $schema;
2,562✔
84
        $this->schemaDefinitionDictionary = $dictionary ?? new SchemaDefinitionDictionary($schema);
2,562✔
85
        $this->description = $schema->getJson()['description'] ?? '';
2,562✔
86
        $this->propertyMerger = new PropertyMerger($generatorConfiguration);
2,562✔
87

88
        $this
2,562✔
89
            ->addInterface(JSONModelInterface::class)
2,562✔
90
            ->addAttribute(
2,562✔
91
                new PhpAttribute(JsonPointer::class, [$schema->getPointer()]),
2,562✔
92
                $generatorConfiguration,
2,562✔
93
                PhpAttribute::JSON_POINTER,
2,562✔
94
            )
2,562✔
95
            ->addAttribute(
2,562✔
96
                new PhpAttribute(
2,562✔
97
                    JsonSchemaAttribute::class,
2,562✔
98
                    [empty($schema->getJson()) ? '{}' : json_encode($schema->getJson())],
2,562✔
99
                ),
2,562✔
100
                $generatorConfiguration,
2,562✔
101
                PhpAttribute::JSON_SCHEMA,
2,562✔
102
            )
2,562✔
103
            ->addAttribute(
2,562✔
104
                new PhpAttribute(Source::class, [$schema->getFile()]),
2,562✔
105
                $generatorConfiguration,
2,562✔
106
                PhpAttribute::SOURCE,
2,562✔
107
            );
2,562✔
108

109
        if (isset($schema->getJson()['deprecated']) && $schema->getJson()['deprecated'] === true) {
2,562✔
110
            $this->addAttribute(
2✔
111
                new PhpAttribute(Deprecated::class),
2✔
112
                $generatorConfiguration,
2✔
113
                PhpAttribute::DEPRECATED,
2✔
114
            );
2✔
115
        }
116
    }
117

118
    public function getTargetFileName(): string
2,421✔
119
    {
120
        return $this->targetFileName;
2,421✔
121
    }
122

123
    public function getClassName(): string
2,410✔
124
    {
125
        return $this->className;
2,410✔
126
    }
127

128
    public function getClassPath(): string
2,405✔
129
    {
130
        return $this->classPath;
2,405✔
131
    }
132

133
    public function getDescription(): string
2,395✔
134
    {
135
        return $this->description;
2,395✔
136
    }
137

138
    public function onAllPropertiesResolved(callable $callback): self
460✔
139
    {
140
        $this->resolvedProperties === count($this->properties)
460✔
141
            ? $callback()
458✔
UNCOV
142
            : $this->onAllPropertiesResolvedCallbacks[] = $callback;
×
143

144
        return $this;
458✔
145
    }
146

147
    /**
148
     * @return PropertyInterface[]
149
     */
150
    public function getProperties(): array
2,419✔
151
    {
152
        $hasSchemaDependencyValidator = static function (PropertyInterface $property): bool {
2,419✔
153
            foreach ($property->getValidators() as $validator) {
1,236✔
154
                if ($validator->getValidator() instanceof SchemaDependencyValidator) {
980✔
155
                    return true;
63✔
156
                }
157
            }
158

159
            return false;
1,236✔
160
        };
2,419✔
161

162
        // order the properties to make sure properties with a SchemaDependencyValidator are validated at the beginning
163
        // of the validation process for correct exception order of the messages
164
        usort(
2,419✔
165
            $this->properties,
2,419✔
166
            static function (
2,419✔
167
                PropertyInterface $property,
2,419✔
168
                PropertyInterface $comparedProperty,
2,419✔
169
            ) use ($hasSchemaDependencyValidator): int {
2,419✔
170
                $propertyHasSchemaDependencyValidator = $hasSchemaDependencyValidator($property);
1,236✔
171
                $comparedPropertyHasSchemaDependencyValidator = $hasSchemaDependencyValidator($comparedProperty);
1,236✔
172
                return $comparedPropertyHasSchemaDependencyValidator <=> $propertyHasSchemaDependencyValidator;
1,236✔
173
            },
2,419✔
174
        );
2,419✔
175

176
        return $this->properties;
2,419✔
177
    }
178

179
    /**
180
     * @param string|null $compositionProcessor The FQCN of the composition processor transferring this property,
181
     *                                           or null when not called from a composition context.
182
     *
183
     * @throws SchemaException
184
     */
185
    public function addProperty(PropertyInterface $property, ?string $compositionProcessor = null): self
2,394✔
186
    {
187
        if (!isset($this->properties[$property->getName()])) {
2,394✔
188
            $attribute = $property->getAttribute();
2,394✔
189

190
            $existingRawName = $this->attributeIndex[$attribute] ?? null;
2,394✔
191
            if ($existingRawName !== null && $existingRawName !== $property->getName()) {
2,394✔
192
                throw new SchemaException(
5✔
193
                    sprintf(
5✔
194
                        "Property names '%s' and '%s' both normalize to attribute '%s' in file %s",
5✔
195
                        $existingRawName,
5✔
196
                        $property->getName(),
5✔
197
                        $attribute,
5✔
198
                        $this->jsonSchema->getFile(),
5✔
199
                    ),
5✔
200
                );
5✔
201
            }
202

203
            $this->attributeIndex[$attribute] = $property->getName();
2,394✔
204
            $this->properties[$property->getName()] = $property;
2,394✔
205

206
            if ($compositionProcessor === null) {
2,394✔
207
                $this->propertyMerger->markRootRegistered($property->getName());
2,394✔
208
            }
209

210
            $property->onResolve(function (): void {
2,394✔
211
                if (++$this->resolvedProperties === count($this->properties)) {
2,346✔
212
                    foreach ($this->onAllPropertiesResolvedCallbacks as $callback) {
2,346✔
UNCOV
213
                        $callback();
×
214

UNCOV
215
                        $this->onAllPropertiesResolvedCallbacks = [];
×
216
                    }
217
                }
218
            });
2,394✔
219

220
            return $this;
2,394✔
221
        }
222

223
        $this->propertyMerger->merge(
180✔
224
            $this->properties[$property->getName()],
180✔
225
            $property,
180✔
226
            is_a($compositionProcessor, AllOfValidatorFactory::class, true),
180✔
227
        );
180✔
228

229
        return $this;
177✔
230
    }
231

232
    /**
233
     * @return PropertyValidatorInterface[]
234
     */
235
    public function getBaseValidators(): array
2,412✔
236
    {
237
        return $this->baseValidators;
2,412✔
238
    }
239

240
    /**
241
     * Get the keys of all composition base validators
242
     */
243
    public function getCompositionValidatorKeys(): array
2,412✔
244
    {
245
        $keys = [];
2,412✔
246

247
        foreach ($this->baseValidators as $key => $validator) {
2,412✔
248
            if (is_a($validator, AbstractComposedPropertyValidator::class)) {
718✔
249
                $keys[] = $key;
369✔
250
            }
251
        }
252

253
        return $keys;
2,412✔
254
    }
255

256
    public function addBaseValidator(PropertyValidatorInterface $baseValidator): self
725✔
257
    {
258
        $this->baseValidators[] = $baseValidator;
725✔
259

260
        return $this;
725✔
261
    }
262

263
    public function getSchemaDictionary(): SchemaDefinitionDictionary
2,543✔
264
    {
265
        return $this->schemaDefinitionDictionary;
2,543✔
266
    }
267

268
    /**
269
     * Add a class to the schema which is required
270
     */
271
    public function addUsedClass(string $fqcn): self
2,562✔
272
    {
273
        $this->usedClasses[] = trim($fqcn, '\\');
2,562✔
274

275
        return $this;
2,562✔
276
    }
277

278
    public function addNamespaceTransferDecorator(SchemaNamespaceTransferDecorator $decorator): self
1,025✔
279
    {
280
        $this->namespaceTransferDecorators[] = $decorator;
1,025✔
281

282
        return $this;
1,025✔
283
    }
284

285
    /**
286
     * @param Schema[] $visitedSchema
287
     *
288
     * @return string[]
289
     */
290
    public function getUsedClasses(array $visitedSchema = []): array
2,395✔
291
    {
292
        $usedClasses = $this->usedClasses;
2,395✔
293

294
        foreach ($this->namespaceTransferDecorators as $decorator) {
2,395✔
295
            $usedClasses = array_merge($usedClasses, $decorator->resolve(array_merge($visitedSchema, [$this])));
1,014✔
296
        }
297

298
        return $usedClasses;
2,395✔
299
    }
300

301
    /**
302
     * @param string $methodKey An unique key in the scope of the schema to identify the method
303
     */
304
    public function addMethod(string $methodKey, MethodInterface $method): self
1,258✔
305
    {
306
        $this->methods[$methodKey] = $method;
1,258✔
307

308
        return $this;
1,258✔
309
    }
310

311
    /**
312
     * @return MethodInterface[]
313
     */
314
    public function getMethods(): array
2,395✔
315
    {
316
        return $this->methods;
2,395✔
317
    }
318

319
    public function hasMethod(string $methodKey): bool
809✔
320
    {
321
        return isset($this->methods[$methodKey]);
809✔
322
    }
323

324
    /**
325
     * @return string[]
326
     */
327
    public function getTraits(): array
2,395✔
328
    {
329
        return $this->traits;
2,395✔
330
    }
331

332
    public function addTrait(string $trait): self
60✔
333
    {
334
        $this->traits[] = $trait;
60✔
335
        $this->addUsedClass($trait);
60✔
336

337
        return $this;
60✔
338
    }
339

340
    /**
341
     * @return string[]
342
     */
343
    public function getInterfaces(): array
2,395✔
344
    {
345
        return $this->interfaces;
2,395✔
346
    }
347

348
    public function addInterface(string $interface): self
2,562✔
349
    {
350
        $this->interfaces[] = $interface;
2,562✔
351
        $this->addUsedClass($interface);
2,562✔
352

353
        return $this;
2,562✔
354
    }
355

356
    /**
357
     * Add an additional schema hook
358
     */
359
    public function addSchemaHook(SchemaHookInterface $schemaHook): self
2,411✔
360
    {
361
        $this->schemaHooks[] = $schemaHook;
2,411✔
362

363
        return $this;
2,411✔
364
    }
365

366
    /**
367
     * @return SchemaHookInterface[]
368
     */
369
    public function getSchemaHooks(): array
2,395✔
370
    {
371
        return $this->schemaHooks;
2,395✔
372
    }
373

UNCOV
374
    public function isInitialClass(): bool
×
375
    {
UNCOV
376
        return $this->initialClass;
×
377
    }
378

379
    public function getPropertyMerger(): PropertyMerger
365✔
380
    {
381
        return $this->propertyMerger;
365✔
382
    }
383
}
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