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

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

11 May 2026 10:16PM UTC coverage: 98.554% (+0.003%) from 98.551%
25700642152

Pull #126

github

Enno Woortmann
Add content validator registry for contentMediaType/contentEncoding (Draft 7 Phase 5)

Introduces an opt-in registry on GeneratorConfiguration that lets users register
ContentValidatorInterface implementations keyed by media type and/or encoding.
At generation time the most specific matching validator is embedded into the
generated model; at runtime it is called against the raw string before the
MediaString wrapper is applied. Validator failures throw a ContentException whose
getPrevious() carries the original error for programmatic inspection.

addContentValidator() accepts null (wildcard), string, or string[] for each
dimension; arrays expand via Cartesian product so a single call can cover
multiple media types or encodings at once.
Pull Request #126: Json schema draft7

244 of 252 new or added lines in 13 files covered. (96.83%)

4 existing lines in 4 files now uncovered.

4770 of 4840 relevant lines covered (98.55%)

643.67 hits per line

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

99.38
/src/SchemaProcessor/PostProcessor/Internal/SerializationPostProcessor.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace PHPModelGenerator\SchemaProcessor\PostProcessor\Internal;
6

7
use JsonSerializable;
8
use PHPModelGenerator\Filter\TransformingFilterInterface;
9
use PHPModelGenerator\Interfaces\SerializationInterface;
10
use PHPModelGenerator\Model\GeneratorConfiguration;
11
use PHPModelGenerator\Model\Property\Property;
12
use PHPModelGenerator\Model\Property\PropertyInterface;
13
use PHPModelGenerator\Model\Property\PropertyType;
14
use PHPModelGenerator\Model\Schema;
15
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
16
use PHPModelGenerator\Model\Validator\AdditionalPropertiesValidator;
17
use PHPModelGenerator\Model\Validator\FilterValidator;
18
use PHPModelGenerator\Model\Validator\PatternPropertiesValidator;
19
use PHPModelGenerator\SchemaProcessor\Hook\SchemaHookResolver;
20
use PHPModelGenerator\SchemaProcessor\Hook\SerializationHookInterface;
21
use PHPModelGenerator\SchemaProcessor\PostProcessor\PostProcessor;
22
use PHPModelGenerator\SchemaProcessor\PostProcessor\RenderedMethod;
23
use PHPModelGenerator\Traits\SerializableTrait;
24

25
/**
26
 * Class SerializationPostProcessor
27
 *
28
 * @package PHPModelGenerator\SchemaProcessor\PostProcessor
29
 */
30
class SerializationPostProcessor extends PostProcessor
31
{
32
    /**
33
     * Add serialization support to the provided schema
34
     */
35
    public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void
60 ✔
36
    {
37
        $schema
60 ✔
38
            ->addTrait(SerializableTrait::class)
60 ✔
39
            ->addInterface(JsonSerializable::class)
60 ✔
40
            ->addInterface(SerializationInterface::class);
60 ✔
41

42
        $this->addSerializeFunctionsForTransformingFilters($schema, $generatorConfiguration);
60 ✔
43
        $this->addSerializationHookMethod($schema, $generatorConfiguration);
60 ✔
44
        $this->addSkipNotProvidedPropertiesMap($schema, $generatorConfiguration);
60 ✔
45
        $this->addWriteOnlyExclusion($schema, $generatorConfiguration);
60 ✔
46

47
        $this->addPatternPropertiesSerialization($schema, $generatorConfiguration);
60 ✔
48

49
        $json = $schema->getJsonSchema()->getJson();
60 ✔
50
        if (isset($json['additionalProperties']) && $json['additionalProperties'] !== false) {
60 ✔
51
            $this->addAdditionalPropertiesSerialization($schema, $generatorConfiguration);
11 ✔
52
        }
53
    }
54

55
    /**
56
     * Each transforming filter must provide a method to serialize the value. Add a method to the schema to call the
57
     * serialization for each property with a transforming filter
58
     */
59
    private function addSerializeFunctionsForTransformingFilters(
60 ✔
60
        Schema $schema,
61
        GeneratorConfiguration $generatorConfiguration,
62
    ): void {
63
        foreach ($schema->getProperties() as $property) {
60 ✔
64
            foreach ($property->getValidators() as $validator) {
59 ✔
65
                $validator = $validator->getValidator();
56 ✔
66

67
                if (
68
                    $validator instanceof FilterValidator &&
56 ✔
69
                    $validator->getFilter() instanceof TransformingFilterInterface
56 ✔
70
                ) {
71
                    [$serializerClass, $serializerMethod] = $validator->getFilter()->getSerializer();
24 ✔
72

73
                    $schema->addMethod(
24 ✔
74
                        "serialize{$property->getAttribute()}",
24 ✔
75
                        new RenderedMethod(
24 ✔
76
                            $schema,
24 ✔
77
                            $generatorConfiguration,
24 ✔
78
                            join(
24 ✔
79
                                DIRECTORY_SEPARATOR,
24 ✔
80
                                ['Serialization', 'TransformingFilterSerializer.phptpl'],
24 ✔
81
                            ),
24 ✔
82
                            [
24 ✔
83
                                'property' => $property,
24 ✔
84
                                'serializerClass' => $serializerClass,
24 ✔
85
                                'serializerMethod' => $serializerMethod,
24 ✔
86
                                'serializerOptions' => var_export($validator->getFilterOptions(), true),
24 ✔
87
                            ],
24 ✔
88
                        )
24 ✔
89
                    );
24 ✔
90
                }
91
            }
92
        }
93

94
        foreach ($schema->getBaseValidators() as $validator) {
60 ✔
95
            if ($validator instanceof PatternPropertiesValidator) {
23 ✔
96
                foreach ($validator->getValidationProperty()->getValidators() as $patternPropertyValidator) {
6 ✔
97
                    $filterValidator = $patternPropertyValidator->getValidator();
6 ✔
98

99
                    if (
100
                        $filterValidator instanceof FilterValidator &&
6 ✔
101
                        $filterValidator->getFilter() instanceof TransformingFilterInterface
6 ✔
102
                    ) {
103
                        [$serializerClass, $serializerMethod] = $filterValidator->getFilter()->getSerializer();
2 ✔
104

105
                        $schema->addMethod(
2 ✔
106
                            "serialize{$validator->getKey()}",
2 ✔
107
                            new RenderedMethod(
2 ✔
108
                                $schema,
2 ✔
109
                                $generatorConfiguration,
2 ✔
110
                                join(
2 ✔
111
                                    DIRECTORY_SEPARATOR,
2 ✔
112
                                    ['Serialization', 'PatternPropertyTransformingFilterSerializer.phptpl'],
2 ✔
113
                                ),
2 ✔
114
                                [
2 ✔
115
                                    'key' => $validator->getKey(),
2 ✔
116
                                    'serializerClass' => $serializerClass,
2 ✔
117
                                    'serializerMethod' => $serializerMethod,
2 ✔
118
                                    'serializerOptions' => var_export($filterValidator->getFilterOptions(), true),
2 ✔
119
                                ],
2 ✔
120
                            )
2 ✔
121
                        );
2 ✔
122
                    }
123
                }
124
            }
125
        }
126
    }
127

128
    private function addSerializationHookMethod(Schema $schema, GeneratorConfiguration $generatorConfiguration): void
60 ✔
129
    {
130
        $schema->addMethod(
60 ✔
131
            'resolveSerializationHook',
60 ✔
132
            new RenderedMethod(
60 ✔
133
                $schema,
60 ✔
134
                $generatorConfiguration,
60 ✔
135
                join(DIRECTORY_SEPARATOR, ['Serialization', 'SerializationHook.phptpl']),
60 ✔
136
                [
60 ✔
137
                    'schemaHookResolver' => new SchemaHookResolver($schema),
60 ✔
138
                ],
60 ✔
139
            )
60 ✔
140
        );
60 ✔
141
    }
142

143
    /**
144
     * Adds code to merge serialized pattern properties into the serialization result
145
     */
146
    private function addPatternPropertiesSerialization(
60 ✔
147
        Schema $schema,
148
        GeneratorConfiguration $generatorConfiguration,
149
    ): void {
150
        if (!isset($schema->getJsonSchema()->getJson()['patternProperties'])) {
60 ✔
151
            return;
54 ✔
152
        }
153

154
        $schema->addMethod(
6 ✔
155
            'serializePatternProperties',
6 ✔
156
            new RenderedMethod($schema, $generatorConfiguration, 'Serialization/PatternPropertiesSerializer.phptpl'),
6 ✔
157
        );
6 ✔
158

159
        $schema->addSchemaHook(
6 ✔
160
            new class () implements SerializationHookInterface {
6 ✔
161
                public function getCode(): string
162
                {
163
                    return '$data += $this->serializePatternProperties($depth, $except);';
6 ✔
164
                }
165
            },
6 ✔
166
        );
6 ✔
167
    }
168

169
    /**
170
     * Adds a custom serialization function to the schema to merge all additional properties into the serialization
171
     * result on serializations
172
     */
173
    public function addAdditionalPropertiesSerialization(
15 ✔
174
        Schema $schema,
175
        GeneratorConfiguration $generatorConfiguration,
176
    ): void {
177
        $validationProperty = null;
15 ✔
178
        foreach ($schema->getBaseValidators() as $validator) {
15 ✔
179
            if (is_a($validator, AdditionalPropertiesValidator::class)) {
15 ✔
180
                $validationProperty = $validator->getValidationProperty();
11 ✔
181
            }
182
        }
183

184
        $transformingFilterValidator = null;
15 ✔
185

186
        if ($validationProperty) {
15 ✔
187
            foreach ($validationProperty->getValidators() as $validator) {
11 ✔
188
                $validator = $validator->getValidator();
11 ✔
189

190
                if (
191
                    $validator instanceof FilterValidator &&
11 ✔
192
                    $validator->getFilter() instanceof TransformingFilterInterface
11 ✔
193
                ) {
194
                    $transformingFilterValidator = $validator;
3 ✔
195
                    [$serializerClass, $serializerMethod] = $validator->getFilter()->getSerializer();
3 ✔
196
                }
197
            }
198
        }
199

200
        $schema->addMethod(
15 ✔
201
            'serializeAdditionalProperties',
15 ✔
202
            new RenderedMethod(
15 ✔
203
                $schema,
15 ✔
204
                $generatorConfiguration,
15 ✔
205
                'Serialization/AdditionalPropertiesSerializer.phptpl',
15 ✔
206
                [
15 ✔
207
                    'serializerClass' => $serializerClass ?? null,
15 ✔
208
                    'serializerMethod' => $serializerMethod ?? null,
15 ✔
209
                    'serializerOptions' => $transformingFilterValidator
15 ✔
210
                        ? var_export($transformingFilterValidator->getFilterOptions(), true)
3 ✔
UNCOV
211
                        : [],
×
212
                ],
15 ✔
213
            )
15 ✔
214
        );
15 ✔
215

216
        $schema->addSchemaHook(
15 ✔
217
            new class () implements SerializationHookInterface {
15 ✔
218
                public function getCode(): string
219
                {
220
                    return '$data = array_merge($this->serializeAdditionalProperties($depth, $except), $data);';
15 ✔
221
                }
222
            },
15 ✔
223
        );
15 ✔
224
    }
225

226
    private function addWriteOnlyExclusion(
60 ✔
227
        Schema $schema,
228
        GeneratorConfiguration $generatorConfiguration,
229
    ): void {
230
        $writeOnlyAttributes = array_map(
60 ✔
231
            static fn(PropertyInterface $property): string => $property->getAttribute(true),
60 ✔
232
            array_filter(
60 ✔
233
                $schema->getProperties(),
60 ✔
234
                static fn(PropertyInterface $property): bool => $property->isWriteOnly(),
60 ✔
235
            ),
60 ✔
236
        );
60 ✔
237

238
        if (!$writeOnlyAttributes) {
60 ✔
239
            return;
58 ✔
240
        }
241

242
        $keysExport = var_export(array_values($writeOnlyAttributes), true);
2 ✔
243

244
        $schema->addSchemaHook(
2 ✔
245
            new class ($keysExport) implements SerializationHookInterface
2 ✔
246
            {
2 ✔
247
                public function __construct(private readonly string $keysExport)
248
                {}
2 ✔
249

250
                public function getCode(): string
251
                {
252
                    return sprintf(
2 ✔
253
                        'foreach (%s as $_writeOnlyKey) { unset($data[$_writeOnlyKey]); }',
2 ✔
254
                        $this->keysExport,
2 ✔
255
                    );
2 ✔
256
                }
257
            },
2 ✔
258
        );
2 ✔
259
    }
260

261
    private function addSkipNotProvidedPropertiesMap(
60 ✔
262
        Schema $schema,
263
        GeneratorConfiguration $generatorConfiguration,
264
    ): void {
265
        if ($generatorConfiguration->isImplicitNullAllowed()) {
60 ✔
266
            return;
45 ✔
267
        }
268

269
        $skipNotProvidedValues = array_map(
15 ✔
270
            static fn(PropertyInterface $property): string => $property->getName(),
15 ✔
271
            array_filter(
15 ✔
272
                $schema->getProperties(),
15 ✔
273
                static fn(PropertyInterface $property): bool =>
15 ✔
274
                    !$property->isRequired() && !$property->getDefaultValue(),
15 ✔
275
            )
15 ✔
276
        );
15 ✔
277

278
        $schema->addProperty(
15 ✔
279
            (new Property(
15 ✔
280
                'skipNotProvidedPropertiesMap',
15 ✔
281
                new PropertyType('array'),
15 ✔
282
                new JsonSchema(__FILE__, []),
15 ✔
283
                'Values which might be skipped for serialization if not provided',
15 ✔
284
            ))
15 ✔
285
                ->setDefaultValue($skipNotProvidedValues)
15 ✔
286
                ->setInternal(true),
15 ✔
287
        );
15 ✔
288
    }
289
}
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