Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
71 / 71 |
|
100.00% |
11 / 11 |
CRAP | |
100.00% |
1 / 1 |
ClassGenerationHelper | |
100.00% |
71 / 71 |
|
100.00% |
11 / 11 |
30 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createClass | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setNamespace | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
createClassProperty | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
8 | |||
addClassProperty | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
addClassMethod | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
extendClass | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
implementInterfaces | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
addTraitsToClass | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
addConstructorToClass | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
4 | |||
createFullClass | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
4 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace quintenmbusiness\PhpAstCodeGenerationHelper\GeneratorHelpers; |
6 | |
7 | use InvalidArgumentException; |
8 | use PhpParser\Builder\Class_; |
9 | use PhpParser\Builder\Namespace_; |
10 | use PhpParser\Builder\Property; |
11 | use PhpParser\BuilderFactory; |
12 | use PhpParser\Node\Name; |
13 | use PhpParser\Node\Stmt; |
14 | use PhpParser\Node\Stmt\TraitUse; |
15 | |
16 | class 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 | } |