Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
71 / 71
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
ClassGenerationHelper
100.00% covered (success)
100.00%
71 / 71
100.00% covered (success)
100.00%
11 / 11
30
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setNamespace
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 createClassProperty
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
8
 addClassProperty
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addClassMethod
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 extendClass
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 implementInterfaces
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addTraitsToClass
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addConstructorToClass
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 createFullClass
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace quintenmbusiness\PhpAstCodeGenerationHelper\GeneratorHelpers;
6
7use InvalidArgumentException;
8use PhpParser\Builder\Class_;
9use PhpParser\Builder\Namespace_;
10use PhpParser\Builder\Property;
11use PhpParser\BuilderFactory;
12use PhpParser\Node\Name;
13use PhpParser\Node\Stmt;
14use PhpParser\Node\Stmt\TraitUse;
15
16class ClassGenerationHelper extends MethodGenerationHelper
17{
18    /**
19     * @param BuilderFactory $factory
20     */
21    public function __construct(BuilderFactory $factory)
22    {
23        parent::__construct($factory);
24    }
25
26    /**
27     * Creates a new class.
28     *
29     * @param string $name
30     * @return Class_
31     */
32    public function createClass(string $name): Class_
33    {
34        return $this->factory->class($name);
35    }
36
37    /**
38     * Sets the namespace for a class.
39     *
40     * @param string $namespace
41     * @param Class_ $class
42     * @param string[] $imports
43     * @return Namespace_
44     */
45    public function setNamespace(string $namespace, Class_ $class, array $imports = []): Namespace_
46    {
47        $namespaceNode = $this->factory->namespace($namespace);
48
49        foreach ($imports as $import) {
50            $useStmt = $this->factory->use($import);
51            $namespaceNode->addStmt($useStmt);
52        }
53
54        $namespaceNode->addStmt($class->getNode());
55
56        return $namespaceNode;
57    }
58
59    /**
60     * Creates a class property.
61     *
62     * @param string $name
63     * @param string|null $type
64     * @param string $visibility
65     * @param mixed|null $default
66     * @return Property
67     * @throws InvalidArgumentException if the visibility, type, or default value is invalid.
68     */
69    public function createClassProperty(string $name, ?string $type = null, string $visibility = 'public', mixed $default = null): Property
70    {
71        if (!in_array($visibility, ['public', 'protected', 'private'])) {
72            throw new InvalidArgumentException('Invalid visibility provided: ' . $visibility);
73        }
74
75        if ($type !== null && !in_array($type, ['string', 'int', 'bool', 'float', 'array', 'object'])) {
76            throw new InvalidArgumentException('Invalid type provided: ' . $type);
77        }
78
79        if ($default !== null && !$this->isValidDefault($type, $default)) {
80            throw new InvalidArgumentException('Invalid default value for type: ' . $type);
81        }
82
83        $property = $this->factory->property($name);
84
85        if ($type !== null) {
86            $property->setType($type);
87        }
88
89        if ($default !== null) {
90            $property->setDefault($this->convertToAstNode($default));
91        }
92
93        $this->addVisibility($property, $visibility);
94
95        return $property;
96    }
97
98    /**
99     * Adds properties to a class.
100     *
101     * @param Class_ $class
102     * @param string $name
103     * @param string|null $type
104     * @param string $visibility
105     * @param mixed|null $default
106     * @return Class_
107     */
108    public function addClassProperty(Class_ $class, string $name, ?string $type = null, string $visibility = 'public', mixed $default = null): Class_
109    {
110        $property = $this->createClassProperty($name, $type, $visibility, $default);
111        $class->addStmt($property);
112
113        return $class;
114    }
115
116    /**
117     * Adds methods to a class.
118     *
119     * @param Class_ $class
120     * @param string $name
121     * @param string $visibility
122     * @param string|null $returnType
123     * @param array<int, array{name: string, type: string|null}> $params
124     * @param array<int, Stmt> $body
125     * @return Class_
126     */
127    public function addClassMethod(
128        Class_ $class,
129        string $name,
130        string $visibility = 'public',
131        ?string $returnType = null,
132        array $params = [],
133        array $body = []
134    ): Class_ {
135        $method = $this->createMethod($name, $visibility, $returnType);
136
137        foreach ($params as $param) {
138            $methodParam = $this->factory->param($param['name']);
139            if ($param['type'] !== null) {
140                $methodParam->setType($param['type']);
141            }
142            $method->addParam($methodParam);
143        }
144
145        foreach ($body as $stmt) {
146            $method->addStmt($stmt);
147        }
148
149        $class->addStmt($method);
150
151        return $class;
152    }
153
154    /**
155     * Sets the parent class for a class.
156     *
157     * @param Class_ $class
158     * @param string $parentClassName
159     * @return Class_
160     */
161    public function extendClass(Class_ $class, string $parentClassName): Class_
162    {
163        $class->extend($parentClassName);
164        return $class;
165    }
166
167    /**
168     * Adds interfaces to a class.
169     *
170     * @param Class_ $class
171     * @param string[] $interfaces
172     * @return Class_
173     */
174    public function implementInterfaces(Class_ $class, array $interfaces): Class_
175    {
176        foreach ($interfaces as $interface) {
177            $class->implement($interface);
178        }
179
180        return $class;
181    }
182
183    /**
184     * Adds traits to a class.
185     *
186     * @param Class_ $class
187     * @param string[] $traits
188     * @return Class_
189     */
190    public function addTraitsToClass(Class_ $class, array $traits): Class_
191    {
192        foreach ($traits as $trait) {
193            $class->addStmt(new TraitUse([new Name($trait)]));
194        }
195
196        return $class;
197    }
198
199    /**
200     * Adds a constructor to a class.
201     *
202     * @param Class_ $class
203     * @param array<int, array{name: string, type: string|null, default: mixed|null}> $params
204     * @return Class_
205     */
206    public function addConstructorToClass(Class_ $class, array $params = []): Class_
207    {
208        $constructor = $this->factory->method('__construct')
209            ->makePublic();
210
211        foreach ($params as $param) {
212            $methodParam = $this->factory->param($param['name']);
213            if ($param['type'] !== null) {
214                $methodParam->setType($param['type']);
215            }
216            if ($param['default'] !== null) { // No need for array_key_exists
217                $methodParam->setDefault($this->convertToAstNode($param['default']));
218            }
219            $constructor->addParam($methodParam);
220
221            $constructor->addStmt($this->assignThisVarToVar($param['name'], $param['name']));
222        }
223
224        $class->addStmt($constructor);
225
226        return $class;
227    }
228
229    /**
230     * Combines multiple class creation methods into one.
231     *
232     * @param string $className
233     * @param string $namespace
234     * @param array<string, array{type: string|null, visibility: string, default: mixed|null}> $properties
235     * @param array<int, array{name: string, type: string|null, default: mixed|null}> $constructorParams
236     * @param array<int, array{name: string, visibility: string, returnType: string|null, params: array<int, array{name: string, type: string|null}>, body: array<int, Stmt>}> $methods
237     * @param string[] $traits
238     * @param string[] $implements
239     * @param string[] $imports
240     * @return Namespace_
241     */
242    public function createFullClass(
243        string $className,
244        string $namespace,
245        array $properties = [],
246        array $constructorParams = [],
247        array $methods = [],
248        array $traits = [],
249        array $implements = [],
250        array $imports = []
251    ): Namespace_ {
252        $class = $this->createClass($className);
253
254        foreach ($properties as $name => $details) {
255            $this->addClassProperty($class, $name, $details['type'], $details['visibility'], $details['default']);
256        }
257
258        if (!empty($constructorParams)) {
259            $this->addConstructorToClass($class, $constructorParams);
260        }
261
262        foreach ($methods as $method) {
263            $this->addClassMethod(
264                $class,
265                $method['name'],
266                $method['visibility'],
267                $method['returnType'],
268                $method['params'],
269                $method['body']
270            );
271        }
272
273        $this->addTraitsToClass($class, $traits);
274        $this->implementInterfaces($class, $implements);
275
276        return $this->setNamespace($namespace, $class, $imports);
277    }
278}