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

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

23 Mar 2026 01:55PM UTC coverage: 98.584% (-0.001%) from 98.585%
23440965067

Pull #118

github

Enno Woortmann
Merge fix/issue-110 into chore/phpunit-13-upgrade

Brings in patternProperties type intersection, PSR-12 code style
enforcement, PropertyMerger API refactor, and all related tests and
documentation. Resolved conflicts by keeping readonly constructors and
PHP 8.4+ style from this branch, applying phpcbf to align opening
braces with the PSR-12 rule. Added squizlabs/php_codesniffer to
require-dev alongside phpunit ^13.0.
Pull Request #118: Upgrade to PHP 8.4 minimum and PHPUnit 13

508 of 517 new or added lines in 54 files covered. (98.26%)

23 existing lines in 9 files now uncovered.

3828 of 3883 relevant lines covered (98.58%)

548.12 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace PHPModelGenerator\Model;
6

7
use PHPModelGenerator\Interfaces\JSONModelInterface;
8
use PHPModelGenerator\Model\Property\PropertyInterface;
9
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
10
use PHPModelGenerator\Model\SchemaDefinition\JsonSchemaTrait;
11
use PHPModelGenerator\Model\SchemaDefinition\SchemaDefinitionDictionary;
12
use PHPModelGenerator\Exception\SchemaException;
13
use PHPModelGenerator\Model\Validator\AbstractComposedPropertyValidator;
14
use PHPModelGenerator\Model\Validator\PropertyValidatorInterface;
15
use PHPModelGenerator\Model\Validator\SchemaDependencyValidator;
16
use PHPModelGenerator\PropertyProcessor\ComposedValue\AllOfProcessor;
17
use PHPModelGenerator\PropertyProcessor\Decorator\SchemaNamespaceTransferDecorator;
18
use PHPModelGenerator\SchemaProcessor\Hook\SchemaHookInterface;
19
use PHPModelGenerator\Utils\PropertyMerger;
20

21
/**
22
 * Class Schema
23
 *
24
 * @package PHPModelGenerator\Model
25
 */
26
class Schema
27
{
28
    use JsonSchemaTrait;
29

30
    /** @var string */
31
    protected $description;
32

33
    /** @var string[] */
34
    protected $traits = [];
35
    /** @var string[] */
36
    protected $interfaces = [];
37
    /** @var PropertyInterface[] The properties which are part of the class */
38
    protected $properties = [];
39
    /** @var MethodInterface[] */
40
    protected $methods = [];
41

42
    /** @var PropertyValidatorInterface[] A Collection of validators which must be applied
43
     *                                    before adding properties to the model
44
     */
45
    protected $baseValidators = [];
46
    /** @var string[] */
47
    protected $usedClasses = [];
48
    /** @var SchemaNamespaceTransferDecorator[] */
49
    protected $namespaceTransferDecorators = [];
50
    /** @var SchemaHookInterface[] */
51
    protected $schemaHooks = [];
52

53
    protected SchemaDefinitionDictionary $schemaDefinitionDictionary;
54

55
    private int $resolvedProperties = 0;
56
    /** @var callable[] */
57
    private array $onAllPropertiesResolvedCallbacks = [];
58

59
    private PropertyMerger $propertyMerger;
60

61
    /**
62
     * Schema constructor.
63
     */
64
    public function __construct(
2,069✔
65
        protected string $targetFileName,
66
        protected string $classPath,
67
        protected string $className,
68
        JsonSchema $schema,
69
        ?SchemaDefinitionDictionary $dictionary = null,
70
        protected bool $initialClass = false,
71
        ?GeneratorConfiguration $generatorConfiguration = null,
72
    ) {
73
        $this->jsonSchema = $schema;
2,069✔
74
        $this->schemaDefinitionDictionary = $dictionary ?? new SchemaDefinitionDictionary($schema);
2,069✔
75
        $this->description = $schema->getJson()['description'] ?? '';
2,069✔
76
        $this->propertyMerger = new PropertyMerger($generatorConfiguration);
2,069✔
77

78
        $this->addInterface(JSONModelInterface::class);
2,069✔
79
    }
80

81
    public function getTargetFileName(): string
1,998✔
82
    {
83
        return $this->targetFileName;
1,998✔
84
    }
85

86
    public function getClassName(): string
1,987✔
87
    {
88
        return $this->className;
1,987✔
89
    }
90

91
    public function getClassPath(): string
1,983✔
92
    {
93
        return $this->classPath;
1,983✔
94
    }
95

96
    public function getDescription(): string
1,979✔
97
    {
98
        return $this->description;
1,979✔
99
    }
100

101
    public function onAllPropertiesResolved(callable $callback): self
351✔
102
    {
103
        $this->resolvedProperties === count($this->properties)
351✔
104
            ? $callback()
349✔
UNCOV
105
            : $this->onAllPropertiesResolvedCallbacks[] = $callback;
×
106

107
        return $this;
349✔
108
    }
109

110
    /**
111
     * @return PropertyInterface[]
112
     */
113
    public function getProperties(): array
1,998✔
114
    {
115
        $hasSchemaDependencyValidator = static function (PropertyInterface $property): bool {
1,998✔
116
            foreach ($property->getValidators() as $validator) {
1,075✔
117
                if ($validator->getValidator() instanceof SchemaDependencyValidator) {
861✔
118
                    return true;
63✔
119
                }
120
            }
121

122
            return false;
1,075✔
123
        };
1,998✔
124

125
        // order the properties to make sure properties with a SchemaDependencyValidator are validated at the beginning
126
        // of the validation process for correct exception order of the messages
127
        usort(
1,998✔
128
            $this->properties,
1,998✔
129
            static function (
1,998✔
130
                PropertyInterface $property,
1,998✔
131
                PropertyInterface $comparedProperty,
1,998✔
132
            ) use ($hasSchemaDependencyValidator): int {
1,998✔
133
                $propertyHasSchemaDependencyValidator = $hasSchemaDependencyValidator($property);
1,075✔
134
                $comparedPropertyHasSchemaDependencyValidator = $hasSchemaDependencyValidator($comparedProperty);
1,075✔
135
                return $comparedPropertyHasSchemaDependencyValidator <=> $propertyHasSchemaDependencyValidator;
1,075✔
136
            },
1,998✔
137
        );
1,998✔
138

139
        return $this->properties;
1,998✔
140
    }
141

142
    /**
143
     * @param string|null $compositionProcessor The FQCN of the composition processor transferring this property,
144
     *                                           or null when not called from a composition context.
145
     *
146
     * @throws SchemaException
147
     */
148
    public function addProperty(PropertyInterface $property, ?string $compositionProcessor = null): self
1,969✔
149
    {
150
        if (!isset($this->properties[$property->getName()])) {
1,969✔
151
            $this->properties[$property->getName()] = $property;
1,969✔
152

153
            if ($compositionProcessor === null) {
1,969✔
154
                $this->propertyMerger->markRootRegistered($property->getName());
1,969✔
155
            }
156

157
            $property->onResolve(function (): void {
1,969✔
158
                if (++$this->resolvedProperties === count($this->properties)) {
1,925✔
159
                    foreach ($this->onAllPropertiesResolvedCallbacks as $callback) {
1,925✔
UNCOV
160
                        $callback();
×
161

UNCOV
162
                        $this->onAllPropertiesResolvedCallbacks = [];
×
163
                    }
164
                }
165
            });
1,969✔
166

167
            return $this;
1,969✔
168
        }
169

170
        $this->propertyMerger->merge(
95✔
171
            $this->properties[$property->getName()],
95✔
172
            $property,
95✔
173
            is_a($compositionProcessor, AllOfProcessor::class, true),
95✔
174
        );
95✔
175

176
        return $this;
92✔
177
    }
178

179
    /**
180
     * @return PropertyValidatorInterface[]
181
     */
182
    public function getBaseValidators(): array
1,995✔
183
    {
184
        return $this->baseValidators;
1,995✔
185
    }
186

187
    /**
188
     * Get the keys of all composition base validators
189
     */
190
    public function getCompositionValidatorKeys(): array
586✔
191
    {
192
        $keys = [];
586✔
193

194
        foreach ($this->baseValidators as $key => $validator) {
586✔
195
            if (is_a($validator, AbstractComposedPropertyValidator::class)) {
586✔
196
                $keys[] = $key;
258✔
197
            }
198
        }
199

200
        return $keys;
586✔
201
    }
202

203
    public function addBaseValidator(PropertyValidatorInterface $baseValidator): self
594✔
204
    {
205
        $this->baseValidators[] = $baseValidator;
594✔
206

207
        return $this;
594✔
208
    }
209

210
    public function getSchemaDictionary(): SchemaDefinitionDictionary
2,061✔
211
    {
212
        return $this->schemaDefinitionDictionary;
2,061✔
213
    }
214

215
    /**
216
     * Add a class to the schema which is required
217
     */
218
    public function addUsedClass(string $fqcn): self
2,069✔
219
    {
220
        $this->usedClasses[] = trim($fqcn, '\\');
2,069✔
221

222
        return $this;
2,069✔
223
    }
224

225
    public function addNamespaceTransferDecorator(SchemaNamespaceTransferDecorator $decorator): self
885✔
226
    {
227
        $this->namespaceTransferDecorators[] = $decorator;
885✔
228

229
        return $this;
885✔
230
    }
231

232
    /**
233
     * @param Schema[] $visitedSchema
234
     *
235
     * @return string[]
236
     */
237
    public function getUsedClasses(array $visitedSchema = []): array
1,979✔
238
    {
239
        $usedClasses = $this->usedClasses;
1,979✔
240

241
        foreach ($this->namespaceTransferDecorators as $decorator) {
1,979✔
242
            $usedClasses = array_merge($usedClasses, $decorator->resolve(array_merge($visitedSchema, [$this])));
880✔
243
        }
244

245
        return $usedClasses;
1,979✔
246
    }
247

248
    /**
249
     * @param string $methodKey An unique key in the scope of the schema to identify the method
250
     */
251
    public function addMethod(string $methodKey, MethodInterface $method): self
1,020✔
252
    {
253
        $this->methods[$methodKey] = $method;
1,020✔
254

255
        return $this;
1,020✔
256
    }
257

258
    /**
259
     * @return MethodInterface[]
260
     */
261
    public function getMethods(): array
1,979✔
262
    {
263
        return $this->methods;
1,979✔
264
    }
265

266
    public function hasMethod(string $methodKey): bool
697✔
267
    {
268
        return isset($this->methods[$methodKey]);
697✔
269
    }
270

271
    /**
272
     * @return string[]
273
     */
274
    public function getTraits(): array
1,979✔
275
    {
276
        return $this->traits;
1,979✔
277
    }
278

279
    public function addTrait(string $trait): self
49✔
280
    {
281
        $this->traits[] = $trait;
49✔
282
        $this->addUsedClass($trait);
49✔
283

284
        return $this;
49✔
285
    }
286

287
    /**
288
     * @return string[]
289
     */
290
    public function getInterfaces(): array
1,979✔
291
    {
292
        return $this->interfaces;
1,979✔
293
    }
294

295
    public function addInterface(string $interface): self
2,069✔
296
    {
297
        $this->interfaces[] = $interface;
2,069✔
298
        $this->addUsedClass($interface);
2,069✔
299

300
        return $this;
2,069✔
301
    }
302

303
    /**
304
     * Add an additional schema hook
305
     */
306
    public function addSchemaHook(SchemaHookInterface $schemaHook): self
1,994✔
307
    {
308
        $this->schemaHooks[] = $schemaHook;
1,994✔
309

310
        return $this;
1,994✔
311
    }
312

313
    /**
314
     * @return SchemaHookInterface[]
315
     */
316
    public function getSchemaHooks(): array
1,979✔
317
    {
318
        return $this->schemaHooks;
1,979✔
319
    }
320

UNCOV
321
    public function isInitialClass(): bool
×
322
    {
UNCOV
323
        return $this->initialClass;
×
324
    }
325
}
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