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

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

20 Mar 2026 12:32AM UTC coverage: 98.663% (+0.005%) from 98.658%
23323674448

Pull #118

github

web-flow
Merge ef6187e70 into f7c94aa3c
Pull Request #118: Upgrade to PHP 8.4 minimum and PHPUnit 13

431 of 436 new or added lines in 54 files covered. (98.85%)

22 existing lines in 8 files now uncovered.

3690 of 3740 relevant lines covered (98.66%)

556.09 hits per line

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

98.81
/src/PropertyProcessor/Property/MultiTypeProcessor.php
1
<?php
2

3
declare(strict_types = 1);
4

5
namespace PHPModelGenerator\PropertyProcessor\Property;
6

7
use PHPModelGenerator\Exception\SchemaException;
8
use PHPModelGenerator\Model\Property\PropertyInterface;
9
use PHPModelGenerator\Model\Property\PropertyType;
10
use PHPModelGenerator\Model\Schema;
11
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
12
use PHPModelGenerator\Model\Validator\MultiTypeCheckValidator;
13
use PHPModelGenerator\Model\Validator\TypeCheckInterface;
14
use PHPModelGenerator\PropertyProcessor\Decorator\Property\PropertyTransferDecorator;
15
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\TypeHintDecorator;
16
use PHPModelGenerator\PropertyProcessor\PropertyMetaDataCollection;
17
use PHPModelGenerator\PropertyProcessor\PropertyProcessorFactory;
18
use PHPModelGenerator\PropertyProcessor\PropertyProcessorInterface;
19
use PHPModelGenerator\SchemaProcessor\SchemaProcessor;
20
use ReflectionException;
21

22
/**
23
 * Class MultiTypePropertyProcessor
24
 *
25
 * @package PHPModelGenerator\PropertyProcessor\Property
26
 */
27
class MultiTypeProcessor extends AbstractValueProcessor
28
{
29
    /** @var PropertyProcessorInterface[] */
30
    protected $propertyProcessors = [];
31
    /** @var string[] */
32
    protected $allowedPropertyTypes = [];
33
    /** @var string[] */
34
    protected $checks = [];
35

36
    /**
37
     * MultiTypePropertyProcessor constructor.
38
     *
39
     * @throws SchemaException
40
     */
41
    public function __construct(
72✔
42
        PropertyProcessorFactory $propertyProcessorFactory,
43
        array $types,
44
        PropertyMetaDataCollection $propertyMetaDataCollection,
45
        SchemaProcessor $schemaProcessor,
46
        Schema $schema,
47
    ) {
48
        parent::__construct($propertyMetaDataCollection, $schemaProcessor, $schema);
72✔
49

50
        foreach ($types as $type) {
72✔
51
            $this->propertyProcessors[$type] = $propertyProcessorFactory->getProcessor(
72✔
52
                $type,
72✔
53
                $propertyMetaDataCollection,
72✔
54
                $schemaProcessor,
72✔
55
                $schema,
72✔
56
            );
72✔
57
        }
58
    }
59

60
    /**
61
     * Process a property
62
     *
63
     * @param string $propertyName The name of the property
64
     * @param JsonSchema $propertySchema The schema of the property
65
     *
66
     * @throws SchemaException
67
     * @throws ReflectionException
68
     */
69
    public function process(string $propertyName, JsonSchema $propertySchema): PropertyInterface
72✔
70
    {
71
        $property = parent::process($propertyName, $propertySchema);
72✔
72

73
        $property->onResolve(function () use ($property, $propertyName, $propertySchema): void {
71✔
74
            foreach ($property->getValidators() as $validator) {
71✔
75
                $this->checks[] = $validator->getValidator()->getCheck();
17✔
76
            }
77

78
            $subProperties = $this->processSubProperties($propertyName, $propertySchema, $property);
71✔
79

80
            $processedSubProperties = 0;
68✔
81
            foreach ($subProperties as $subProperty) {
68✔
82
                $subProperty->onResolve(function () use ($property, $subProperties, &$processedSubProperties): void {
68✔
83
                    if (++$processedSubProperties === count($subProperties)) {
68✔
84
                        if (empty($this->allowedPropertyTypes)) {
68✔
UNCOV
85
                            return;
×
86
                        }
87

88
                        $property->addTypeHintDecorator(
68✔
89
                            new TypeHintDecorator(
68✔
90
                                array_map(
68✔
91
                                    static fn(PropertyInterface $subProperty): string => $subProperty->getTypeHint(),
68✔
92
                                    $subProperties,
68✔
93
                                )
68✔
94
                            ),
68✔
95
                        );
68✔
96

97
                        $property->addValidator(
68✔
98
                            new MultiTypeCheckValidator(
68✔
99
                                array_unique($this->allowedPropertyTypes),
68✔
100
                                $property,
68✔
101
                                $this->isImplicitNullAllowed($property),
68✔
102
                            ),
68✔
103
                            2,
68✔
104
                        );
68✔
105

106
                        // Set a union PropertyType so the native PHP type hint path can emit
107
                        // e.g. float|string|array instead of falling back to no hint at all.
108
                        // 'null' must be converted to nullable=true rather than kept as a type name,
109
                        // otherwise the render pipeline would emit string|null|null.
110
                        $hasNull = in_array('null', $this->allowedPropertyTypes, true);
68✔
111
                        $nonNullTypes = array_values(array_filter(
68✔
112
                            $this->allowedPropertyTypes,
68✔
113
                            fn(string $t): bool => $t !== 'null',
68✔
114
                        ));
68✔
115

116
                        if ($nonNullTypes) {
68✔
117
                            $propertyType = new PropertyType($nonNullTypes, $hasNull ? true : null);
68✔
118
                            $property->setType($propertyType, $propertyType);
68✔
119
                        }
120
                    }
121
                });
68✔
122
            }
123
        });
71✔
124

125
        return $property;
68✔
126
    }
127

128
    /**
129
     * Move validators from the $source property to the $destination property
130
     */
131
    protected function transferValidators(PropertyInterface $source, PropertyInterface $destination)
71✔
132
    {
133
        foreach ($source->getValidators() as $validatorContainer) {
71✔
134
            $validator = $validatorContainer->getValidator();
71✔
135

136
            // filter out type checks to create a single type check which covers all allowed types
137
            if ($validator instanceof TypeCheckInterface) {
71✔
138
                array_push($this->allowedPropertyTypes, ...$validator->getTypes());
71✔
139

140
                continue;
71✔
141
            }
142

143
            // remove duplicated checks like an isset check
144
            if (in_array($validator->getCheck(), $this->checks)) {
50✔
145
                continue;
17✔
146
            }
147

148
            $destination->addValidator($validator, $validatorContainer->getPriority());
35✔
149
            $this->checks[] = $validator->getCheck();
35✔
150
        }
151
    }
152

153
    /**
154
     * @return PropertyInterface[]
155
     *
156
     * @throws SchemaException
157
     */
158
    protected function processSubProperties(
71✔
159
        string $propertyName,
160
        JsonSchema $propertySchema,
161
        PropertyInterface $property,
162
    ): array {
163
        $defaultValue = null;
71✔
164
        $invalidDefaultValueException = null;
71✔
165
        $invalidDefaultValues = 0;
71✔
166
        $subProperties = [];
71✔
167
        $json = $propertySchema->getJson();
71✔
168

169
        if (isset($json['default'])) {
71✔
170
            $defaultValue = $json['default'];
5✔
171
            unset($json['default']);
5✔
172
        }
173

174
        foreach ($this->propertyProcessors as $type => $propertyProcessor) {
71✔
175
            $json['type'] = $type;
71✔
176

177
            $subProperty = $propertyProcessor->process($propertyName, $propertySchema->withJson($json));
71✔
178

179
            $subProperty->onResolve(function () use ($property, $subProperty): void {
71✔
180
                $this->transferValidators($subProperty, $property);
71✔
181

182
                if ($subProperty->getDecorators()) {
71✔
183
                    $property->addDecorator(new PropertyTransferDecorator($subProperty));
33✔
184
                }
185
            });
71✔
186

187
            if ($defaultValue !== null && $propertyProcessor instanceof AbstractTypedValueProcessor) {
71✔
188
                try {
189
                    $propertyProcessor->setDefaultValue($property, $defaultValue, $propertySchema);
5✔
190
                } catch (SchemaException $e) {
5✔
191
                    $invalidDefaultValues++;
5✔
192
                    $invalidDefaultValueException = $e;
5✔
193
                }
194
            }
195

196
            $subProperties[] = $subProperty;
71✔
197
        }
198

199
        if ($invalidDefaultValues === count($this->propertyProcessors)) {
70✔
200
            throw $invalidDefaultValueException;
2✔
201
        }
202

203
        return $subProperties;
68✔
204
    }
205
}
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