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

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

18 Sep 2025 01:40PM UTC coverage: 98.434% (-0.1%) from 98.564%
17830597583

Pull #92

github

web-flow
Merge 59383d515 into 0e2c43036
Pull Request #92: BuilderClassPostProcessor

135 of 142 new or added lines in 10 files covered. (95.07%)

2 existing lines in 1 file now uncovered.

3394 of 3448 relevant lines covered (98.43%)

564.49 hits per line

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

96.72
/src/SchemaProcessor/PostProcessor/BuilderClassPostProcessor.php
1
<?php
2

3
declare(strict_types = 1);
4

5
namespace PHPModelGenerator\SchemaProcessor\PostProcessor;
6

7
use PHPMicroTemplate\Render;
8
use PHPModelGenerator\Exception\FileSystemException;
9
use PHPModelGenerator\Exception\ValidationException;
10
use PHPModelGenerator\Interfaces\BuilderInterface;
11
use PHPModelGenerator\Interfaces\JSONModelInterface;
12
use PHPModelGenerator\Model\GeneratorConfiguration;
13
use PHPModelGenerator\Model\Property\PropertyInterface;
14
use PHPModelGenerator\Model\Property\PropertyType;
15
use PHPModelGenerator\Model\Schema;
16
use PHPModelGenerator\Model\Validator;
17
use PHPModelGenerator\Model\Validator\FilterValidator;
18
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\TypeHintDecorator;
19
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\TypeHintTransferDecorator;
20
use PHPModelGenerator\Utils\RenderHelper;
21
use UnitEnum;
22

23
class BuilderClassPostProcessor extends PostProcessor
24
{
25
    /** @var Schema[] */
26
    private array $schemas = [];
27
    private GeneratorConfiguration $generatorConfiguration;
28

29
    public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void
3✔
30
    {
31
        // collect the schemas and generate builder classes in postProcess hook to make sure the related model class
32
        // already has been created
33
        $this->schemas[] = $schema;
3✔
34
        $this->generatorConfiguration = $generatorConfiguration;
3✔
35
    }
36

37
    public function postProcess(): void
3✔
38
    {
39
        parent::postProcess();
3✔
40

41
        foreach ($this->schemas as $schema) {
3✔
42
            $properties = [];
3✔
43
            foreach ($schema->getProperties() as $property) {
3✔
44
                if (!$property->isInternal()) {
3✔
45
                    $properties[] = (clone $property)
3✔
46
                        ->setReadOnly(false)
3✔
47
                        // ensure the getter methods for required properties can return null (they have not been set yet)
3✔
48
                        ->setType($property->getType(), new PropertyType($property->getType(true)->getName(), true))
3✔
49
                        ->addTypeHintDecorator(new TypeHintTransferDecorator($property))
3✔
50
                        // keep filters to ensure values set on the builder match the return type of the getter
3✔
51
                        ->filterValidators(static fn(Validator $validator): bool
3✔
52
                            => is_a($validator->getValidator(), FilterValidator::class)
3✔
53
                        );
3✔
54
                }
55
            }
56

57
            $namespace = trim(
3✔
58
                join('\\', [$this->generatorConfiguration->getNamespacePrefix(), $schema->getClassPath()]),
3✔
59
                '\\',
3✔
60
            );
3✔
61

62
            $result = file_put_contents(
3✔
63
                $filename = str_replace('.php', 'Builder.php', $schema->getTargetFileName()),
3✔
64
                (new Render(__DIR__ . DIRECTORY_SEPARATOR . 'Templates' . DIRECTORY_SEPARATOR))->renderTemplate(
3✔
65
                    'BuilderClass.phptpl',
3✔
66
                    [
3✔
67
                        'namespace'              => $namespace,
3✔
68
                        'class'                  => $schema->getClassName(),
3✔
69
                        'schema'                 => $schema,
3✔
70
                        'properties'             => $properties,
3✔
71
                        'use'                    => $this->getBuilderClassImports($properties, $schema->getUsedClasses(), $namespace),
3✔
72
                        'generatorConfiguration' => $this->generatorConfiguration,
3✔
73
                        'viewHelper'             => new RenderHelper($this->generatorConfiguration),
3✔
74
                    ],
3✔
75
                )
3✔
76
            );
3✔
77

78
            $fqcn = "$namespace\\{$schema->getClassName()}Builder";
3✔
79

80
            if ($result === false) {
3✔
81
                // @codeCoverageIgnoreStart
82
                throw new FileSystemException("Can't write builder class $fqcn.",);
83
                // @codeCoverageIgnoreEnd
84
            }
85

86
            require $filename;
3✔
87

88
            if ($this->generatorConfiguration->isOutputEnabled()) {
3✔
89
                // @codeCoverageIgnoreStart
90
                echo "Rendered builder class $fqcn\n";
91
                // @codeCoverageIgnoreEnd
92
            }
93
        }
94
    }
95

96
    /**
97
     * @param PropertyInterface[] $properties
98
     *
99
     * @return string[]
100
     */
101
    private function getBuilderClassImports(array $properties, array $originalClassImports, string $namespace): array
3✔
102
    {
103
        $imports = [BuilderInterface::class];
3✔
104
        $imports[] = $this->generatorConfiguration->collectErrors()
3✔
105
            ? $this->generatorConfiguration->getErrorRegistryClass()
1✔
106
            : ValidationException::class;
2✔
107

108
        foreach ($properties as $property) {
3✔
109
            // use typehint instead of type to cover multi-types
110
            foreach (array_unique(
3✔
111
                [...explode('|', $property->getTypeHint()), ...explode('|', $property->getTypeHint(true))]
3✔
112
            ) as $type) {
3✔
113
                // as the typehint only knows the class name but not the fqcn, lookup in the original imports
114
                foreach ($originalClassImports as $originalClassImport) {
3✔
115
                    if (str_ends_with($originalClassImport, "\\$type")) {
3✔
NEW
116
                        $type = $originalClassImport;
×
117
                    }
118
                }
119

120
                // required for compatibility with the EnumPostProcessor
121
                if (enum_exists($type)) {
3✔
NEW
122
                    array_push($imports, $type, UnitEnum::class);
×
123
                }
124

125
                if (class_exists($type)) {
3✔
126
                    $imports[] = $type;
1✔
127

128
                    // for nested objects, allow additionally to pass an instance of the nested model also just plain
129
                    // arrays which will result in an object instantiation and validation during the build process
130
                    if (in_array(JSONModelInterface::class, class_implements($type))) {
1✔
131
                        $property->addTypeHintDecorator(new TypeHintDecorator([basename($type) . 'Builder', 'array']));
1✔
132
                        $property->setType();
1✔
133
                    }
134
                }
135
            }
136
        }
137

138
        return RenderHelper::filterClassImports(array_unique($imports), $namespace);
3✔
139
    }
140
}
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