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

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

30 Jul 2026 09:16PM UTC coverage: 98.587% (-0.1%) from 98.735%
30582776386

Pull #166

github

claude
Align composition-error assertions with master's summary-only direct mode

The merge with master resolved the ComposedItem template conflict in favor of
master's structure (the extracted ComposedItemBody template), which does not
enumerate cleanly-validated branches in direct-exception mode. This drops the
per-branch "Valid" enumeration that this branch had added for direct mode.

Realign the affected direct-mode assertions to the actual summary-only output
(failing branches are still enumerated with their underlying reason; passing
branches are no longer listed, and the remaining failing branches renumber
sequentially). Also correct the impliedObjects doc example, which showed the
now-removed enumeration. Verified against pristine master: this matches
master's direct-mode composition-error format exactly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dm5LeHyWzk3JnMaQqSgXPy
Pull Request #166: Add characterization tests for issue #72 / PR #74 composition defects

194 of 206 new or added lines in 8 files covered. (94.17%)

2 existing lines in 2 files now uncovered.

7326 of 7431 relevant lines covered (98.59%)

577.82 hits per line

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

98.18
/src/Model/Validator/AbstractComposedPropertyValidator.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace PHPModelGenerator\Model\Validator;
6

7
use PHPModelGenerator\Model\Property\CompositionPropertyDecorator;
8
use PHPModelGenerator\SchemaProcessor\PostProcessor\RenderedMethod;
9
use PHPModelGenerator\Utils\RenderHelper;
10

11
/**
12
 * Class AbstractComposedPropertyValidator
13
 *
14
 * @package PHPModelGenerator\Model\Validator
15
 */
16
abstract class AbstractComposedPropertyValidator extends ExtractedMethodValidator
17
{
18
    /** @var string */
19
    protected $compositionProcessor;
20
    /** @var CompositionPropertyDecorator[] */
21
    protected $composedProperties;
22
    protected string $modifiedValuesMethod = '';
23

24
    public function getCompositionProcessor(): string
531✔
25
    {
26
        return $this->compositionProcessor;
531✔
27
    }
28

29
    /**
30
     * @return CompositionPropertyDecorator[]
31
     */
32
    public function getComposedProperties(): array
531✔
33
    {
34
        return $this->composedProperties;
531✔
35
    }
36

37
    protected function initModifiedValuesMethod(): void
990✔
38
    {
39
        $this->modifiedValuesMethod = '_getModifiedValues_' . substr(md5(spl_object_hash($this)), 0, 5);
990✔
40
    }
41

42
    /**
43
     * Returns true when at least one composition branch has a nested schema with declared
44
     * properties, meaning the modified-values helper method may produce non-empty results.
45
     */
46
    protected function hasNestedSchemaWithProperties(): bool
964✔
47
    {
48
        foreach ($this->composedProperties as $compositionProperty) {
964✔
49
            $nestedSchema = $compositionProperty->getNestedSchema();
945✔
50
            if ($nestedSchema !== null && !empty($nestedSchema->getProperties())) {
945✔
51
                return true;
619✔
52
            }
53
        }
54

55
        return false;
350✔
56
    }
57

58
    /**
59
     * Sets up the allBranchDefaultAttributeMap template variable and registers the
60
     * _getModifiedValues_* helper method on the schema scope. Properties that already carry
61
     * a root-level (unconditional) default in the parent schema are excluded from the map;
62
     * those defaults are applied via PHP field initializers and must not be reset by the
63
     * per-branch mechanism.
64
     *
65
     * Returns true when the helper method was registered (at least one branch has a nested
66
     * schema with properties), false otherwise.
67
     */
68
    protected function setupBranchDefaultHelpers(): bool
964✔
69
    {
70
        $hasNestedSchemaWithProperties = $this->hasNestedSchemaWithProperties();
964✔
71

72
        $this->templateValues['hasModifiedValuesMethod'] = $hasNestedSchemaWithProperties;
964✔
73

74
        if (!$hasNestedSchemaWithProperties) {
964✔
75
            $this->templateValues['allBranchDefaultAttributeMap'] = RenderHelper::varExportArray([]);
350✔
76

77
            return false;
350✔
78
        }
79

80
        $allBranchDefaultAttributeMap = [];
619✔
81
        $componentDefaultValueMap = [];
619✔
82
        $propertyAccessors = [];
619✔
83

84
        foreach ($this->composedProperties as $branchIndex => $compositionProperty) {
619✔
85
            if (!$compositionProperty->getNestedSchema()) {
619✔
86
                continue;
80✔
87
            }
88

89
            foreach ($compositionProperty->getNestedSchema()->getProperties() as $branchProperty) {
619✔
90
                // Internal machinery properties are never real branch data and must not be
91
                // transferred as a branch default of the outer composition. This covers both the
92
                // composition state tracker propertyValidationState of a re-routed composition
93
                // branch class and bookkeeping properties such as _skipNotProvidedPropertiesMap
94
                // added by SerializationPostProcessor - neither gets a getter generated, and
95
                // misreading their default values as a branch default both clobbers the outer
96
                // schema's own internal attributes and, for a mixed object/scalar composition,
97
                // feeds a non-array scalar input into the branch-default array_key_exists lookup.
98
                if ($branchProperty->isInternal()) {
619✔
99
                    continue;
61✔
100
                }
101

102
                $propertyAccessors[$branchProperty->getName()] = 'get' . ucfirst($branchProperty->getAttribute());
619✔
103

104
                if ($branchProperty->getDefaultValue() === null) {
619✔
105
                    continue;
615✔
106
                }
107

108
                $componentDefaultValueMap[$branchIndex][] = $branchProperty->getName();
27✔
109

110
                $scopeProperty = $this->scope?->getProperty($branchProperty->getName());
27✔
111

112
                // Only a branch property which was actually transferred onto the containing
113
                // schema (root-schema/base compositions, via
114
                // SchemaProcessor::transferComposedPropertiesToSchema()) is a real attribute of
115
                // $this. A composition on a named property (e.g. `target: {"oneOf": [...]}`)
116
                // never transfers its branches' properties — an object-typed branch instead
117
                // compiles to its own separate generated class, instantiated as a nested object.
118
                // Resetting such a branch-local property (e.g. the internal
119
                // `additionalProperties`/`patternProperties` bookkeeping properties) on $this
120
                // would create a dynamic property on the wrong object instead of doing nothing,
121
                // which is what's correct here: that nested object already initializes its own
122
                // default state through its own constructor, so no external reset is needed.
123
                if ($scopeProperty === null) {
27✔
UNCOV
124
                    continue;
×
125
                }
126

127
                // Do not include properties that already have a root-level default on the
128
                // parent schema — root defaults are applied unconditionally via PHP field
129
                // initializers and must not be overwritten or reset by the branch mechanism.
130
                if ($scopeProperty->getDefaultValue() !== null) {
27✔
131
                    continue;
1✔
132
                }
133

134
                $allBranchDefaultAttributeMap[$branchProperty->getName()] = $branchProperty->getAttribute();
26✔
135
            }
136
        }
137

138
        $this->templateValues['allBranchDefaultAttributeMap'] = RenderHelper::varExportArray(
619✔
139
            $allBranchDefaultAttributeMap,
619✔
140
        );
619✔
141
        $this->templateValues['modifiedValuesMethod'] = $this->modifiedValuesMethod;
619✔
142

143
        $this->scope->addMethod(
619✔
144
            $this->modifiedValuesMethod,
619✔
145
            new RenderedMethod(
619✔
146
                $this->scope,
619✔
147
                $this->generatorConfiguration,
619✔
148
                'GetModifiedValues.phptpl',
619✔
149
                [
619✔
150
                    'modifiedValuesMethod' => $this->modifiedValuesMethod,
619✔
151
                    'componentDefaultValueMap' => RenderHelper::varExportArray($componentDefaultValueMap),
619✔
152
                    'propertyAccessors' => RenderHelper::varExportArray($propertyAccessors),
619✔
153
                ],
619✔
154
            ),
619✔
155
        );
619✔
156

157
        return true;
619✔
158
    }
159
}
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