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

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

18 Jul 2026 09:07PM UTC coverage: 98.744% (-0.05%) from 98.79%
29661031835

push

github

web-flow
Merge pull request #165 from wol-soft/claude/validation-error-messages-c73hox

Validation error message readability (issue #131): jsonPointer + real array-item names

72 of 76 new or added lines in 7 files covered. (94.74%)

7078 of 7168 relevant lines covered (98.74%)

552.49 hits per line

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

66.96
/src/Model/Property/PropertyProxy.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace PHPModelGenerator\Model\Property;
6

7
use PHPModelGenerator\Attributes\JsonPointer;
8
use PHPModelGenerator\Attributes\SchemaName;
9
use PHPModelGenerator\Exception\SchemaException;
10
use PHPModelGenerator\Model\Attributes\PhpAttribute;
11
use PHPModelGenerator\Model\GeneratorConfiguration;
12
use PHPModelGenerator\Model\Schema;
13
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
14
use PHPModelGenerator\Model\SchemaDefinition\ResolvedDefinitionsCollection;
15
use PHPModelGenerator\Model\Validator\PropertyValidatorInterface;
16
use PHPModelGenerator\PropertyProcessor\Decorator\Property\PropertyDecoratorInterface;
17
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\TypeHintDecoratorInterface;
18

19
/**
20
 * Class PropertyProxy
21
 *
22
 * @package PHPModelGenerator\Model
23
 */
24
class PropertyProxy extends AbstractProperty
25
{
26
    private ?JsonSchema $overrideJsonSchema = null;
27
    private ?PhpAttribute $overrideJsonPointer = null;
28
    private bool $isProxyArrayItem = false;
29

30
    /**
31
     * PropertyProxy constructor.
32
     *
33
     * @param string $name The name must be provided separately as the name is not bound to the structure of a
34
     * referenced schema. Consequently, two properties with different names can refer an identical schema utilizing the
35
     * PropertyProxy. By providing a name to each of the proxies the resulting properties will get the correct names.
36
     *
37
     * @throws SchemaException
38
     */
39
    public function __construct(
993✔
40
        string $name,
41
        JsonSchema $jsonSchema,
42
        protected ResolvedDefinitionsCollection $definitionsCollection,
43
        protected string $key,
44
    ) {
45
        parent::__construct($name, $jsonSchema);
993✔
46
    }
47

48
    /**
49
     * Get the property out of the resolved definitions collection to proxy function calls
50
     */
51
    protected function getProperty(): PropertyInterface
993✔
52
    {
53
        return $this->definitionsCollection->offsetGet($this->key);
993✔
54
    }
55

56
    /**
57
     * @inheritdoc
58
     */
59
    public function getType(bool $outputType = false): ?PropertyType
235✔
60
    {
61
        return $this->getProperty()->getType($outputType);
235✔
62
    }
63

64
    /**
65
     * @inheritdoc
66
     */
67
    public function setType(
5✔
68
        ?PropertyType $type = null,
69
        ?PropertyType $outputType = null,
70
        bool $reset = false,
71
    ): PropertyInterface {
72
        $this->getProperty()->setType($type, $outputType, $reset);
5✔
73

74
        return $this;
5✔
75
    }
76

77
    /**
78
     * @inheritdoc
79
     */
80
    public function getTypeHint(bool $outputType = false, array $skipDecorators = []): string
451✔
81
    {
82
        return $this->getProperty()->getTypeHint($outputType, $skipDecorators);
451✔
83
    }
84

85
    /**
86
     * @inheritdoc
87
     */
88
    public function addTypeHintDecorator(TypeHintDecoratorInterface $typeHintDecorator): PropertyInterface
1✔
89
    {
90
        $this->getProperty()->addTypeHintDecorator($typeHintDecorator);
1✔
91

92
        return $this;
1✔
93
    }
94

95
    /**
96
     * @inheritdoc
97
     */
98
    public function getDescription(): string
20✔
99
    {
100
        return $this->getProperty()->getDescription();
20✔
101
    }
102

103
    public function getComment(): ?string
20✔
104
    {
105
        return $this->getProperty()->getComment();
20✔
106
    }
107

108
    public function setComment(string $comment): PropertyInterface
×
109
    {
110
        $this->getProperty()->setComment($comment);
×
111

112
        return $this;
×
113
    }
114

115
    public function getExamples(): array
20✔
116
    {
117
        return $this->getProperty()->getExamples();
20✔
118
    }
119

120
    public function setExamples(array $examples): PropertyInterface
×
121
    {
122
        $this->getProperty()->setExamples($examples);
×
123

124
        return $this;
×
125
    }
126

127
    /**
128
     * @inheritdoc
129
     */
130
    public function addValidator(
44✔
131
        PropertyValidatorInterface $validator,
132
        int $priority = 99,
133
        ?string $sourceKey = null,
134
    ): PropertyInterface {
135
        $this->getProperty()->addValidator($validator, $priority, $sourceKey);
44✔
136

137
        return $this;
44✔
138
    }
139

140
    /**
141
     * @inheritdoc
142
     */
143
    public function getValidators(): array
25✔
144
    {
145
        return $this->getProperty()->getValidators();
25✔
146
    }
147

148
    /**
149
     * @inheritdoc
150
     */
151
    public function filterValidators(callable $filter): PropertyInterface
975✔
152
    {
153
        $this->getProperty()->filterValidators($filter);
975✔
154

155
        return $this;
975✔
156
    }
157

158
    /**
159
     * @inheritdoc
160
     */
161
    public function getOrderedValidators(): array
963✔
162
    {
163
        return array_map(
963✔
164
            fn(PropertyValidatorInterface $propertyValidator): PropertyValidatorInterface =>
963✔
165
                $propertyValidator->withProperty($this),
963✔
166
            $this->getProperty()->getOrderedValidators(),
963✔
167
        );
963✔
168
    }
169

170
    /**
171
     * @inheritdoc
172
     */
173
    public function addDecorator(PropertyDecoratorInterface $decorator): PropertyInterface
×
174
    {
175
        $this->getProperty()->addDecorator($decorator);
×
176

177
        return $this;
×
178
    }
179

180
    /**
181
     * @inheritdoc
182
     */
183
    public function filterDecorators(callable $filter): PropertyInterface
×
184
    {
185
        $this->getProperty()->filterDecorators($filter);
×
186

187
        return $this;
×
188
    }
189

190
    /**
191
     * @inheritdoc
192
     */
193
    public function resolveDecorator(string $input, bool $nestedProperty): string
721✔
194
    {
195
        foreach ($this->getProperty()->getDecorators() as $decorator) {
721✔
196
            $input = $decorator->decorate($input, $this, $nestedProperty);
721✔
197
        }
198

199
        return $input;
721✔
200
    }
201

202
    /**
203
     * @inheritdoc
204
     */
205
    public function getDecorators(): array
963✔
206
    {
207
        return $this->getProperty()->getDecorators();
963✔
208
    }
209

210
    /**
211
     * @inheritdoc
212
     */
213
    public function setRequired(bool $isPropertyRequired): PropertyInterface
×
214
    {
215
        $this->getProperty()->setRequired($isPropertyRequired);
×
216

217
        return $this;
×
218
    }
219

220
    /**
221
     * @inheritdoc
222
     */
223
    public function isRequired(): bool
218✔
224
    {
225
        return $this->isProxyArrayItem || $this->getProperty()->isRequired();
218✔
226
    }
227

228
    /**
229
     * @inheritdoc
230
     *
231
     * Stores the array-item flag locally rather than delegating: the same $ref-resolved
232
     * property can be reused as a plain property in one place and as an array item in
233
     * another (each usage gets its own PropertyProxy), so the flag must not be shared via
234
     * the underlying property.
235
     */
236
    public function setArrayItem(bool $isArrayItem): PropertyInterface
121✔
237
    {
238
        $this->isProxyArrayItem = $isArrayItem;
121✔
239

240
        return $this;
121✔
241
    }
242

243
    /**
244
     * @inheritdoc
245
     */
NEW
246
    public function isArrayItem(): bool
×
247
    {
NEW
248
        return $this->isProxyArrayItem;
×
249
    }
250

251
    /**
252
     * @inheritdoc
253
     */
254
    public function setReadOnly(bool $isPropertyReadOnly): PropertyInterface
1✔
255
    {
256
        $this->getProperty()->setReadOnly($isPropertyReadOnly);
1✔
257

258
        return $this;
1✔
259
    }
260

261
    /**
262
     * @inheritdoc
263
     */
264
    public function isReadOnly(): bool
20✔
265
    {
266
        return $this->getProperty()->isReadOnly();
20✔
267
    }
268

269
    /**
270
     * @inheritdoc
271
     */
272
    public function setWriteOnly(bool $isPropertyWriteOnly): PropertyInterface
×
273
    {
274
        $this->getProperty()->setWriteOnly($isPropertyWriteOnly);
×
275

276
        return $this;
×
277
    }
278

279
    /**
280
     * @inheritdoc
281
     */
282
    public function isWriteOnly(): bool
20✔
283
    {
284
        return $this->getProperty()->isWriteOnly();
20✔
285
    }
286

287
    /**
288
     * @inheritdoc
289
     */
290
    public function setDefaultValue($defaultValue, bool $raw = false): PropertyInterface
×
291
    {
292
        $this->getProperty()->setDefaultValue($defaultValue, $raw);
×
293

294
        return $this;
×
295
    }
296

297
    /**
298
     * @inheritdoc
299
     */
300
    public function getDefaultValue(): ?string
24✔
301
    {
302
        return $this->getProperty()->getDefaultValue();
24✔
303
    }
304

305
    /**
306
     * @inheritdoc
307
     */
308
    public function setNestedSchema(Schema $schema): PropertyInterface
×
309
    {
310
        $this->getProperty()->setNestedSchema($schema);
×
311

312
        return $this;
×
313
    }
314

315
    /**
316
     * @inheritdoc
317
     */
318
    public function getNestedSchema(): ?Schema
897✔
319
    {
320
        return $this->getProperty()->getNestedSchema();
897✔
321
    }
322

323
    /**
324
     * @inheritdoc
325
     */
326
    public function getJsonSchema(): JsonSchema
5✔
327
    {
328
        return $this->overrideJsonSchema ?? $this->getProperty()->getJsonSchema();
5✔
329
    }
330

331
    /**
332
     * @inheritdoc
333
     *
334
     * Stores a local override rather than delegating to the underlying property, preventing
335
     * mutation of a shared $ref-resolved property when only this proxy's schema must change.
336
     */
337
    public function setJsonSchema(JsonSchema $jsonSchema): static
×
338
    {
339
        $this->overrideJsonSchema = $jsonSchema;
×
340

341
        return $this;
×
342
    }
343

344
    /**
345
     * @inheritdoc
346
     */
347
    public function setInternal(bool $isPropertyInternal): PropertyInterface
×
348
    {
349
        $this->getProperty()->setInternal($isPropertyInternal);
×
350

351
        return $this;
×
352
    }
353

354
    /**
355
     * @inheritdoc
356
     */
357
    public function isInternal(): bool
20✔
358
    {
359
        return $this->getProperty()->isInternal();
20✔
360
    }
361

362
    /**
363
     * @inheritdoc
364
     */
365
    public function filterAttributes(callable $filter): static
×
366
    {
367
        $this->getProperty()->filterAttributes($filter);
×
368

369
        return $this;
×
370
    }
371

372
    /**
373
     * @inheritdoc
374
     */
375
    public function addAttribute(
×
376
        PhpAttribute $attribute,
377
        ?GeneratorConfiguration $generatorConfiguration = null,
378
        ?int $enablementFlag = null,
379
    ): static {
380
        $this->getProperty()->addAttribute($attribute);
×
381

382
        return $this;
×
383
    }
384

385
    /**
386
     * Store the pointer attribute locally so this proxy can show a different JsonPointer from
387
     * the shared underlying property (each reference site has its own pointer).
388
     */
389
    public function overrideJsonPointer(PhpAttribute $attribute): static
121✔
390
    {
391
        $this->overrideJsonPointer = $attribute;
121✔
392

393
        return $this;
121✔
394
    }
395

396
    /**
397
     * @inheritdoc
398
     *
399
     * Replaces the SchemaName attribute from the underlying shared property with one that
400
     * carries the proxy's own name. Two proxies sharing the same $ref definition would
401
     * otherwise both report the first property's name via the shared underlying attribute.
402
     *
403
     * When a JsonPointer override is set, all existing JsonPointer attributes (there may be
404
     * multiple when the underlying property was synthesised from composition branches) are
405
     * removed and replaced with a single attribute pointing to the reference site.
406
     */
407
    public function getAttributes(): array
20✔
408
    {
409
        $attributes = array_map(
20✔
410
            fn(PhpAttribute $attribute): PhpAttribute => $attribute->getFqcn() === SchemaName::class
20✔
411
                ? new PhpAttribute(SchemaName::class, [$this->name])
20✔
412
                : $attribute,
20✔
413
            $this->getProperty()->getAttributes(),
20✔
414
        );
20✔
415

416
        if ($this->overrideJsonPointer !== null) {
20✔
417
            $attributes = array_values(array_filter(
20✔
418
                $attributes,
20✔
419
                static fn(PhpAttribute $attribute): bool => $attribute->getFqcn() !== JsonPointer::class,
20✔
420
            ));
20✔
421
            $attributes[] = $this->overrideJsonPointer;
20✔
422
        }
423

424
        return $attributes;
20✔
425
    }
426
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc