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

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

pending completion
4564420247

Pull #68

github

GitHub
Merge 3a8dbf1f2 into 1294d211a
Pull Request #68: Fix recursive reference resolving

288 of 288 new or added lines in 39 files covered. (100.0%)

2777 of 2813 relevant lines covered (98.72%)

526.99 hits per line

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

94.74
/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\Model\Validator\AbstractComposedPropertyValidator;
13
use PHPModelGenerator\Model\Validator\PropertyValidatorInterface;
14
use PHPModelGenerator\Model\Validator\SchemaDependencyValidator;
15
use PHPModelGenerator\PropertyProcessor\Decorator\SchemaNamespaceTransferDecorator;
16
use PHPModelGenerator\SchemaProcessor\Hook\SchemaHookInterface;
17

18
/**
19
 * Class Schema
20
 *
21
 * @package PHPModelGenerator\Model
22
 */
23
class Schema
24
{
25
    use JsonSchemaTrait;
26

27
    /** @var string */
28
    protected $className;
29
    /** @var string */
30
    protected $classPath;
31
    /** @var string */
32
    protected $description;
33

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

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

56
    /** @var SchemaDefinitionDictionary */
57
    protected $schemaDefinitionDictionary;
58

59
    /** @var int */
60
    private $resolvedProperties = 0;
61
    /** @var callable[] */
62
    private $onAllPropertiesResolvedCallbacks = [];
63

64
    /**
65
     * Schema constructor.
66
     *
67
     * @param string $classPath
68
     * @param string $className
69
     * @param JsonSchema $schema
70
     * @param SchemaDefinitionDictionary|null $dictionary
71
     * @param bool $initialClass
72
     */
73
    public function __construct(
74
        string $classPath,
75
        string $className,
76
        JsonSchema $schema,
77
        SchemaDefinitionDictionary $dictionary = null,
78
        bool $initialClass = false
79
    ) {
80
        $this->className = $className;
1,838✔
81
        $this->classPath = $classPath;
1,838✔
82
        $this->jsonSchema = $schema;
1,838✔
83
        $this->schemaDefinitionDictionary = $dictionary ?? new SchemaDefinitionDictionary('');
1,838✔
84
        $this->description = $schema->getJson()['description'] ?? '';
1,838✔
85
        $this->initialClass = $initialClass;
1,838✔
86

87
        $this->addInterface(JSONModelInterface::class);
1,838✔
88
    }
89

90
    /**
91
     * @return string
92
     */
93
    public function getClassName(): string
94
    {
95
        return $this->className;
942✔
96
    }
97

98
    /**
99
     * @return string
100
     */
101
    public function getClassPath(): string
102
    {
103
        return $this->classPath;
837✔
104
    }
105

106
    /**
107
     * @return string
108
     */
109
    public function getDescription(): string
110
    {
111
        return $this->description;
1,769✔
112
    }
113

114
    public function onAllPropertiesResolved(callable $callback): self
115
    {
116
        $this->resolvedProperties === count($this->properties)
274✔
117
            ? $callback()
274✔
118
            : $this->onAllPropertiesResolvedCallbacks[] = $callback;
×
119

120
        return $this;
274✔
121
    }
122

123
    /**
124
     * @return PropertyInterface[]
125
     */
126
    public function getProperties(): array
127
    {
128
        $hasSchemaDependencyValidator = static function (PropertyInterface $property): bool {
1,771✔
129
            foreach ($property->getValidators() as $validator) {
902✔
130
                if ($validator->getValidator() instanceof SchemaDependencyValidator) {
742✔
131
                    return true;
53✔
132
                }
133
            }
134

135
            return false;
902✔
136
        };
1,771✔
137

138
        // order the properties to make sure properties with a SchemaDependencyValidator are validated at the beginning
139
        // of the validation process for correct exception order of the messages
140
        usort(
1,771✔
141
            $this->properties,
1,771✔
142
            static function (
1,771✔
143
                PropertyInterface $property,
1,771✔
144
                PropertyInterface $comparedProperty
1,771✔
145
            ) use ($hasSchemaDependencyValidator): int {
1,771✔
146
                $propertyHasSchemaDependencyValidator = $hasSchemaDependencyValidator($property);
902✔
147
                $comparedPropertyHasSchemaDependencyValidator = $hasSchemaDependencyValidator($comparedProperty);
902✔
148

149
                if ($propertyHasSchemaDependencyValidator === $comparedPropertyHasSchemaDependencyValidator) {
902✔
150
                    return 0;
902✔
151
                }
152

153
                return ($propertyHasSchemaDependencyValidator < $comparedPropertyHasSchemaDependencyValidator) ? 1 : -1;
53✔
154
            }
1,771✔
155
        );
1,771✔
156

157
        return $this->properties;
1,771✔
158
    }
159

160
    /**
161
     * @param PropertyInterface $property
162
     *
163
     * @return $this
164
     */
165
    public function addProperty(PropertyInterface $property): self
166
    {
167
        if (!isset($this->properties[$property->getName()])) {
1,746✔
168
            $this->properties[$property->getName()] = $property;
1,746✔
169

170
            $property->onResolve(function (): void {
1,746✔
171
                if (++$this->resolvedProperties === count($this->properties)) {
1,706✔
172
                    foreach ($this->onAllPropertiesResolvedCallbacks as $callback) {
1,706✔
173
                        $callback();
×
174

175
                        $this->onAllPropertiesResolvedCallbacks = [];
×
176
                    }
177
                }
178
            });
1,746✔
179
        } else {
180
            // TODO tests:
181
            // testConditionalObjectProperty
182
            // testInvalidConditionalObjectPropertyThrowsAnException
183
            // testInvalidValuesForMultipleValuesInCompositionThrowsAnException
184
          //  throw new SchemaException("Duplicate attribute name {$property->getName()}");
185
        }
186

187
        return $this;
1,746✔
188
    }
189

190
    /**
191
     * @return PropertyValidatorInterface[]
192
     */
193
    public function getBaseValidators(): array
194
    {
195
        return $this->baseValidators;
1,771✔
196
    }
197

198
    /**
199
     * Get the keys of all composition base validators
200
     *
201
     * @return array
202
     */
203
    public function getCompositionValidatorKeys(): array
204
    {
205
        $keys = [];
1,769✔
206

207
        foreach ($this->baseValidators as $key => $validator) {
1,769✔
208
            if (is_a($validator, AbstractComposedPropertyValidator::class)) {
475✔
209
                $keys[] = $key;
187✔
210
            }
211
        }
212

213
        return $keys;
1,769✔
214
    }
215

216
    /**
217
     * @param PropertyValidatorInterface $baseValidator
218
     *
219
     * @return $this
220
     */
221
    public function addBaseValidator(PropertyValidatorInterface $baseValidator): self
222
    {
223
        $this->baseValidators[] = $baseValidator;
478✔
224

225
        return $this;
478✔
226
    }
227

228
    /**
229
     * @return SchemaDefinitionDictionary
230
     */
231
    public function getSchemaDictionary(): SchemaDefinitionDictionary
232
    {
233
        return $this->schemaDefinitionDictionary;
1,830✔
234
    }
235

236
    /**
237
     * Add a class to the schema which is required
238
     *
239
     * @param string $fqcn
240
     *
241
     * @return $this
242
     */
243
    public function addUsedClass(string $fqcn): self
244
    {
245
        $this->usedClasses[] = trim($fqcn, '\\');
1,838✔
246

247
        return $this;
1,838✔
248
    }
249

250
    /**
251
     * @param SchemaNamespaceTransferDecorator $decorator
252
     *
253
     * @return $this
254
     */
255
    public function addNamespaceTransferDecorator(SchemaNamespaceTransferDecorator $decorator): self
256
    {
257
        $this->namespaceTransferDecorators[] = $decorator;
791✔
258

259
        return $this;
791✔
260
    }
261

262
    /**
263
     * @param Schema[] $visitedSchema
264
     *
265
     * @return string[]
266
     */
267
    public function getUsedClasses(array $visitedSchema = []): array
268
    {
269
        $usedClasses = $this->usedClasses;
1,769✔
270

271
        foreach ($this->namespaceTransferDecorators as $decorator) {
1,769✔
272
            $usedClasses = array_merge($usedClasses, $decorator->resolve(array_merge($visitedSchema, [$this])));
789✔
273
        }
274

275
        return $usedClasses;
1,769✔
276
    }
277

278
    /**
279
     * @param string $methodKey An unique key in the scope of the schema to identify the method
280
     * @param MethodInterface $method
281
     *
282
     * @return $this
283
     */
284
    public function addMethod(string $methodKey, MethodInterface $method): self
285
    {
286
        $this->methods[$methodKey] = $method;
913✔
287

288
        return $this;
913✔
289
    }
290

291
    /**
292
     * @return MethodInterface[]
293
     */
294
    public function getMethods(): array
295
    {
296
        return $this->methods;
1,769✔
297
    }
298

299
    public function hasMethod(string $methodKey): bool
300
    {
301
        return isset($this->methods[$methodKey]);
656✔
302
    }
303

304
    /**
305
     * @return string[]
306
     */
307
    public function getTraits(): array
308
    {
309
        return $this->traits;
1,769✔
310
    }
311

312
    /**
313
     * @param string $trait
314
     *
315
     * @return Schema
316
     */
317
    public function addTrait(string $trait): self
318
    {
319
        $this->traits[] = $trait;
37✔
320
        $this->addUsedClass($trait);
37✔
321

322
        return $this;
37✔
323
    }
324

325
    /**
326
     * @return string[]
327
     */
328
    public function getInterfaces(): array
329
    {
330
        return $this->interfaces;
1,769✔
331
    }
332

333
    /**
334
     * @param string $interface
335
     *
336
     * @return Schema
337
     */
338
    public function addInterface(string $interface): self
339
    {
340
        $this->interfaces[] = $interface;
1,838✔
341
        $this->addUsedClass($interface);
1,838✔
342

343
        return $this;
1,838✔
344
    }
345

346
    /**
347
     * Add an additional schema hook
348
     *
349
     * @param SchemaHookInterface $schemaHook
350
     *
351
     * @return $this
352
     */
353
    public function addSchemaHook(SchemaHookInterface $schemaHook): self
354
    {
355
        $this->schemaHooks[] = $schemaHook;
1,770✔
356

357
        return $this;
1,770✔
358
    }
359

360
    /**
361
     * @return SchemaHookInterface[]
362
     */
363
    public function getSchemaHooks(): array
364
    {
365
        return $this->schemaHooks;
1,769✔
366
    }
367

368
    /**
369
     * @return bool
370
     */
371
    public function isInitialClass(): bool
372
    {
373
        return $this->initialClass;
×
374
    }
375
}
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

© 2025 Coveralls, Inc