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

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

15 Sep 2025 03:25PM UTC coverage: 98.26% (-0.3%) from 98.564%
17738231110

Pull #92

github

wol-soft
BuilderClassPostProcessor draft
Pull Request #92: BuilderClassPostProcessor

127 of 140 new or added lines in 9 files covered. (90.71%)

4 existing lines in 1 file now uncovered.

3389 of 3449 relevant lines covered (98.26%)

563.24 hits per line

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

88.89
/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

22
/**
23
 * Class PopulatePostProcessor
24
 *
25
 * @package PHPModelGenerator\SchemaProcessor\PostProcessor
26
 */
27
class BuilderClassPostProcessor extends PostProcessor
28
{
29
    /** @var Schema[] */
30
    private array $schemas = [];
31
    private GeneratorConfiguration $generatorConfiguration;
32

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

41
    public function postProcess(): void
1✔
42
    {
43
        parent::postProcess();
1✔
44

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

61
            $this->generateModelDirectory($schema->getTargetFileName());
1✔
62

63
            $namespace = trim(
1✔
64
                join('\\', [$this->generatorConfiguration->getNamespacePrefix(), $schema->getClassPath()]),
1✔
65
                '\\',
1✔
66
            );
1✔
67

68
            $result = file_put_contents(
1✔
69
                str_replace('.php', 'Builder.php', $schema->getTargetFileName()),
1✔
70
                (new Render(__DIR__ . DIRECTORY_SEPARATOR . 'Templates' . DIRECTORY_SEPARATOR))->renderTemplate(
1✔
71
                    'BuilderClass.phptpl',
1✔
72
                    [
1✔
73
                        'namespace'              => $namespace,
1✔
74
                        'class'                  => $schema->getClassName(),
1✔
75
                        'schema'                 => $schema,
1✔
76
                        'properties'             => $properties,
1✔
77
                        'use'                    => $this->getBuilderClassImports($properties, $schema->getUsedClasses(), $namespace),
1✔
78
                        'generatorConfiguration' => $this->generatorConfiguration,
1✔
79
                        'viewHelper'             => new RenderHelper($this->generatorConfiguration),
1✔
80
                    ],
1✔
81
                )
1✔
82
            );
1✔
83

84
            $fqcn = "{$schema->getClassPath()}\\{$schema->getClassName()}Builder";
1✔
85

86
            if ($result === false) {
1✔
87
                // @codeCoverageIgnoreStart
88
                throw new FileSystemException("Can't write builder class $fqcn.",);
89
                // @codeCoverageIgnoreEnd
90
            }
91

92
            if ($this->generatorConfiguration->isOutputEnabled()) {
1✔
93
                // @codeCoverageIgnoreStart
94
                echo "Rendered builder class $fqcn\n";
95
                // @codeCoverageIgnoreEnd
96
            }
97
        }
98
    }
99

100
    protected function generateModelDirectory(string $targetFileName): void
1✔
101
    {
102
        $destination = dirname($targetFileName);
1✔
103
        if (!is_dir($destination) && !mkdir($destination, 0777, true)) {
1✔
NEW
104
            throw new FileSystemException("Can't create path $destination");
×
105
        }
106
    }
107

108
    /**
109
     * @param PropertyInterface[] $properties
110
     *
111
     * @return string[]
112
     */
113
    private function getBuilderClassImports(array $properties, array $originalClassImports, string $namespace): array
1✔
114
    {
115
        $imports = [BuilderInterface::class];
1✔
116
        $imports[] = $this->generatorConfiguration->collectErrors()
1✔
117
            ? $this->generatorConfiguration->getErrorRegistryClass()
1✔
NEW
118
            : ValidationException::class;
×
119

120
        foreach ($properties as $property) {
1✔
121
            // use typehint instead of type to cover multi-types
122
            foreach (array_unique(
1✔
123
                [...explode('|', $property->getTypeHint()), ...explode('|', $property->getTypeHint(true))]
1✔
124
            ) as $type) {
1✔
125
                // as the typehint only knows the class name but not the fqcn, lookup in the original imports
126
                foreach ($originalClassImports as $originalClassImport) {
1✔
127
                    if (str_ends_with($originalClassImport, "\\$type")) {
1✔
NEW
128
                        $type = $originalClassImport;
×
129
                    }
130
                }
131

132
                if (class_exists($type)) {
1✔
NEW
133
                    $imports[] = $type;
×
134

135
                    // for nested objects, allow additionally to pass an instance of the nested model also just plain
136
                    // arrays which will result in an object instantiation and validation during the build process
NEW
137
                    if (in_array(JSONModelInterface::class, class_implements($type))) {
×
NEW
138
                        $property->addTypeHintDecorator(new TypeHintDecorator(['array', basename($type) . 'Builder']));
×
NEW
139
                        $property->setType();
×
140
                    }
141
                }
142
            }
143
        }
144

145
        return RenderHelper::filterClassImports(array_unique($imports), $namespace);
1✔
146
    }
147
}
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