1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration; |
||||
6 | |||||
7 | use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Command\AbstractCommand; |
||||
8 | use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\AbstractGenerator; |
||||
9 | use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator; |
||||
10 | use EdmondsCommerce\DoctrineStaticMeta\Config; |
||||
11 | use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException; |
||||
12 | use EdmondsCommerce\DoctrineStaticMeta\MappingHelper; |
||||
13 | use Exception; |
||||
14 | use RuntimeException; |
||||
15 | use SplFileInfo; |
||||
16 | use Symfony\Component\Finder\Finder; |
||||
17 | |||||
18 | use function array_merge; |
||||
19 | use function array_slice; |
||||
20 | use function get_class; |
||||
21 | use function implode; |
||||
22 | use function in_array; |
||||
23 | use function str_replace; |
||||
24 | use function strlen; |
||||
25 | use function strrpos; |
||||
26 | use function substr; |
||||
27 | use function ucfirst; |
||||
28 | |||||
29 | /** |
||||
30 | * Class NamespaceHelper |
||||
31 | * |
||||
32 | * Pure functions for working with namespaces and to calculate namespaces |
||||
33 | * |
||||
34 | * @package EdmondsCommerce\DoctrineStaticMeta\CodeGeneration |
||||
35 | * @SuppressWarnings(PHPMD.TooManyPublicMethods) |
||||
36 | * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) |
||||
37 | * @SuppressWarnings(PHPMD.ExcessivePublicCount) |
||||
38 | */ |
||||
39 | class NamespaceHelper |
||||
40 | { |
||||
41 | 2 | public function getAllArchetypeFieldFqns(): array |
|||
42 | { |
||||
43 | 2 | $archetypeFqns = []; |
|||
44 | 2 | $namespaceBase = 'EdmondsCommerce\\DoctrineStaticMeta\\Entity\\Fields\\Traits'; |
|||
45 | 2 | $finder = (new Finder())->files() |
|||
46 | 2 | ->name('*.php') |
|||
47 | 2 | ->in(__DIR__ . '/../Entity/Fields/Traits/'); |
|||
48 | 2 | foreach ($finder as $file) { |
|||
49 | /** @var SplFileInfo $file */ |
||||
50 | 2 | $realpath = $file->getRealPath(); |
|||
51 | 2 | if (\ts\stringContains($realpath, '/PrimaryKey/')) { |
|||
52 | 2 | continue; |
|||
53 | } |
||||
54 | 2 | $subPath = substr( |
|||
55 | 2 | $realpath, |
|||
56 | 2 | strpos($realpath, 'Entity/Fields/Traits/') + strlen('Entity/Fields/Traits/') |
|||
57 | ); |
||||
58 | 2 | $subPath = substr($subPath, 0, -4); |
|||
59 | 2 | $subFqn = str_replace('/', '\\', $subPath); |
|||
60 | 2 | $archetypeFqns[] = $namespaceBase . '\\' . $subFqn; |
|||
61 | } |
||||
62 | |||||
63 | 2 | return $archetypeFqns; |
|||
64 | } |
||||
65 | |||||
66 | 2 | public function swapSuffix(string $fqn, string $currentSuffix, string $newSuffix): string |
|||
67 | { |
||||
68 | 2 | return $this->cropSuffix($fqn, $currentSuffix) . $newSuffix; |
|||
69 | } |
||||
70 | |||||
71 | /** |
||||
72 | * Crop a suffix from an FQN if it is there. |
||||
73 | * |
||||
74 | * If it is not there, do nothing and return the FQN as is |
||||
75 | * |
||||
76 | * @param string $fqn |
||||
77 | * @param string $suffix |
||||
78 | * |
||||
79 | * @return string |
||||
80 | */ |
||||
81 | 6 | public function cropSuffix(string $fqn, string $suffix): string |
|||
82 | { |
||||
83 | 6 | if ($suffix === substr($fqn, -strlen($suffix))) { |
|||
84 | 4 | return substr($fqn, 0, -strlen($suffix)); |
|||
85 | } |
||||
86 | |||||
87 | 2 | return $fqn; |
|||
88 | } |
||||
89 | |||||
90 | 2 | public function getEmbeddableObjectFqnFromEmbeddableObjectInterfaceFqn(string $interfaceFqn): string |
|||
91 | { |
||||
92 | 2 | return str_replace( |
|||
93 | 2 | ['\\Interfaces\\', 'Interface'], |
|||
94 | 2 | ['\\', ''], |
|||
95 | 2 | $interfaceFqn |
|||
96 | ); |
||||
97 | } |
||||
98 | |||||
99 | /** |
||||
100 | * @param mixed|object $object |
||||
101 | * |
||||
102 | * @return string |
||||
103 | */ |
||||
104 | 1 | public function getObjectShortName($object): string |
|||
105 | { |
||||
106 | 1 | return $this->getClassShortName($this->getObjectFqn($object)); |
|||
107 | } |
||||
108 | |||||
109 | /** |
||||
110 | * @param string $className |
||||
111 | * |
||||
112 | * @return string |
||||
113 | */ |
||||
114 | 2 | public function getClassShortName(string $className): string |
|||
115 | { |
||||
116 | 2 | $exp = explode('\\', $className); |
|||
117 | |||||
118 | 2 | return end($exp); |
|||
119 | } |
||||
120 | |||||
121 | /** |
||||
122 | * @param mixed|object $object |
||||
123 | * |
||||
124 | * @return string |
||||
125 | * @see https://gist.github.com/ludofleury/1708784 |
||||
126 | */ |
||||
127 | 2 | public function getObjectFqn($object): string |
|||
128 | { |
||||
129 | 2 | return get_class($object); |
|||
130 | } |
||||
131 | |||||
132 | /** |
||||
133 | * Get the basename of a namespace |
||||
134 | * |
||||
135 | * @param string $namespace |
||||
136 | * |
||||
137 | * @return string |
||||
138 | */ |
||||
139 | 1 | public function basename(string $namespace): string |
|||
140 | { |
||||
141 | 1 | $strrpos = strrpos($namespace, '\\'); |
|||
142 | 1 | if (false === $strrpos) { |
|||
143 | return $namespace; |
||||
144 | } |
||||
145 | |||||
146 | 1 | return $this->tidy(substr($namespace, $strrpos + 1)); |
|||
147 | } |
||||
148 | |||||
149 | /** |
||||
150 | * Checks and tidies up a given namespace |
||||
151 | * |
||||
152 | * @param string $namespace |
||||
153 | * |
||||
154 | * @return string |
||||
155 | * @throws RuntimeException |
||||
156 | */ |
||||
157 | 24 | public function tidy(string $namespace): string |
|||
158 | { |
||||
159 | 24 | if (\ts\stringContains($namespace, '/')) { |
|||
160 | throw new RuntimeException('Invalid namespace ' . $namespace); |
||||
161 | } |
||||
162 | #remove repeated separators |
||||
163 | 24 | $namespace = preg_replace( |
|||
164 | 24 | '#' . '\\\\' . '+#', |
|||
165 | 24 | '\\', |
|||
166 | 24 | $namespace |
|||
167 | ); |
||||
168 | |||||
169 | 24 | return $namespace; |
|||
170 | } |
||||
171 | |||||
172 | /** |
||||
173 | * Get the fully qualified name of the Fixture class for a specified Entity fully qualified name |
||||
174 | * |
||||
175 | * @param string $entityFqn |
||||
176 | * |
||||
177 | * @return string |
||||
178 | */ |
||||
179 | 3 | public function getFixtureFqnFromEntityFqn(string $entityFqn): string |
|||
180 | { |
||||
181 | 3 | return str_replace( |
|||
182 | 3 | '\\Entities', |
|||
183 | 3 | '\\Assets\\Entity\\Fixtures', |
|||
184 | 3 | $entityFqn |
|||
185 | 3 | ) . 'Fixture'; |
|||
186 | } |
||||
187 | |||||
188 | /** |
||||
189 | * Get the fully qualified name of the Entity for a specified Entity fully qualified name |
||||
190 | * |
||||
191 | * @param string $fixtureFqn |
||||
192 | * |
||||
193 | * @return string |
||||
194 | */ |
||||
195 | public function getEntityFqnFromFixtureFqn(string $fixtureFqn): string |
||||
196 | { |
||||
197 | return substr( |
||||
198 | str_replace( |
||||
199 | '\\Assets\\Entity\\Fixtures', |
||||
200 | '\\Entities', |
||||
201 | $fixtureFqn |
||||
202 | ), |
||||
203 | 0, |
||||
204 | -strlen('Fixture') |
||||
205 | ); |
||||
206 | } |
||||
207 | |||||
208 | /** |
||||
209 | * Get the namespace root up to and including a specified directory |
||||
210 | * |
||||
211 | * @param string $fqn |
||||
212 | * @param string $directory |
||||
213 | * |
||||
214 | * @return null|string |
||||
215 | */ |
||||
216 | public function getNamespaceRootToDirectoryFromFqn(string $fqn, string $directory): ?string |
||||
217 | { |
||||
218 | $strPos = strrpos( |
||||
219 | $fqn, |
||||
220 | $directory |
||||
221 | ); |
||||
222 | if (false !== $strPos) { |
||||
223 | return $this->tidy(substr($fqn, 0, $strPos + strlen($directory))); |
||||
224 | } |
||||
225 | |||||
226 | return null; |
||||
227 | } |
||||
228 | |||||
229 | /** |
||||
230 | * Get the sub path for an Entity file, start from the Entities path - normally `/path/to/project/src/Entities` |
||||
231 | * |
||||
232 | * @param string $entityFqn |
||||
233 | * |
||||
234 | * @return string |
||||
235 | */ |
||||
236 | 1 | public function getEntityFileSubPath( |
|||
237 | string $entityFqn |
||||
238 | ): string { |
||||
239 | 1 | return $this->getEntitySubPath($entityFqn) . '.php'; |
|||
240 | } |
||||
241 | |||||
242 | /** |
||||
243 | * Get the folder structure for an Entity, start from the Entities path - normally `/path/to/project/src/Entities` |
||||
244 | * |
||||
245 | * This is not the path to the file, but the sub path of directories for storing entity related items. |
||||
246 | * |
||||
247 | * @param string $entityFqn |
||||
248 | * |
||||
249 | * @return string |
||||
250 | */ |
||||
251 | 2 | public function getEntitySubPath( |
|||
252 | string $entityFqn |
||||
253 | ): string { |
||||
254 | 2 | $entityPath = str_replace( |
|||
255 | 2 | '\\', |
|||
256 | 2 | '/', |
|||
257 | 2 | $this->getEntitySubNamespace($entityFqn) |
|||
258 | ); |
||||
259 | |||||
260 | 2 | return '/' . $entityPath; |
|||
261 | } |
||||
262 | |||||
263 | /** |
||||
264 | * Get the Namespace for an Entity, start from the Entities Fully Qualified Name base - normally |
||||
265 | * `\My\Project\Entities\` |
||||
266 | * |
||||
267 | * @param string $entityFqn |
||||
268 | * |
||||
269 | * @return string |
||||
270 | */ |
||||
271 | 7 | public function getEntitySubNamespace( |
|||
272 | string $entityFqn |
||||
273 | ): string { |
||||
274 | 7 | return $this->tidy( |
|||
275 | 7 | substr( |
|||
276 | 7 | $entityFqn, |
|||
277 | 7 | strrpos( |
|||
278 | 7 | $entityFqn, |
|||
279 | 7 | '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\' |
|||
280 | ) |
||||
281 | 7 | + strlen('\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\') |
|||
282 | ) |
||||
283 | ); |
||||
284 | } |
||||
285 | |||||
286 | /** |
||||
287 | * Get the Fully Qualified Namespace root for Traits for the specified Entity |
||||
288 | * |
||||
289 | * @param string $entityFqn |
||||
290 | * |
||||
291 | * @return string |
||||
292 | */ |
||||
293 | 1 | public function getTraitsNamespaceForEntity( |
|||
294 | string $entityFqn |
||||
295 | ): string { |
||||
296 | 1 | $traitsNamespace = $this->getProjectNamespaceRootFromEntityFqn($entityFqn) |
|||
297 | 1 | . AbstractGenerator::ENTITY_RELATIONS_NAMESPACE |
|||
298 | 1 | . '\\' . $this->getEntitySubNamespace($entityFqn) |
|||
299 | 1 | . '\\Traits'; |
|||
300 | |||||
301 | 1 | return $traitsNamespace; |
|||
302 | } |
||||
303 | |||||
304 | /** |
||||
305 | * Use the fully qualified name of two Entities to calculate the Project Namespace Root |
||||
306 | * |
||||
307 | * - note: this assumes a single namespace level for entities, eg `Entities` |
||||
308 | * |
||||
309 | * @param string $entityFqn |
||||
310 | * |
||||
311 | * @return string |
||||
312 | */ |
||||
313 | 5 | public function getProjectNamespaceRootFromEntityFqn(string $entityFqn): string |
|||
314 | { |
||||
315 | 5 | return $this->tidy( |
|||
316 | 5 | substr( |
|||
317 | 5 | $entityFqn, |
|||
318 | 5 | 0, |
|||
319 | 5 | strrpos( |
|||
320 | 5 | $entityFqn, |
|||
321 | 5 | '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\' |
|||
322 | ) |
||||
323 | ) |
||||
324 | ); |
||||
325 | } |
||||
326 | |||||
327 | /** |
||||
328 | * Get the Fully Qualified Namespace for the "HasEntities" interface for the specified Entity |
||||
329 | * |
||||
330 | * @param string $entityFqn |
||||
331 | * |
||||
332 | * @return string |
||||
333 | */ |
||||
334 | 1 | public function getHasPluralInterfaceFqnForEntity( |
|||
335 | string $entityFqn |
||||
336 | ): string { |
||||
337 | 1 | $interfaceNamespace = $this->getInterfacesNamespaceForEntity($entityFqn); |
|||
338 | |||||
339 | 1 | return $interfaceNamespace . '\\Has' . ucfirst($entityFqn::getDoctrineStaticMeta()->getPlural()) . 'Interface'; |
|||
340 | } |
||||
341 | |||||
342 | /** |
||||
343 | * Get the Fully Qualified Namespace root for Interfaces for the specified Entity |
||||
344 | * |
||||
345 | * @param string $entityFqn |
||||
346 | * |
||||
347 | * @return string |
||||
348 | */ |
||||
349 | 3 | public function getInterfacesNamespaceForEntity( |
|||
350 | string $entityFqn |
||||
351 | ): string { |
||||
352 | 3 | $interfacesNamespace = $this->getProjectNamespaceRootFromEntityFqn($entityFqn) |
|||
353 | 3 | . AbstractGenerator::ENTITY_RELATIONS_NAMESPACE |
|||
354 | 3 | . '\\' . $this->getEntitySubNamespace($entityFqn) |
|||
355 | 3 | . '\\Interfaces'; |
|||
356 | |||||
357 | 3 | return $this->tidy($interfacesNamespace); |
|||
358 | } |
||||
359 | |||||
360 | /** |
||||
361 | * Get the Fully Qualified Namespace for the "HasEntity" interface for the specified Entity |
||||
362 | * |
||||
363 | * @param string $entityFqn |
||||
364 | * |
||||
365 | * @return string |
||||
366 | * @throws DoctrineStaticMetaException |
||||
367 | */ |
||||
368 | 1 | public function getHasSingularInterfaceFqnForEntity( |
|||
369 | string $entityFqn |
||||
370 | ): string { |
||||
371 | try { |
||||
372 | 1 | $interfaceNamespace = $this->getInterfacesNamespaceForEntity($entityFqn); |
|||
373 | |||||
374 | 1 | return $interfaceNamespace . '\\Has' . ucfirst($entityFqn::getDoctrineStaticMeta()->getSingular()) |
|||
375 | 1 | . 'Interface'; |
|||
376 | } catch (Exception $e) { |
||||
377 | throw new DoctrineStaticMetaException( |
||||
378 | 'Exception in ' . __METHOD__ . ': ' . $e->getMessage(), |
||||
379 | $e->getCode(), |
||||
380 | $e |
||||
381 | ); |
||||
382 | } |
||||
383 | } |
||||
384 | |||||
385 | /** |
||||
386 | * Get the Fully Qualified Namespace for the Relation Trait for a specific Entity and hasType |
||||
387 | * |
||||
388 | * @param string $hasType |
||||
389 | * @param string $ownedEntityFqn |
||||
390 | * @param string|null $projectRootNamespace |
||||
391 | * @param string $srcFolder |
||||
392 | * |
||||
393 | * @return string |
||||
394 | * @throws DoctrineStaticMetaException |
||||
395 | */ |
||||
396 | 2 | public function getOwningTraitFqn( |
|||
397 | string $hasType, |
||||
398 | string $ownedEntityFqn, |
||||
399 | ?string $projectRootNamespace = null, |
||||
400 | string $srcFolder = AbstractCommand::DEFAULT_SRC_SUBFOLDER |
||||
401 | ): string { |
||||
402 | try { |
||||
403 | 2 | $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, $projectRootNamespace); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
404 | 2 | if (null === $projectRootNamespace) { |
|||
405 | $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcFolder); |
||||
406 | } |
||||
407 | 2 | list($ownedClassName, , $ownedSubDirectories) = $this->parseFullyQualifiedName( |
|||
408 | 2 | $ownedEntityFqn, |
|||
409 | 2 | $srcFolder, |
|||
410 | 2 | $projectRootNamespace |
|||
411 | ); |
||||
412 | 2 | $traitSubDirectories = array_slice($ownedSubDirectories, 2); |
|||
413 | 2 | $owningTraitFqn = $this->getOwningRelationsRootFqn( |
|||
414 | 2 | $projectRootNamespace, |
|||
415 | 2 | $traitSubDirectories |
|||
416 | ); |
||||
417 | 2 | $required = \ts\stringContains($hasType, RelationsGenerator::PREFIX_REQUIRED) |
|||
418 | 1 | ? RelationsGenerator::PREFIX_REQUIRED |
|||
419 | 2 | : ''; |
|||
420 | 2 | $owningTraitFqn .= $ownedClassName . '\\Traits\\Has' . $required . $ownedHasName |
|||
421 | 2 | . '\\' . $this->getBaseHasTypeTraitFqn($ownedHasName, $hasType); |
|||
422 | |||||
423 | 2 | return $this->tidy($owningTraitFqn); |
|||
424 | } catch (Exception $e) { |
||||
425 | throw new DoctrineStaticMetaException( |
||||
426 | 'Exception in ' . __METHOD__ . ': ' . $e->getMessage(), |
||||
427 | $e->getCode(), |
||||
428 | $e |
||||
429 | ); |
||||
430 | } |
||||
431 | } |
||||
432 | |||||
433 | /** |
||||
434 | * Based on the $hasType, we calculate exactly what type of `Has` we have |
||||
435 | * |
||||
436 | * @param string $hasType |
||||
437 | * @param string $ownedEntityFqn |
||||
438 | * @param string $srcOrTestSubFolder |
||||
439 | * |
||||
440 | * @param string $projectRootNamespace |
||||
441 | * |
||||
442 | * @return string |
||||
443 | * @SuppressWarnings(PHPMD.StaticAccess) |
||||
444 | * @throws DoctrineStaticMetaException |
||||
445 | */ |
||||
446 | 4 | public function getOwnedHasName( |
|||
447 | string $hasType, |
||||
448 | string $ownedEntityFqn, |
||||
449 | string $srcOrTestSubFolder, |
||||
450 | string $projectRootNamespace |
||||
451 | ): string { |
||||
452 | 4 | $parsedFqn = $this->parseFullyQualifiedName( |
|||
453 | 4 | $ownedEntityFqn, |
|||
454 | 4 | $srcOrTestSubFolder, |
|||
455 | 4 | $projectRootNamespace |
|||
456 | ); |
||||
457 | |||||
458 | 4 | $subDirectories = $parsedFqn[2]; |
|||
459 | |||||
460 | if ( |
||||
461 | 4 | in_array( |
|||
462 | 4 | $hasType, |
|||
463 | 4 | RelationsGenerator::HAS_TYPES_PLURAL, |
|||
464 | 4 | true |
|||
465 | ) |
||||
466 | ) { |
||||
467 | 4 | return $this->getPluralNamespacedName($ownedEntityFqn, $subDirectories); |
|||
468 | } |
||||
469 | |||||
470 | 2 | return $this->getSingularNamespacedName($ownedEntityFqn, $subDirectories); |
|||
471 | } |
||||
472 | |||||
473 | /** |
||||
474 | * From the fully qualified name, parse out: |
||||
475 | * - class name, |
||||
476 | * - namespace |
||||
477 | * - the namespace parts not including the project root namespace |
||||
478 | * |
||||
479 | * @param string $fqn |
||||
480 | * |
||||
481 | * @param string $srcOrTestSubFolder eg 'src' or 'test' |
||||
482 | * |
||||
483 | * @param string|null $projectRootNamespace |
||||
484 | * |
||||
485 | * @return array [$className,$namespace,$subDirectories] |
||||
486 | * @throws DoctrineStaticMetaException |
||||
487 | */ |
||||
488 | 5 | public function parseFullyQualifiedName( |
|||
489 | string $fqn, |
||||
490 | string $srcOrTestSubFolder = AbstractCommand::DEFAULT_SRC_SUBFOLDER, |
||||
491 | string $projectRootNamespace = null |
||||
492 | ): array { |
||||
493 | try { |
||||
494 | 5 | $fqn = $this->root($fqn); |
|||
495 | 5 | if (null === $projectRootNamespace) { |
|||
496 | $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcOrTestSubFolder); |
||||
497 | } |
||||
498 | 5 | $projectRootNamespace = $this->root($projectRootNamespace); |
|||
499 | 5 | if (false === \ts\stringContains($fqn, $projectRootNamespace)) { |
|||
0 ignored issues
–
show
|
|||||
500 | throw new DoctrineStaticMetaException( |
||||
501 | 'The $fqn [' . $fqn . '] does not contain the project root namespace' |
||||
502 | . ' [' . $projectRootNamespace . '] - are you sure it is the correct FQN?' |
||||
503 | ); |
||||
504 | } |
||||
505 | 5 | $fqnParts = explode('\\', $fqn); |
|||
506 | 5 | $className = array_pop($fqnParts); |
|||
507 | 5 | $namespace = implode('\\', $fqnParts); |
|||
508 | 5 | $rootParts = explode('\\', $projectRootNamespace); |
|||
509 | 5 | $subDirectories = []; |
|||
510 | 5 | foreach ($fqnParts as $k => $fqnPart) { |
|||
511 | 5 | if (isset($rootParts[$k]) && $rootParts[$k] === $fqnPart) { |
|||
512 | 5 | continue; |
|||
513 | } |
||||
514 | 5 | $subDirectories[] = $fqnPart; |
|||
515 | } |
||||
516 | 5 | array_unshift($subDirectories, $srcOrTestSubFolder); |
|||
517 | |||||
518 | return [ |
||||
519 | 5 | $className, |
|||
520 | 5 | $this->root($namespace), |
|||
521 | 5 | $subDirectories, |
|||
522 | ]; |
||||
523 | } catch (Exception $e) { |
||||
524 | throw new DoctrineStaticMetaException( |
||||
525 | 'Exception in ' . __METHOD__ . ': ' . $e->getMessage(), |
||||
526 | $e->getCode(), |
||||
527 | $e |
||||
528 | ); |
||||
529 | } |
||||
530 | } |
||||
531 | |||||
532 | /** |
||||
533 | * Generate a tidy root namespace without a leading \ |
||||
534 | * |
||||
535 | * @param string $namespace |
||||
536 | * |
||||
537 | * @return string |
||||
538 | */ |
||||
539 | 7 | public function root(string $namespace): string |
|||
540 | { |
||||
541 | 7 | return $this->tidy(ltrim($namespace, '\\')); |
|||
542 | } |
||||
543 | |||||
544 | /** |
||||
545 | * Read src autoloader from composer json |
||||
546 | * |
||||
547 | * @param string $dirForNamespace |
||||
548 | * |
||||
549 | * @return string |
||||
550 | * @throws DoctrineStaticMetaException |
||||
551 | * @SuppressWarnings(PHPMD.StaticAccess) |
||||
552 | */ |
||||
553 | 16 | public function getProjectRootNamespaceFromComposerJson( |
|||
554 | string $dirForNamespace = 'src' |
||||
555 | ): string { |
||||
556 | try { |
||||
557 | 16 | $dirForNamespace = trim($dirForNamespace, '/'); |
|||
558 | 16 | $jsonPath = Config::getProjectRootDirectory() . '/composer.json'; |
|||
559 | 16 | $json = json_decode(\ts\file_get_contents($jsonPath), true); |
|||
560 | 16 | if (JSON_ERROR_NONE !== json_last_error()) { |
|||
561 | throw new RuntimeException( |
||||
562 | 'Error decoding json from path ' . $jsonPath . ' , ' . json_last_error_msg() |
||||
563 | ); |
||||
564 | } |
||||
565 | /** |
||||
566 | * @var string[][][][] $json |
||||
567 | */ |
||||
568 | 16 | if (isset($json['autoload']['psr-4'])) { |
|||
569 | 16 | foreach ($json['autoload']['psr-4'] as $namespace => $dirs) { |
|||
570 | 16 | if (!is_array($dirs)) { |
|||
571 | $dirs = [$dirs]; |
||||
572 | } |
||||
573 | 16 | foreach ($dirs as $dir) { |
|||
574 | 16 | $dir = trim($dir, '/'); |
|||
575 | 16 | if ($dir === $dirForNamespace) { |
|||
576 | 16 | return $this->tidy(rtrim($namespace, '\\')); |
|||
577 | } |
||||
578 | } |
||||
579 | } |
||||
580 | } |
||||
581 | } catch (Exception $e) { |
||||
582 | throw new DoctrineStaticMetaException( |
||||
583 | 'Exception in ' . __METHOD__ . ': ' . $e->getMessage(), |
||||
584 | $e->getCode(), |
||||
585 | $e |
||||
586 | ); |
||||
587 | } |
||||
588 | throw new DoctrineStaticMetaException('Failed to find psr-4 namespace root'); |
||||
589 | } |
||||
590 | |||||
591 | /** |
||||
592 | * @param string $entityFqn |
||||
593 | * @param array $subDirectories |
||||
594 | * |
||||
595 | * @return string |
||||
596 | * @SuppressWarnings(PHPMD.StaticAccess) |
||||
597 | */ |
||||
598 | 4 | public function getPluralNamespacedName(string $entityFqn, array $subDirectories): string |
|||
599 | { |
||||
600 | 4 | $plural = ucfirst(MappingHelper::getPluralForFqn($entityFqn)); |
|||
601 | |||||
602 | 4 | return $this->getNamespacedName($plural, $subDirectories); |
|||
603 | } |
||||
604 | |||||
605 | /** |
||||
606 | * @param string $entityName |
||||
607 | * @param array $subDirectories |
||||
608 | * |
||||
609 | * @return string |
||||
610 | */ |
||||
611 | 4 | public function getNamespacedName(string $entityName, array $subDirectories): string |
|||
612 | { |
||||
613 | 4 | $noEntitiesDirectory = array_slice($subDirectories, 2); |
|||
614 | 4 | $namespacedName = array_merge($noEntitiesDirectory, [$entityName]); |
|||
615 | |||||
616 | 4 | return ucfirst(implode('', $namespacedName)); |
|||
617 | } |
||||
618 | |||||
619 | /** |
||||
620 | * @param string $entityFqn |
||||
621 | * @param array $subDirectories |
||||
622 | * |
||||
623 | * @return string |
||||
624 | * @SuppressWarnings(PHPMD.StaticAccess) |
||||
625 | */ |
||||
626 | 3 | public function getSingularNamespacedName(string $entityFqn, array $subDirectories): string |
|||
627 | { |
||||
628 | 3 | $singular = ucfirst(MappingHelper::getSingularForFqn($entityFqn)); |
|||
629 | |||||
630 | 3 | return $this->getNamespacedName($singular, $subDirectories); |
|||
631 | } |
||||
632 | |||||
633 | /** |
||||
634 | * Get the Namespace root for Entity Relations |
||||
635 | * |
||||
636 | * @param string $projectRootNamespace |
||||
637 | * @param array $subDirectories |
||||
638 | * |
||||
639 | * @return string |
||||
640 | */ |
||||
641 | 3 | public function getOwningRelationsRootFqn( |
|||
642 | string $projectRootNamespace, |
||||
643 | array $subDirectories |
||||
644 | ): string { |
||||
645 | $relationsRootFqn = $projectRootNamespace |
||||
646 | 3 | . AbstractGenerator::ENTITY_RELATIONS_NAMESPACE . '\\'; |
|||
647 | 3 | if (count($subDirectories) > 0) { |
|||
648 | 1 | $relationsRootFqn .= implode('\\', $subDirectories) . '\\'; |
|||
649 | } |
||||
650 | |||||
651 | 3 | return $this->tidy($relationsRootFqn); |
|||
652 | } |
||||
653 | |||||
654 | /** |
||||
655 | * Normalise a has type, removing prefixes that are not required |
||||
656 | * |
||||
657 | * Inverse hasTypes use the standard template without the prefix |
||||
658 | * The exclusion ot this are the ManyToMany and OneToOne relations |
||||
659 | * |
||||
660 | * @param string $ownedHasName |
||||
661 | * @param string $hasType |
||||
662 | * |
||||
663 | * @return string |
||||
664 | */ |
||||
665 | 2 | public function getBaseHasTypeTraitFqn( |
|||
666 | string $ownedHasName, |
||||
667 | string $hasType |
||||
668 | ): string { |
||||
669 | 2 | $required = \ts\stringContains($hasType, RelationsGenerator::PREFIX_REQUIRED) |
|||
670 | 1 | ? RelationsGenerator::PREFIX_REQUIRED |
|||
671 | 2 | : ''; |
|||
672 | |||||
673 | 2 | $hasType = str_replace(RelationsGenerator::PREFIX_REQUIRED, '', $hasType); |
|||
674 | foreach ( |
||||
675 | [ |
||||
676 | 2 | RelationsGenerator::INTERNAL_TYPE_MANY_TO_MANY, |
|||
677 | 2 | RelationsGenerator::INTERNAL_TYPE_ONE_TO_ONE, |
|||
678 | ] as $noStrip |
||||
679 | ) { |
||||
680 | 2 | if (\ts\stringContains($hasType, $noStrip)) { |
|||
681 | 2 | return 'Has' . $required . $ownedHasName . $hasType; |
|||
682 | } |
||||
683 | } |
||||
684 | |||||
685 | foreach ( |
||||
686 | [ |
||||
687 | 1 | RelationsGenerator::INTERNAL_TYPE_ONE_TO_MANY, |
|||
688 | 1 | RelationsGenerator::INTERNAL_TYPE_MANY_TO_ONE, |
|||
689 | ] as $stripAll |
||||
690 | ) { |
||||
691 | 1 | if (\ts\stringContains($hasType, $stripAll)) { |
|||
692 | 1 | return str_replace( |
|||
693 | [ |
||||
694 | 1 | RelationsGenerator::PREFIX_OWNING, |
|||
695 | 1 | RelationsGenerator::PREFIX_INVERSE, |
|||
696 | ], |
||||
697 | 1 | '', |
|||
698 | 1 | 'Has' . $required . $ownedHasName . $hasType |
|||
699 | ); |
||||
700 | } |
||||
701 | } |
||||
702 | |||||
703 | return str_replace( |
||||
704 | [ |
||||
705 | RelationsGenerator::PREFIX_INVERSE, |
||||
706 | ], |
||||
707 | '', |
||||
708 | 'Has' . $required . $ownedHasName . $hasType |
||||
709 | ); |
||||
710 | } |
||||
711 | |||||
712 | 3 | public function getFactoryFqnFromEntityFqn(string $entityFqn): string |
|||
713 | { |
||||
714 | 3 | return $this->tidy( |
|||
715 | 3 | str_replace( |
|||
716 | 3 | '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\', |
|||
717 | 3 | '\\' . AbstractGenerator::ENTITY_FACTORIES_NAMESPACE . '\\', |
|||
718 | 3 | $entityFqn |
|||
719 | 3 | ) . 'Factory' |
|||
720 | ); |
||||
721 | } |
||||
722 | |||||
723 | 3 | public function getDtoFactoryFqnFromEntityFqn(string $entityFqn): string |
|||
724 | { |
||||
725 | 3 | return $this->tidy( |
|||
726 | 3 | str_replace( |
|||
727 | 3 | '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\', |
|||
728 | 3 | '\\' . AbstractGenerator::ENTITY_FACTORIES_NAMESPACE . '\\', |
|||
729 | 3 | $entityFqn |
|||
730 | 3 | ) . 'DtoFactory' |
|||
731 | ); |
||||
732 | } |
||||
733 | |||||
734 | 1 | public function getRepositoryqnFromEntityFqn(string $entityFqn): string |
|||
735 | { |
||||
736 | 1 | return $this->tidy( |
|||
737 | 1 | str_replace( |
|||
738 | 1 | '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME . '\\', |
|||
739 | 1 | '\\' . AbstractGenerator::ENTITY_REPOSITORIES_NAMESPACE . '\\', |
|||
740 | 1 | $entityFqn |
|||
741 | 1 | ) . 'Repository' |
|||
742 | ); |
||||
743 | } |
||||
744 | |||||
745 | /** |
||||
746 | * @param string $ownedEntityFqn |
||||
747 | * @param string $srcOrTestSubFolder |
||||
748 | * @param string $projectRootNamespace |
||||
749 | * |
||||
750 | * @return string |
||||
751 | * @throws DoctrineStaticMetaException |
||||
752 | */ |
||||
753 | 1 | public function getReciprocatedHasName( |
|||
754 | string $ownedEntityFqn, |
||||
755 | string $srcOrTestSubFolder, |
||||
756 | string $projectRootNamespace |
||||
757 | ): string { |
||||
758 | 1 | $parsedFqn = $this->parseFullyQualifiedName( |
|||
759 | 1 | $ownedEntityFqn, |
|||
760 | 1 | $srcOrTestSubFolder, |
|||
761 | 1 | $projectRootNamespace |
|||
762 | ); |
||||
763 | |||||
764 | 1 | $subDirectories = $parsedFqn[2]; |
|||
765 | |||||
766 | 1 | return $this->getSingularNamespacedName($ownedEntityFqn, $subDirectories); |
|||
767 | } |
||||
768 | |||||
769 | /** |
||||
770 | * Get the Fully Qualified Namespace for the Relation Interface for a specific Entity and hasType |
||||
771 | * |
||||
772 | * @param string $hasType |
||||
773 | * @param string $ownedEntityFqn |
||||
774 | * @param string|null $projectRootNamespace |
||||
775 | * @param string $srcFolder |
||||
776 | * |
||||
777 | * @return string |
||||
778 | * @throws DoctrineStaticMetaException |
||||
779 | */ |
||||
780 | 2 | public function getOwningInterfaceFqn( |
|||
781 | string $hasType, |
||||
782 | string $ownedEntityFqn, |
||||
783 | string $projectRootNamespace = null, |
||||
784 | string $srcFolder = AbstractCommand::DEFAULT_SRC_SUBFOLDER |
||||
785 | ): string { |
||||
786 | try { |
||||
787 | 2 | $ownedHasName = $this->getOwnedHasName($hasType, $ownedEntityFqn, $srcFolder, $projectRootNamespace); |
|||
0 ignored issues
–
show
It seems like
$projectRootNamespace can also be of type null ; however, parameter $projectRootNamespace of EdmondsCommerce\Doctrine...lper::getOwnedHasName() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
788 | 2 | if (null === $projectRootNamespace) { |
|||
789 | $projectRootNamespace = $this->getProjectRootNamespaceFromComposerJson($srcFolder); |
||||
790 | } |
||||
791 | 2 | list($ownedClassName, , $ownedSubDirectories) = $this->parseFullyQualifiedName( |
|||
792 | 2 | $ownedEntityFqn, |
|||
793 | 2 | $srcFolder, |
|||
794 | 2 | $projectRootNamespace |
|||
795 | ); |
||||
796 | 2 | $interfaceSubDirectories = array_slice($ownedSubDirectories, 2); |
|||
797 | 2 | $owningInterfaceFqn = $this->getOwningRelationsRootFqn( |
|||
798 | 2 | $projectRootNamespace, |
|||
799 | 2 | $interfaceSubDirectories |
|||
800 | ); |
||||
801 | 2 | $required = \ts\stringContains($hasType, RelationsGenerator::PREFIX_REQUIRED) |
|||
802 | 1 | ? 'Required' |
|||
803 | 2 | : ''; |
|||
804 | $owningInterfaceFqn .= '\\' . |
||||
805 | 2 | $ownedClassName . |
|||
806 | 2 | '\\Interfaces\\Has' . |
|||
807 | 2 | $required . |
|||
808 | 2 | $ownedHasName . |
|||
809 | 2 | 'Interface'; |
|||
810 | |||||
811 | 2 | return $this->tidy($owningInterfaceFqn); |
|||
812 | } catch (Exception $e) { |
||||
813 | throw new DoctrineStaticMetaException( |
||||
814 | 'Exception in ' . __METHOD__ . ': ' . $e->getMessage(), |
||||
815 | $e->getCode(), |
||||
816 | $e |
||||
817 | ); |
||||
818 | } |
||||
819 | } |
||||
820 | |||||
821 | 1 | public function getEntityInterfaceFromEntityFqn(string $entityFqn): string |
|||
822 | { |
||||
823 | 1 | return str_replace( |
|||
824 | 1 | '\\Entities\\', |
|||
825 | 1 | '\\Entity\\Interfaces\\', |
|||
826 | 1 | $entityFqn |
|||
827 | 1 | ) . 'Interface'; |
|||
828 | } |
||||
829 | |||||
830 | 2 | public function getEntityFqnFromEntityInterfaceFqn(string $entityInterfaceFqn): string |
|||
831 | { |
||||
832 | 2 | return substr( |
|||
833 | 2 | str_replace( |
|||
834 | 2 | '\\Entity\\Interfaces\\', |
|||
835 | 2 | '\\Entities\\', |
|||
836 | 2 | $entityInterfaceFqn |
|||
837 | ), |
||||
838 | 2 | 0, |
|||
839 | 2 | -strlen('Interface') |
|||
840 | ); |
||||
841 | } |
||||
842 | |||||
843 | 2 | public function getEntityFqnFromEntityFactoryFqn(string $entityFactoryFqn): string |
|||
844 | { |
||||
845 | 2 | return substr( |
|||
846 | 2 | str_replace( |
|||
847 | 2 | '\\Entity\\Factories\\', |
|||
848 | 2 | '\\Entities\\', |
|||
849 | 2 | $entityFactoryFqn |
|||
850 | ), |
||||
851 | 2 | 0, |
|||
852 | 2 | -strlen('Factory') |
|||
853 | ); |
||||
854 | } |
||||
855 | |||||
856 | 2 | public function getEntityFqnFromEntityDtoFactoryFqn(string $entityDtoFactoryFqn): string |
|||
857 | { |
||||
858 | 2 | return substr( |
|||
859 | 2 | str_replace( |
|||
860 | 2 | '\\Entity\\Factories\\', |
|||
861 | 2 | '\\Entities\\', |
|||
862 | 2 | $entityDtoFactoryFqn |
|||
863 | ), |
||||
864 | 2 | 0, |
|||
865 | 2 | -strlen('DtoFactory') |
|||
866 | ); |
||||
867 | } |
||||
868 | |||||
869 | 3 | public function getEntityDtoFqnFromEntityFqn(string $entityFqn): string |
|||
870 | { |
||||
871 | 3 | return str_replace( |
|||
872 | 3 | '\\Entities\\', |
|||
873 | 3 | '\\Entity\\DataTransferObjects\\', |
|||
874 | 3 | $entityFqn |
|||
875 | 3 | ) . 'Dto'; |
|||
876 | } |
||||
877 | |||||
878 | /** |
||||
879 | * @param string $entityDtoFqn |
||||
880 | * |
||||
881 | * @return string |
||||
882 | * @deprecated please use the static method on the DTO directly |
||||
883 | * |
||||
884 | */ |
||||
885 | public function getEntityFqnFromEntityDtoFqn(string $entityDtoFqn): string |
||||
886 | { |
||||
887 | return $entityDtoFqn::getEntityFqn(); |
||||
888 | } |
||||
889 | |||||
890 | public function getEntityFqnFromEntityRepositoryFqn(string $entityRepositoryFqn): string |
||||
891 | { |
||||
892 | return substr( |
||||
893 | str_replace( |
||||
894 | '\\Entity\\Repositories\\', |
||||
895 | '\\Entities\\', |
||||
896 | $entityRepositoryFqn |
||||
897 | ), |
||||
898 | 0, |
||||
899 | -strlen('Repository') |
||||
900 | ); |
||||
901 | } |
||||
902 | |||||
903 | public function getEntityFqnFromEntitySaverFqn(string $entitySaverFqn): string |
||||
904 | { |
||||
905 | return substr( |
||||
906 | str_replace( |
||||
907 | '\\Entity\\Savers\\', |
||||
908 | '\\Entities\\', |
||||
909 | $entitySaverFqn |
||||
910 | ), |
||||
911 | 0, |
||||
912 | -strlen('Saver') |
||||
913 | ); |
||||
914 | } |
||||
915 | |||||
916 | 1 | public function getEntitySaverFqnFromEntityFqn(string $entityFqn): string |
|||
917 | { |
||||
918 | 1 | return str_replace( |
|||
919 | 1 | '\\Entities\\', |
|||
920 | 1 | '\\Entity\\Savers\\', |
|||
921 | 1 | $entityFqn |
|||
922 | 1 | ) . 'Saver'; |
|||
923 | } |
||||
924 | |||||
925 | 2 | public function getEntityFqnFromEntityUpserterFqn(string $entityUpserterFqn): string |
|||
926 | { |
||||
927 | 2 | return substr( |
|||
928 | 2 | str_replace( |
|||
929 | 2 | '\\Entity\\Savers\\', |
|||
930 | 2 | '\\Entities\\', |
|||
931 | 2 | $entityUpserterFqn |
|||
932 | ), |
||||
933 | 2 | 0, |
|||
934 | 2 | -strlen('Upserter') |
|||
935 | ); |
||||
936 | } |
||||
937 | |||||
938 | 3 | public function getEntityUpserterFqnFromEntityFqn(string $entityFqn): string |
|||
939 | { |
||||
940 | 3 | return str_replace( |
|||
941 | 3 | '\\Entities\\', |
|||
942 | 3 | '\\Entity\\Savers\\', |
|||
943 | 3 | $entityFqn |
|||
944 | 3 | ) . 'Upserter'; |
|||
945 | } |
||||
946 | |||||
947 | 2 | public function getEntityFqnFromEntityUnitOfWorkHelperFqn(string $entityUnitofWorkHelperFqn): string |
|||
948 | { |
||||
949 | 2 | return substr( |
|||
950 | 2 | str_replace( |
|||
951 | 2 | '\\Entity\\Savers\\', |
|||
952 | 2 | '\\Entities\\', |
|||
953 | 2 | $entityUnitofWorkHelperFqn |
|||
954 | ), |
||||
955 | 2 | 0, |
|||
956 | 2 | -strlen('UnitOfWorkHelper') |
|||
957 | ); |
||||
958 | } |
||||
959 | |||||
960 | 3 | public function getEntityUnitOfWorkHelperFqnFromEntityFqn(string $entityFqn): string |
|||
961 | { |
||||
962 | 3 | return str_replace( |
|||
963 | 3 | '\\Entities\\', |
|||
964 | 3 | '\\Entity\\Savers\\', |
|||
965 | 3 | $entityFqn |
|||
966 | 3 | ) . 'UnitOfWorkHelper'; |
|||
967 | } |
||||
968 | |||||
969 | public function getEntityFqnFromEntityTestFqn(string $entityTestFqn): string |
||||
970 | { |
||||
971 | return substr( |
||||
972 | $entityTestFqn, |
||||
973 | 0, |
||||
974 | -strlen('Test') |
||||
975 | ); |
||||
976 | } |
||||
977 | |||||
978 | 1 | public function getEntityTestFqnFromEntityFqn(string $entityFqn): string |
|||
979 | { |
||||
980 | 1 | return $entityFqn . 'Test'; |
|||
981 | } |
||||
982 | } |
||||
983 |