1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator; |
4
|
|
|
|
5
|
|
|
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException; |
6
|
|
|
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper; |
7
|
|
|
use gossi\codegen\generator\CodeFileGenerator; |
8
|
|
|
use gossi\codegen\model\PhpClass; |
9
|
|
|
use gossi\codegen\model\PhpInterface; |
10
|
|
|
use gossi\codegen\model\PhpTrait; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Class RelationsGenerator |
14
|
|
|
* |
15
|
|
|
* @package EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator |
16
|
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
17
|
|
|
*/ |
18
|
|
|
class RelationsGenerator extends AbstractGenerator |
19
|
|
|
{ |
20
|
|
|
public const PREFIX_OWNING = 'Owning'; |
21
|
|
|
public const PREFIX_INVERSE = 'Inverse'; |
22
|
|
|
public const PREFIX_UNIDIRECTIONAL = 'Unidirectional'; |
23
|
|
|
|
24
|
|
|
|
25
|
|
|
/******************************************************************************************************************* |
26
|
|
|
* OneToOne - One instance of the current Entity refers to One instance of the referred Entity. |
27
|
|
|
*/ |
28
|
|
|
public const INTERNAL_TYPE_ONE_TO_ONE = 'OneToOne'; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityOwningOneToOne.php |
32
|
|
|
*/ |
33
|
|
|
public const HAS_ONE_TO_ONE = self::PREFIX_OWNING.self::INTERNAL_TYPE_ONE_TO_ONE; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityInverseOneToOne.php |
37
|
|
|
*/ |
38
|
|
|
public const HAS_INVERSE_ONE_TO_ONE = self::PREFIX_INVERSE.self::INTERNAL_TYPE_ONE_TO_ONE; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityUnidrectionalOneToOne.php |
42
|
|
|
*/ |
43
|
|
|
public const HAS_UNIDIRECTIONAL_ONE_TO_ONE = self::PREFIX_UNIDIRECTIONAL.self::INTERNAL_TYPE_ONE_TO_ONE; |
44
|
|
|
|
45
|
|
|
|
46
|
|
|
/******************************************************************************************************************* |
47
|
|
|
* OneToMany - One instance of the current Entity has Many instances (references) to the referred Entity. |
48
|
|
|
*/ |
49
|
|
|
public const INTERNAL_TYPE_ONE_TO_MANY = 'OneToMany'; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOneToMany.php |
53
|
|
|
*/ |
54
|
|
|
public const HAS_ONE_TO_MANY = self::INTERNAL_TYPE_ONE_TO_MANY; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOneToMany.php |
58
|
|
|
*/ |
59
|
|
|
public const HAS_UNIDIRECTIONAL_ONE_TO_MANY = self::PREFIX_UNIDIRECTIONAL.self::INTERNAL_TYPE_ONE_TO_MANY; |
60
|
|
|
|
61
|
|
|
|
62
|
|
|
/******************************************************************************************************************* |
63
|
|
|
* ManyToOne - Many instances of the current Entity refer to One instance of the referred Entity. |
64
|
|
|
*/ |
65
|
|
|
public const INTERNAL_TYPE_MANY_TO_ONE = 'ManyToOne'; |
66
|
|
|
/** |
67
|
|
|
* @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityManyToOne.php |
68
|
|
|
*/ |
69
|
|
|
public const HAS_MANY_TO_ONE = self::INTERNAL_TYPE_MANY_TO_ONE; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityManyToOne.php |
73
|
|
|
*/ |
74
|
|
|
public const HAS_UNIDIRECTIONAL_MANY_TO_ONE = self::PREFIX_UNIDIRECTIONAL.self::INTERNAL_TYPE_MANY_TO_ONE; |
75
|
|
|
|
76
|
|
|
|
77
|
|
|
/******************************************************************************************************************* |
78
|
|
|
* ManyToMany - Many instances of the current Entity refer to Many instance of the referred Entity. |
79
|
|
|
*/ |
80
|
|
|
public const INTERNAL_TYPE_MANY_TO_MANY = 'ManyToMany'; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOwningManyToMany.php |
84
|
|
|
*/ |
85
|
|
|
public const HAS_MANY_TO_MANY = self::PREFIX_OWNING.self::INTERNAL_TYPE_MANY_TO_MANY; |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesInverseManyToMany.php |
89
|
|
|
*/ |
90
|
|
|
public const HAS_INVERSE_MANY_TO_MANY = self::PREFIX_INVERSE.self::INTERNAL_TYPE_MANY_TO_MANY; |
91
|
|
|
|
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* The full list of possible relation types |
95
|
|
|
*/ |
96
|
|
|
public const HAS_TYPES = [ |
97
|
|
|
self::HAS_ONE_TO_ONE, |
98
|
|
|
self::HAS_INVERSE_ONE_TO_ONE, |
99
|
|
|
self::HAS_UNIDIRECTIONAL_ONE_TO_ONE, |
100
|
|
|
self::HAS_ONE_TO_MANY, |
101
|
|
|
self::HAS_UNIDIRECTIONAL_ONE_TO_MANY, |
102
|
|
|
self::HAS_MANY_TO_ONE, |
103
|
|
|
self::HAS_UNIDIRECTIONAL_MANY_TO_ONE, |
104
|
|
|
self::HAS_MANY_TO_MANY, |
105
|
|
|
self::HAS_INVERSE_MANY_TO_MANY, |
106
|
|
|
]; |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Of the full list, which ones will be automatically reciprocated in the generated code |
110
|
|
|
*/ |
111
|
|
|
public const HAS_TYPES_RECIPROCATED = [ |
112
|
|
|
self::HAS_ONE_TO_ONE, |
113
|
|
|
self::HAS_INVERSE_ONE_TO_ONE, |
114
|
|
|
self::HAS_ONE_TO_MANY, |
115
|
|
|
self::HAS_MANY_TO_ONE, |
116
|
|
|
self::HAS_MANY_TO_MANY, |
117
|
|
|
self::HAS_INVERSE_MANY_TO_MANY, |
118
|
|
|
]; |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
*Of the full list, which ones are unidirectional (i.e not reciprocated) |
122
|
|
|
*/ |
123
|
|
|
public const HAS_TYPES_UNIDIRECTIONAL = [ |
124
|
|
|
self::HAS_UNIDIRECTIONAL_MANY_TO_ONE, |
125
|
|
|
self::HAS_UNIDIRECTIONAL_ONE_TO_MANY, |
126
|
|
|
self::HAS_UNIDIRECTIONAL_ONE_TO_ONE, |
127
|
|
|
]; |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Of the full list, which ones are a plural relationship, i.e they have multiple of the related entity |
131
|
|
|
*/ |
132
|
|
|
public const HAS_TYPES_PLURAL = [ |
133
|
|
|
self::HAS_MANY_TO_MANY, |
134
|
|
|
self::HAS_INVERSE_MANY_TO_MANY, |
135
|
|
|
self::HAS_ONE_TO_MANY, |
136
|
|
|
self::HAS_UNIDIRECTIONAL_ONE_TO_MANY, |
137
|
|
|
]; |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Generator that yields relative paths of all the files in the relations template path and the SplFileInfo objects |
141
|
|
|
* |
142
|
|
|
* Use a PHP Generator to iterate over a recursive iterator iterator and then yield: |
143
|
|
|
* - key: string $relativePath |
144
|
|
|
* - value: \SplFileInfo $fileInfo |
145
|
|
|
* |
146
|
|
|
* The `finally` step unsets the recursiveIterator once everything is done |
147
|
|
|
* |
148
|
|
|
* @return \Generator |
149
|
|
|
*/ |
150
|
|
|
public function getRelativePathRelationsGenerator(): \Generator |
151
|
|
|
{ |
152
|
|
|
try { |
153
|
|
|
$recursiveIterator = new \RecursiveIteratorIterator( |
154
|
|
|
new \RecursiveDirectoryIterator( |
155
|
|
|
\realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH), |
156
|
|
|
\RecursiveDirectoryIterator::SKIP_DOTS |
157
|
|
|
), |
158
|
|
|
\RecursiveIteratorIterator::SELF_FIRST |
159
|
|
|
); |
160
|
|
|
foreach ($recursiveIterator as $path => $fileInfo) { |
161
|
|
|
$relativePath = rtrim( |
162
|
|
|
$this->getFilesystem()->makePathRelative( |
163
|
|
|
$path, |
164
|
|
|
\realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH) |
165
|
|
|
), |
166
|
|
|
'/' |
167
|
|
|
); |
168
|
|
|
yield $relativePath => $fileInfo; |
169
|
|
|
} |
170
|
|
|
} finally { |
171
|
|
|
$recursiveIterator = null; |
172
|
|
|
unset($recursiveIterator); |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Generate the relation traits for specified Entity |
179
|
|
|
* |
180
|
|
|
* This works by copying the template traits folder over and then updating the file contents, name and path |
181
|
|
|
* |
182
|
|
|
* @param string $entityFqn Fully Qualified Name of Entity |
183
|
|
|
* |
184
|
|
|
* @throws DoctrineStaticMetaException |
185
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
186
|
|
|
*/ |
187
|
|
|
public function generateRelationCodeForEntity(string $entityFqn) |
188
|
|
|
{ |
189
|
|
|
try { |
190
|
|
|
list($className, , $subDirsNoEntities) = $this->parseFullyQualifiedName($entityFqn); |
191
|
|
|
|
192
|
|
|
$singularNamespacedName = $this->namespaceHelper->getSingularNamespacedName($entityFqn, $subDirsNoEntities); |
193
|
|
|
$pluralNamespacedName = $this->namespaceHelper->getPluralNamespacedName($entityFqn, $subDirsNoEntities); |
194
|
|
|
|
195
|
|
|
$subDirsNoEntities = \array_slice($subDirsNoEntities, 2); |
196
|
|
|
$destinationDirectory = $this->codeHelper->resolvePath( |
197
|
|
|
$this->pathToProjectRoot |
198
|
|
|
.'/'.$this->srcSubFolderName |
199
|
|
|
.AbstractGenerator::ENTITY_RELATIONS_FOLDER_NAME |
200
|
|
|
.\implode( |
201
|
|
|
'/', |
202
|
|
|
$subDirsNoEntities |
203
|
|
|
) |
204
|
|
|
.'/'.$className |
205
|
|
|
); |
206
|
|
|
|
207
|
|
|
$this->copyTemplateDirectoryAndGetPath( |
208
|
|
|
AbstractGenerator::RELATIONS_TEMPLATE_PATH, |
209
|
|
|
$destinationDirectory |
210
|
|
|
); |
211
|
|
|
|
212
|
|
|
$plural = \ucfirst(MappingHelper::getPluralForFqn($entityFqn)); |
213
|
|
|
$singular = \ucfirst(MappingHelper::getSingularForFqn($entityFqn)); |
214
|
|
|
$nsNoEntities = \implode('\\', $subDirsNoEntities); |
215
|
|
|
$singularWithNs = \ltrim($nsNoEntities.'\\'.$singular, '\\'); |
216
|
|
|
$pluralWithNs = \ltrim($nsNoEntities.'\\'.$plural, '\\'); |
217
|
|
|
$dirsToRename = []; |
218
|
|
|
$filesCreated = []; |
219
|
|
|
//update file contents apart from namespace |
220
|
|
|
foreach ($this->getRelativePathRelationsGenerator() as $path => $fileInfo) { |
221
|
|
|
$realPath = \realpath("$destinationDirectory/$path"); |
222
|
|
|
if (false === $realPath) { |
223
|
|
|
throw new \RuntimeException("path $destinationDirectory/$path does not exist"); |
224
|
|
|
} |
225
|
|
|
$path = $realPath; |
226
|
|
|
if (!$fileInfo->isDir()) { |
227
|
|
|
$this->findReplace( |
228
|
|
|
'use '.self::FIND_ENTITIES_NAMESPACE.'\\'.self::FIND_ENTITY_NAME, |
229
|
|
|
"use $entityFqn", |
230
|
|
|
$path |
231
|
|
|
); |
232
|
|
|
$this->findReplaceRegex( |
233
|
|
|
'%use(.+?)Relations\\\TemplateEntity(.+?);%', |
234
|
|
|
'use ${1}Relations\\'.$singularWithNs.'${2};', |
235
|
|
|
$path |
236
|
|
|
); |
237
|
|
|
$this->findReplaceRegex( |
238
|
|
|
'%use(.+?)Relations\\\TemplateEntity(.+?);%', |
239
|
|
|
'use ${1}Relations\\'.$pluralWithNs.'${2};', |
240
|
|
|
$path |
241
|
|
|
); |
242
|
|
|
|
243
|
|
|
$this->replaceName($singularNamespacedName, $path); |
244
|
|
|
$this->replacePluralName($pluralNamespacedName, $path); |
245
|
|
|
$this->replaceProjectNamespace($this->projectRootNamespace, $path); |
246
|
|
|
$filesCreated[] = function () use ($path, $singularNamespacedName, $pluralNamespacedName) { |
247
|
|
|
return $this->renamePathBasenameSingularOrPlural( |
248
|
|
|
$path, |
249
|
|
|
$singularNamespacedName, |
250
|
|
|
$pluralNamespacedName |
251
|
|
|
); |
252
|
|
|
}; |
253
|
|
|
continue; |
254
|
|
|
} |
255
|
|
|
$dirsToRename[] = $path; |
256
|
|
|
} |
257
|
|
|
foreach ($filesCreated as $k => $closure) { |
258
|
|
|
$filesCreated[$k] = $closure(); |
259
|
|
|
} |
260
|
|
|
//update directory names and update file created paths accordingly |
261
|
|
|
foreach ($dirsToRename as $dirPath) { |
262
|
|
|
$updateDirPath = $this->renamePathBasenameSingularOrPlural( |
263
|
|
|
$dirPath, |
264
|
|
|
$singularNamespacedName, |
265
|
|
|
$pluralNamespacedName |
266
|
|
|
); |
267
|
|
|
foreach ($filesCreated as $k => $filePath) { |
268
|
|
|
$filesCreated[$k] = \str_replace($dirPath, $updateDirPath, $filePath); |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
//now path is totally sorted, update namespace based on path |
272
|
|
|
foreach ($filesCreated as $filePath) { |
273
|
|
|
$this->setNamespaceFromPath($filePath); |
274
|
|
|
} |
275
|
|
|
} catch (\Exception $e) { |
276
|
|
|
throw new DoctrineStaticMetaException( |
277
|
|
|
'Exception generating relation for entity '.$entityFqn.': '.$e->getMessage(), |
278
|
|
|
$e->getCode(), |
279
|
|
|
$e |
280
|
|
|
); |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Add the specified interface to the specified class |
286
|
|
|
* |
287
|
|
|
* @param string $classPath |
288
|
|
|
* @param string $interfacePath |
289
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
290
|
|
|
*/ |
291
|
|
|
protected function useRelationInterfaceInClass(string $classPath, string $interfacePath) |
292
|
|
|
{ |
293
|
|
|
$class = PhpClass::fromFile($classPath); |
294
|
|
|
$interface = PhpInterface::fromFile($interfacePath); |
295
|
|
|
$class->addInterface($interface); |
296
|
|
|
$this->codeHelper->generate($class, $classPath); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Add the specified trait to the specified class |
301
|
|
|
* |
302
|
|
|
* @param string $classPath |
303
|
|
|
* @param string $traitPath |
304
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
305
|
|
|
*/ |
306
|
|
|
protected function useRelationTraitInClass(string $classPath, string $traitPath) |
307
|
|
|
{ |
308
|
|
|
$class = PhpClass::fromFile($classPath); |
309
|
|
|
$trait = PhpTrait::fromFile($traitPath); |
310
|
|
|
$class->addTrait($trait); |
311
|
|
|
$this->codeHelper->generate($class, $classPath); |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* Get the absolute paths for the owning traits and interfaces for the specified relation type |
316
|
|
|
* Will ensure that the files exists |
317
|
|
|
* |
318
|
|
|
* @param string $hasType |
319
|
|
|
* @param string $ownedEntityFqn |
320
|
|
|
* |
321
|
|
|
* @return array [ |
322
|
|
|
* $owningTraitPath, |
323
|
|
|
* $owningInterfacePath, |
324
|
|
|
* $reciprocatingInterfacePath |
325
|
|
|
* ] |
326
|
|
|
* @throws DoctrineStaticMetaException |
327
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
328
|
|
|
*/ |
329
|
|
|
protected function getPathsForOwningTraitsAndInterfaces(string $hasType, string $ownedEntityFqn): array |
330
|
|
|
{ |
331
|
|
|
try { |
332
|
|
|
$ownedHasName = $this->namespaceHelper->getOwnedHasName( |
333
|
|
|
$hasType, |
334
|
|
|
$ownedEntityFqn, |
335
|
|
|
$this->srcSubFolderName, |
336
|
|
|
$this->projectRootNamespace |
337
|
|
|
); |
338
|
|
|
$reciprocatedHasName = $this->namespaceHelper->getReciprocatedHasName( |
339
|
|
|
$ownedEntityFqn, |
340
|
|
|
$this->srcSubFolderName, |
341
|
|
|
$this->projectRootNamespace |
342
|
|
|
); |
343
|
|
|
$owningTraitFqn = $this->getOwningTraitFqn($hasType, $ownedEntityFqn); |
344
|
|
|
list($traitName, , $traitSubDirsNoEntities) = $this->parseFullyQualifiedName($owningTraitFqn); |
345
|
|
|
$owningTraitPath = $this->getPathFromNameAndSubDirs($traitName, $traitSubDirsNoEntities); |
346
|
|
|
if (!\file_exists($owningTraitPath)) { |
347
|
|
|
$this->generateRelationCodeForEntity($ownedEntityFqn); |
348
|
|
|
} |
349
|
|
|
$owningInterfaceFqn = $this->getOwningInterfaceFqn($hasType, $ownedEntityFqn); |
350
|
|
|
list($interfaceName, , $interfaceSubDirsNoEntities) = $this->parseFullyQualifiedName($owningInterfaceFqn); |
351
|
|
|
$owningInterfacePath = $this->getPathFromNameAndSubDirs($interfaceName, $interfaceSubDirsNoEntities); |
352
|
|
|
$reciprocatingInterfacePath = \str_replace( |
353
|
|
|
'Has'.$ownedHasName, |
354
|
|
|
'Reciprocates'.$reciprocatedHasName, |
355
|
|
|
$owningInterfacePath |
356
|
|
|
); |
357
|
|
|
|
358
|
|
|
return [ |
359
|
|
|
$owningTraitPath, |
360
|
|
|
$owningInterfacePath, |
361
|
|
|
$reciprocatingInterfacePath, |
362
|
|
|
]; |
363
|
|
|
} catch (\Exception $e) { |
364
|
|
|
throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e); |
365
|
|
|
} |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* @param string $hasType |
370
|
|
|
* @param string $ownedEntityFqn |
371
|
|
|
* |
372
|
|
|
* @return string |
373
|
|
|
* @throws DoctrineStaticMetaException |
374
|
|
|
*/ |
375
|
|
|
public function getOwningTraitFqn(string $hasType, string $ownedEntityFqn): string |
376
|
|
|
{ |
377
|
|
|
return $this->namespaceHelper->getOwningTraitFqn( |
378
|
|
|
$hasType, |
379
|
|
|
$ownedEntityFqn, |
380
|
|
|
$this->projectRootNamespace, |
381
|
|
|
$this->srcSubFolderName |
382
|
|
|
); |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
/** |
386
|
|
|
* @param string $hasType |
387
|
|
|
* @param string $ownedEntityFqn |
388
|
|
|
* |
389
|
|
|
* @return string |
390
|
|
|
* @throws DoctrineStaticMetaException |
391
|
|
|
*/ |
392
|
|
|
public function getOwningInterfaceFqn(string $hasType, string $ownedEntityFqn): string |
393
|
|
|
{ |
394
|
|
|
return $this->namespaceHelper->getOwningInterfaceFqn( |
395
|
|
|
$hasType, |
396
|
|
|
$ownedEntityFqn, |
397
|
|
|
$this->projectRootNamespace, |
398
|
|
|
$this->srcSubFolderName |
399
|
|
|
); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* @param string $hasType |
404
|
|
|
* |
405
|
|
|
* @throws \InvalidArgumentException |
406
|
|
|
*/ |
407
|
|
|
protected function validateHasType(string $hasType) |
408
|
|
|
{ |
409
|
|
|
if (!\in_array($hasType, static::HAS_TYPES, true)) { |
410
|
|
|
throw new \InvalidArgumentException( |
411
|
|
|
'Invalid $hasType '.$hasType.', must be one of: ' |
412
|
|
|
.\print_r(static::HAS_TYPES, true) |
413
|
|
|
); |
414
|
|
|
} |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Set a relationship from one Entity to Another Entity. |
419
|
|
|
* |
420
|
|
|
* Also used internally to set the reciprocal side. Uses an undocumented 4th bool parameter to kill recursion. |
421
|
|
|
* |
422
|
|
|
* @param string $owningEntityFqn |
423
|
|
|
* @param string $hasType |
424
|
|
|
* @param string $ownedEntityFqn |
425
|
|
|
* @param bool $reciprocate |
426
|
|
|
* |
427
|
|
|
* @throws DoctrineStaticMetaException |
428
|
|
|
* @SuppressWarnings(PHPMD.BooleanArgumentFlag) |
429
|
|
|
*/ |
430
|
|
|
public function setEntityHasRelationToEntity( |
431
|
|
|
string $owningEntityFqn, |
432
|
|
|
string $hasType, |
433
|
|
|
string $ownedEntityFqn, |
434
|
|
|
bool $reciprocate = true |
435
|
|
|
) { |
436
|
|
|
try { |
437
|
|
|
$this->validateHasType($hasType); |
438
|
|
|
list( |
439
|
|
|
$owningTraitPath, |
440
|
|
|
$owningInterfacePath, |
441
|
|
|
$reciprocatingInterfacePath, |
442
|
|
|
) = $this->getPathsForOwningTraitsAndInterfaces( |
443
|
|
|
$hasType, |
444
|
|
|
$ownedEntityFqn |
445
|
|
|
); |
446
|
|
|
list($owningClass, , $owningClassSubDirs) = $this->parseFullyQualifiedName($owningEntityFqn); |
447
|
|
|
$owningClassPath = $this->getPathFromNameAndSubDirs($owningClass, $owningClassSubDirs); |
448
|
|
|
$this->useRelationTraitInClass($owningClassPath, $owningTraitPath); |
449
|
|
|
$this->useRelationInterfaceInClass($owningClassPath, $owningInterfacePath); |
450
|
|
|
if (\in_array($hasType, self::HAS_TYPES_RECIPROCATED, true)) { |
451
|
|
|
$this->useRelationInterfaceInClass($owningClassPath, $reciprocatingInterfacePath); |
452
|
|
|
if (true === $reciprocate) { |
453
|
|
|
$inverseType = $this->getInverseHasType($hasType); |
454
|
|
|
$this->setEntityHasRelationToEntity( |
455
|
|
|
$ownedEntityFqn, |
456
|
|
|
$inverseType, |
457
|
|
|
$owningEntityFqn, |
458
|
|
|
false |
459
|
|
|
); |
460
|
|
|
} |
461
|
|
|
} |
462
|
|
|
} catch (\Exception $e) { |
463
|
|
|
throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e); |
464
|
|
|
} |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Get the inverse of a hasType |
469
|
|
|
* |
470
|
|
|
* @param string $hasType |
471
|
|
|
* |
472
|
|
|
* @return string |
473
|
|
|
* @throws DoctrineStaticMetaException |
474
|
|
|
*/ |
475
|
|
|
protected function getInverseHasType(string $hasType): string |
476
|
|
|
{ |
477
|
|
|
switch ($hasType) { |
478
|
|
|
case self::HAS_ONE_TO_ONE: |
479
|
|
|
case self::HAS_MANY_TO_MANY: |
480
|
|
|
return \str_replace( |
481
|
|
|
self::PREFIX_OWNING, |
482
|
|
|
self::PREFIX_INVERSE, |
483
|
|
|
$hasType |
484
|
|
|
); |
485
|
|
|
|
486
|
|
|
case self::HAS_INVERSE_ONE_TO_ONE: |
487
|
|
|
case self::HAS_INVERSE_MANY_TO_MANY: |
488
|
|
|
return \str_replace( |
489
|
|
|
self::PREFIX_INVERSE, |
490
|
|
|
self::PREFIX_OWNING, |
491
|
|
|
$hasType |
492
|
|
|
); |
493
|
|
|
|
494
|
|
|
case self::HAS_MANY_TO_ONE: |
495
|
|
|
return self::HAS_ONE_TO_MANY; |
496
|
|
|
|
497
|
|
|
case self::HAS_ONE_TO_MANY: |
498
|
|
|
return self::HAS_MANY_TO_ONE; |
499
|
|
|
|
500
|
|
|
default: |
501
|
|
|
throw new DoctrineStaticMetaException( |
502
|
|
|
'invalid $hasType '.$hasType.' when trying to set the inverted relation' |
503
|
|
|
); |
504
|
|
|
} |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
|
508
|
|
|
/** |
509
|
|
|
* @param string $path |
510
|
|
|
* @param string $singular |
511
|
|
|
* @param string $plural |
512
|
|
|
* |
513
|
|
|
* @return string |
514
|
|
|
* @throws DoctrineStaticMetaException |
515
|
|
|
*/ |
516
|
|
|
protected function renamePathBasenameSingularOrPlural( |
517
|
|
|
string $path, |
518
|
|
|
string $singular, |
519
|
|
|
string $plural |
520
|
|
|
): string { |
521
|
|
|
$find = self::FIND_ENTITY_NAME; |
522
|
|
|
$replace = $singular; |
523
|
|
|
$basename = \basename($path); |
524
|
|
|
if (false !== \strpos($basename, self::FIND_ENTITY_NAME_PLURAL)) { |
525
|
|
|
$find = self::FIND_ENTITY_NAME_PLURAL; |
526
|
|
|
$replace = $plural; |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
return $this->renamePathBasename($find, $replace, $path); |
530
|
|
|
} |
531
|
|
|
} |
532
|
|
|
|