edmondscommerce /
doctrine-static-meta
| 1 | <?php declare(strict_types=1); |
||
| 2 | |||
| 3 | namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator; |
||
| 4 | |||
| 5 | use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Relations\GenerateRelationCodeForEntity; |
||
| 6 | use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException; |
||
| 7 | use gossi\codegen\model\PhpClass; |
||
| 8 | use gossi\codegen\model\PhpInterface; |
||
| 9 | use gossi\codegen\model\PhpTrait; |
||
| 10 | use PhpParser\Error; |
||
| 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 | public const PREFIX_REQUIRED = 'Required'; |
||
| 24 | |||
| 25 | |||
| 26 | /******************************************************************************************************************* |
||
| 27 | * OneToOne - One instance of the current Entity refers to One instance of the referred Entity. |
||
| 28 | */ |
||
| 29 | public const INTERNAL_TYPE_ONE_TO_ONE = 'OneToOne'; |
||
| 30 | |||
| 31 | /** |
||
| 32 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityOwningOneToOne.php |
||
| 33 | */ |
||
| 34 | public const HAS_ONE_TO_ONE = self::PREFIX_OWNING . self::INTERNAL_TYPE_ONE_TO_ONE; |
||
| 35 | |||
| 36 | /** |
||
| 37 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntity/HasRequiredTemplateEntityOwningOneToOne.php |
||
| 38 | */ |
||
| 39 | public const HAS_REQUIRED_ONE_TO_ONE = self::PREFIX_REQUIRED . self::PREFIX_OWNING . self::INTERNAL_TYPE_ONE_TO_ONE; |
||
| 40 | |||
| 41 | /** |
||
| 42 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityInverseOneToOne.php |
||
| 43 | */ |
||
| 44 | public const HAS_INVERSE_ONE_TO_ONE = self::PREFIX_INVERSE . self::INTERNAL_TYPE_ONE_TO_ONE; |
||
| 45 | |||
| 46 | /** |
||
| 47 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntity/HasRequiredTemplateEntityInverseOneToOne.php |
||
| 48 | */ |
||
| 49 | public const HAS_REQUIRED_INVERSE_ONE_TO_ONE = self::PREFIX_REQUIRED . |
||
| 50 | self::PREFIX_INVERSE . |
||
| 51 | self::INTERNAL_TYPE_ONE_TO_ONE; |
||
| 52 | |||
| 53 | /** |
||
| 54 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityUnidrectionalOneToOne.php |
||
| 55 | */ |
||
| 56 | public const HAS_UNIDIRECTIONAL_ONE_TO_ONE = self::PREFIX_UNIDIRECTIONAL . self::INTERNAL_TYPE_ONE_TO_ONE; |
||
| 57 | |||
| 58 | /** |
||
| 59 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntity/HasRequiredTemplateEntityUnidrectionalOneToOne.php |
||
| 60 | */ |
||
| 61 | public const HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_ONE = self::PREFIX_REQUIRED . |
||
| 62 | self::PREFIX_UNIDIRECTIONAL . |
||
| 63 | self::INTERNAL_TYPE_ONE_TO_ONE; |
||
| 64 | |||
| 65 | /******************************************************************************************************************* |
||
| 66 | * OneToMany - One instance of the current Entity has Many instances (references) to the referred Entity. |
||
| 67 | */ |
||
| 68 | public const INTERNAL_TYPE_ONE_TO_MANY = 'OneToMany'; |
||
| 69 | |||
| 70 | /** |
||
| 71 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOneToMany.php |
||
| 72 | */ |
||
| 73 | public const HAS_ONE_TO_MANY = self::INTERNAL_TYPE_ONE_TO_MANY; |
||
| 74 | |||
| 75 | /** |
||
| 76 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntities/HasRequiredTemplateEntitiesOneToMany.php |
||
| 77 | */ |
||
| 78 | public const HAS_REQUIRED_ONE_TO_MANY = self::PREFIX_REQUIRED . self::INTERNAL_TYPE_ONE_TO_MANY; |
||
| 79 | |||
| 80 | /** |
||
| 81 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOneToMany.php |
||
| 82 | */ |
||
| 83 | public const HAS_UNIDIRECTIONAL_ONE_TO_MANY = self::PREFIX_UNIDIRECTIONAL . self::INTERNAL_TYPE_ONE_TO_MANY; |
||
| 84 | |||
| 85 | /** |
||
| 86 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntities/HasRequiredTemplateEntitiesOneToMany.php |
||
| 87 | */ |
||
| 88 | public const HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_MANY = self::PREFIX_REQUIRED . |
||
| 89 | self::PREFIX_UNIDIRECTIONAL . |
||
| 90 | self::INTERNAL_TYPE_ONE_TO_MANY; |
||
| 91 | |||
| 92 | /******************************************************************************************************************* |
||
| 93 | * ManyToOne - Many instances of the current Entity refer to One instance of the referred Entity. |
||
| 94 | */ |
||
| 95 | public const INTERNAL_TYPE_MANY_TO_ONE = 'ManyToOne'; |
||
| 96 | |||
| 97 | /** |
||
| 98 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityManyToOne.php |
||
| 99 | */ |
||
| 100 | public const HAS_MANY_TO_ONE = self::INTERNAL_TYPE_MANY_TO_ONE; |
||
| 101 | |||
| 102 | /** |
||
| 103 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntity/HasRequiredTemplateEntityManyToOne.php |
||
| 104 | */ |
||
| 105 | public const HAS_REQUIRED_MANY_TO_ONE = self::PREFIX_REQUIRED . self::INTERNAL_TYPE_MANY_TO_ONE; |
||
| 106 | |||
| 107 | /** |
||
| 108 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntity/HasTemplateEntityManyToOne.php |
||
| 109 | */ |
||
| 110 | public const HAS_UNIDIRECTIONAL_MANY_TO_ONE = self::PREFIX_UNIDIRECTIONAL . self::INTERNAL_TYPE_MANY_TO_ONE; |
||
| 111 | |||
| 112 | /** |
||
| 113 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntity/HasRequiredTemplateEntityManyToOne.php |
||
| 114 | */ |
||
| 115 | public const HAS_REQUIRED_UNIDIRECTIONAL_MANY_TO_ONE = self::PREFIX_REQUIRED . |
||
| 116 | self::PREFIX_UNIDIRECTIONAL . |
||
| 117 | self::INTERNAL_TYPE_MANY_TO_ONE; |
||
| 118 | |||
| 119 | |||
| 120 | /******************************************************************************************************************* |
||
| 121 | * ManyToMany - Many instances of the current Entity refer to Many instance of the referred Entity. |
||
| 122 | */ |
||
| 123 | public const INTERNAL_TYPE_MANY_TO_MANY = 'ManyToMany'; |
||
| 124 | |||
| 125 | /** |
||
| 126 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesOwningManyToMany.php |
||
| 127 | */ |
||
| 128 | public const HAS_MANY_TO_MANY = self::PREFIX_OWNING . self::INTERNAL_TYPE_MANY_TO_MANY; |
||
| 129 | |||
| 130 | /** |
||
| 131 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntities/HasRequiredTemplateEntitiesOwningManyToMany.php |
||
| 132 | */ |
||
| 133 | public const HAS_REQUIRED_MANY_TO_MANY = self::PREFIX_REQUIRED . |
||
| 134 | self::PREFIX_OWNING . |
||
| 135 | self::INTERNAL_TYPE_MANY_TO_MANY; |
||
| 136 | /** |
||
| 137 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasTemplateEntities/HasTemplateEntitiesInverseManyToMany.php |
||
| 138 | */ |
||
| 139 | public const HAS_INVERSE_MANY_TO_MANY = self::PREFIX_INVERSE . self::INTERNAL_TYPE_MANY_TO_MANY; |
||
| 140 | |||
| 141 | /** |
||
| 142 | * @see codeTemplates/src/Entities/Traits/Relations/TemplateEntity/HasRequiredTemplateEntities/HasRequiredTemplateEntitiesInverseManyToMany.php |
||
| 143 | */ |
||
| 144 | public const HAS_REQUIRED_INVERSE_MANY_TO_MANY = self::PREFIX_REQUIRED . |
||
| 145 | self::PREFIX_INVERSE . |
||
| 146 | self::INTERNAL_TYPE_MANY_TO_MANY; |
||
| 147 | |||
| 148 | |||
| 149 | /** |
||
| 150 | * The full list of possible relation types |
||
| 151 | */ |
||
| 152 | public const HAS_TYPES = [ |
||
| 153 | self::HAS_ONE_TO_ONE, |
||
| 154 | self::HAS_INVERSE_ONE_TO_ONE, |
||
| 155 | self::HAS_UNIDIRECTIONAL_ONE_TO_ONE, |
||
| 156 | self::HAS_ONE_TO_MANY, |
||
| 157 | self::HAS_UNIDIRECTIONAL_ONE_TO_MANY, |
||
| 158 | self::HAS_MANY_TO_ONE, |
||
| 159 | self::HAS_UNIDIRECTIONAL_MANY_TO_ONE, |
||
| 160 | self::HAS_MANY_TO_MANY, |
||
| 161 | self::HAS_INVERSE_MANY_TO_MANY, |
||
| 162 | |||
| 163 | self::HAS_REQUIRED_ONE_TO_ONE, |
||
| 164 | self::HAS_REQUIRED_INVERSE_ONE_TO_ONE, |
||
| 165 | self::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_ONE, |
||
| 166 | self::HAS_REQUIRED_ONE_TO_MANY, |
||
| 167 | self::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_MANY, |
||
| 168 | self::HAS_REQUIRED_MANY_TO_ONE, |
||
| 169 | self::HAS_REQUIRED_UNIDIRECTIONAL_MANY_TO_ONE, |
||
| 170 | self::HAS_REQUIRED_MANY_TO_MANY, |
||
| 171 | self::HAS_REQUIRED_INVERSE_MANY_TO_MANY, |
||
| 172 | ]; |
||
| 173 | |||
| 174 | /** |
||
| 175 | * Of the full list, which ones will be automatically reciprocated in the generated code |
||
| 176 | */ |
||
| 177 | public const HAS_TYPES_RECIPROCATED = [ |
||
| 178 | self::HAS_ONE_TO_ONE, |
||
| 179 | self::HAS_INVERSE_ONE_TO_ONE, |
||
| 180 | self::HAS_ONE_TO_MANY, |
||
| 181 | self::HAS_MANY_TO_ONE, |
||
| 182 | self::HAS_MANY_TO_MANY, |
||
| 183 | self::HAS_INVERSE_MANY_TO_MANY, |
||
| 184 | |||
| 185 | self::HAS_REQUIRED_ONE_TO_ONE, |
||
| 186 | self::HAS_REQUIRED_INVERSE_ONE_TO_ONE, |
||
| 187 | self::HAS_REQUIRED_ONE_TO_MANY, |
||
| 188 | self::HAS_REQUIRED_MANY_TO_ONE, |
||
| 189 | self::HAS_REQUIRED_MANY_TO_MANY, |
||
| 190 | self::HAS_REQUIRED_INVERSE_MANY_TO_MANY, |
||
| 191 | ]; |
||
| 192 | |||
| 193 | /** |
||
| 194 | *Of the full list, which ones are unidirectional (i.e not reciprocated) |
||
| 195 | */ |
||
| 196 | public const HAS_TYPES_UNIDIRECTIONAL = [ |
||
| 197 | self::HAS_UNIDIRECTIONAL_MANY_TO_ONE, |
||
| 198 | self::HAS_UNIDIRECTIONAL_ONE_TO_MANY, |
||
| 199 | self::HAS_UNIDIRECTIONAL_ONE_TO_ONE, |
||
| 200 | |||
| 201 | self::HAS_REQUIRED_UNIDIRECTIONAL_MANY_TO_ONE, |
||
| 202 | self::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_MANY, |
||
| 203 | self::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_ONE, |
||
| 204 | ]; |
||
| 205 | |||
| 206 | /** |
||
| 207 | * Of the full list, which ones are a plural relationship, i.e they have multiple of the related entity |
||
| 208 | */ |
||
| 209 | public const HAS_TYPES_PLURAL = [ |
||
| 210 | self::HAS_MANY_TO_MANY, |
||
| 211 | self::HAS_INVERSE_MANY_TO_MANY, |
||
| 212 | self::HAS_ONE_TO_MANY, |
||
| 213 | self::HAS_UNIDIRECTIONAL_ONE_TO_MANY, |
||
| 214 | |||
| 215 | self::HAS_REQUIRED_MANY_TO_MANY, |
||
| 216 | self::HAS_REQUIRED_INVERSE_MANY_TO_MANY, |
||
| 217 | self::HAS_REQUIRED_ONE_TO_MANY, |
||
| 218 | self::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_MANY, |
||
| 219 | ]; |
||
| 220 | |||
| 221 | /** |
||
| 222 | * Set a relationship from one Entity to Another Entity. |
||
| 223 | * |
||
| 224 | * Also used internally to set the reciprocal side. Uses an undocumented 4th bool parameter to kill recursion. |
||
| 225 | * |
||
| 226 | * @param string $owningEntityFqn |
||
| 227 | * @param string $hasType |
||
| 228 | * @param string $ownedEntityFqn |
||
| 229 | * @param bool $requiredReciprocation |
||
| 230 | * |
||
| 231 | * You should never pass in this parameter, it is only used internally |
||
| 232 | * @param bool $internalUseOnly |
||
| 233 | * |
||
| 234 | * @throws DoctrineStaticMetaException |
||
| 235 | * @SuppressWarnings(PHPMD.BooleanArgumentFlag) |
||
| 236 | */ |
||
| 237 | public function setEntityHasRelationToEntity( |
||
| 238 | string $owningEntityFqn, |
||
| 239 | string $hasType, |
||
| 240 | string $ownedEntityFqn, |
||
| 241 | bool $requiredReciprocation = false, |
||
| 242 | bool $internalUseOnly = true |
||
| 243 | ): void { |
||
| 244 | $reciprocate = $internalUseOnly; |
||
| 245 | try { |
||
| 246 | $this->validateHasType($hasType); |
||
| 247 | list( |
||
| 248 | $owningTraitPath, |
||
| 249 | $owningInterfacePath, |
||
| 250 | $reciprocatingInterfacePath, |
||
| 251 | ) = $this->getPathsForOwningTraitsAndInterfaces( |
||
| 252 | $hasType, |
||
| 253 | $ownedEntityFqn |
||
| 254 | ); |
||
| 255 | list($owningClass, , $owningClassSubDirs) = $this->parseFullyQualifiedName($owningEntityFqn); |
||
| 256 | $owningClassPath = $this->pathHelper->getPathFromNameAndSubDirs( |
||
| 257 | $this->pathToProjectRoot, |
||
| 258 | $owningClass, |
||
| 259 | $owningClassSubDirs |
||
| 260 | ); |
||
| 261 | $this->useRelationTraitInClass($owningClassPath, $owningTraitPath); |
||
| 262 | $this->useRelationInterfaceInEntityInterface($owningClassPath, $owningInterfacePath); |
||
| 263 | if (\in_array($hasType, self::HAS_TYPES_RECIPROCATED, true)) { |
||
| 264 | $this->useRelationInterfaceInEntityInterface($owningClassPath, $reciprocatingInterfacePath); |
||
| 265 | } |
||
| 266 | if (true === $reciprocate && \in_array($hasType, self::HAS_TYPES_RECIPROCATED, true)) { |
||
| 267 | $inverseType = $this->getInverseHasType($hasType); |
||
| 268 | $inverseType = $this->updateHasTypeForPossibleRequired($inverseType, $requiredReciprocation); |
||
| 269 | $this->setEntityHasRelationToEntity( |
||
| 270 | $ownedEntityFqn, |
||
| 271 | $inverseType, |
||
| 272 | $owningEntityFqn, |
||
| 273 | /** |
||
| 274 | * Setting required reciprocation to false, |
||
| 275 | * actually it is irrelevant because reciprocation is disabled |
||
| 276 | */ |
||
| 277 | false, |
||
| 278 | false |
||
| 279 | ); |
||
| 280 | } |
||
| 281 | } catch (\Exception $e) { |
||
| 282 | throw new DoctrineStaticMetaException( |
||
| 283 | 'Exception in ' . __METHOD__ . ': ' . $e->getMessage(), |
||
| 284 | $e->getCode(), |
||
| 285 | $e |
||
| 286 | ); |
||
| 287 | } |
||
| 288 | } |
||
| 289 | |||
| 290 | /** |
||
| 291 | * @param string $hasType |
||
| 292 | * |
||
| 293 | * @throws \InvalidArgumentException |
||
| 294 | */ |
||
| 295 | protected function validateHasType(string $hasType): void |
||
| 296 | { |
||
| 297 | if (!\in_array($hasType, static::HAS_TYPES, true)) { |
||
| 298 | throw new \InvalidArgumentException( |
||
| 299 | 'Invalid $hasType ' . $hasType . ', must be one of: ' |
||
| 300 | . \print_r(static::HAS_TYPES, true) |
||
| 301 | ); |
||
| 302 | } |
||
| 303 | } |
||
| 304 | |||
| 305 | /** |
||
| 306 | * Get the absolute paths for the owning traits and interfaces for the specified relation type |
||
| 307 | * Will ensure that the files exists |
||
| 308 | * |
||
| 309 | * @param string $hasType |
||
| 310 | * @param string $ownedEntityFqn |
||
| 311 | * |
||
| 312 | * @return array [ |
||
| 313 | * $owningTraitPath, |
||
| 314 | * $owningInterfacePath, |
||
| 315 | * $reciprocatingInterfacePath |
||
| 316 | * ] |
||
| 317 | * @throws DoctrineStaticMetaException |
||
| 318 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
| 319 | * @SuppressWarnings(PHPMD.BooleanArgumentFlag) |
||
| 320 | */ |
||
| 321 | protected function getPathsForOwningTraitsAndInterfaces( |
||
| 322 | string $hasType, |
||
| 323 | string $ownedEntityFqn |
||
| 324 | ): array { |
||
| 325 | try { |
||
| 326 | $ownedHasName = $this->namespaceHelper->getOwnedHasName( |
||
| 327 | $hasType, |
||
| 328 | $ownedEntityFqn, |
||
| 329 | $this->srcSubFolderName, |
||
| 330 | $this->projectRootNamespace |
||
| 331 | ); |
||
| 332 | $reciprocatedHasName = $this->namespaceHelper->getReciprocatedHasName( |
||
| 333 | $ownedEntityFqn, |
||
| 334 | $this->srcSubFolderName, |
||
| 335 | $this->projectRootNamespace |
||
| 336 | ); |
||
| 337 | $owningTraitFqn = $this->getOwningTraitFqn($hasType, $ownedEntityFqn); |
||
| 338 | list($traitName, , $traitSubDirsNoEntities) = $this->parseFullyQualifiedName($owningTraitFqn); |
||
| 339 | $owningTraitPath = $this->pathHelper->getPathFromNameAndSubDirs( |
||
| 340 | $this->pathToProjectRoot, |
||
| 341 | $traitName, |
||
| 342 | $traitSubDirsNoEntities |
||
| 343 | ); |
||
| 344 | if (!\file_exists($owningTraitPath)) { |
||
| 345 | $this->generateRelationCodeForEntity($ownedEntityFqn); |
||
| 346 | } |
||
| 347 | $owningInterfaceFqn = $this->getOwningInterfaceFqn($hasType, $ownedEntityFqn); |
||
| 348 | list($interfaceName, , $interfaceSubDirsNoEntities) = $this->parseFullyQualifiedName($owningInterfaceFqn); |
||
| 349 | $owningInterfacePath = $this->pathHelper->getPathFromNameAndSubDirs( |
||
| 350 | $this->pathToProjectRoot, |
||
| 351 | $interfaceName, |
||
| 352 | $interfaceSubDirsNoEntities |
||
| 353 | ); |
||
| 354 | $reciprocatingInterfacePath = \preg_replace( |
||
| 355 | '%Has(Required|)' . $ownedHasName . '%', |
||
| 356 | 'Reciprocates' . $reciprocatedHasName, |
||
| 357 | $owningInterfacePath |
||
| 358 | ); |
||
| 359 | |||
| 360 | return [ |
||
| 361 | $owningTraitPath, |
||
| 362 | $owningInterfacePath, |
||
| 363 | $reciprocatingInterfacePath, |
||
| 364 | ]; |
||
| 365 | } catch (\Exception $e) { |
||
| 366 | throw new DoctrineStaticMetaException( |
||
| 367 | 'Exception in ' . __METHOD__ . ': ' . $e->getMessage(), |
||
| 368 | $e->getCode(), |
||
| 369 | $e |
||
| 370 | ); |
||
| 371 | } |
||
| 372 | } |
||
| 373 | |||
| 374 | /** |
||
| 375 | * @param string $hasType |
||
| 376 | * @param string $ownedEntityFqn |
||
| 377 | * |
||
| 378 | * @return string |
||
| 379 | * @throws DoctrineStaticMetaException |
||
| 380 | */ |
||
| 381 | public function getOwningTraitFqn(string $hasType, string $ownedEntityFqn): string |
||
| 382 | { |
||
| 383 | return $this->namespaceHelper->getOwningTraitFqn( |
||
| 384 | $hasType, |
||
| 385 | $ownedEntityFqn, |
||
| 386 | $this->projectRootNamespace, |
||
| 387 | $this->srcSubFolderName |
||
| 388 | ); |
||
| 389 | } |
||
| 390 | |||
| 391 | /** |
||
| 392 | * Generate the relation traits for specified Entity |
||
| 393 | * |
||
| 394 | * This works by copying the template traits folder over and then updating the file contents, name and path |
||
| 395 | * |
||
| 396 | * @param string $entityFqn Fully Qualified Name of Entity |
||
| 397 | * |
||
| 398 | * @throws DoctrineStaticMetaException |
||
| 399 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
| 400 | */ |
||
| 401 | public function generateRelationCodeForEntity(string $entityFqn): void |
||
| 402 | { |
||
| 403 | $invokable = new GenerateRelationCodeForEntity( |
||
| 404 | $entityFqn, |
||
| 405 | $this->pathToProjectRoot, |
||
| 406 | $this->projectRootNamespace, |
||
| 407 | $this->srcSubFolderName, |
||
| 408 | $this->namespaceHelper, |
||
| 409 | $this->pathHelper, |
||
| 410 | $this->findAndReplaceHelper |
||
| 411 | ); |
||
| 412 | $invokable($this->getRelativePathRelationsGenerator()); |
||
| 413 | } |
||
| 414 | |||
| 415 | /** |
||
| 416 | * Generator that yields relative paths of all the files in the relations template path and the SplFileInfo objects |
||
| 417 | * |
||
| 418 | * Use a PHP Generator to iterate over a recursive iterator iterator and then yield: |
||
| 419 | * - key: string $relativePath |
||
| 420 | * - value: \SplFileInfo $fileInfo |
||
| 421 | * |
||
| 422 | * The `finally` step unsets the recursiveIterator once everything is done |
||
| 423 | * |
||
| 424 | * @return \Generator |
||
| 425 | */ |
||
| 426 | public function getRelativePathRelationsGenerator(): \Generator |
||
| 427 | { |
||
| 428 | try { |
||
| 429 | $recursiveIterator = new \RecursiveIteratorIterator( |
||
| 430 | new \RecursiveDirectoryIterator( |
||
| 431 | \realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH), |
||
| 432 | \RecursiveDirectoryIterator::SKIP_DOTS |
||
| 433 | ), |
||
| 434 | \RecursiveIteratorIterator::SELF_FIRST |
||
| 435 | ); |
||
| 436 | foreach ($recursiveIterator as $path => $fileInfo) { |
||
| 437 | $relativePath = rtrim( |
||
| 438 | $this->getFilesystem()->makePathRelative( |
||
| 439 | $path, |
||
| 440 | \realpath(AbstractGenerator::RELATIONS_TEMPLATE_PATH) |
||
| 441 | ), |
||
| 442 | '/' |
||
| 443 | ); |
||
| 444 | yield $relativePath => $fileInfo; |
||
| 445 | } |
||
| 446 | } finally { |
||
| 447 | $recursiveIterator = null; |
||
| 448 | unset($recursiveIterator); |
||
| 449 | } |
||
| 450 | } |
||
| 451 | |||
| 452 | /** |
||
| 453 | * @param string $hasType |
||
| 454 | * @param string $ownedEntityFqn |
||
| 455 | * |
||
| 456 | * @return string |
||
| 457 | * @throws DoctrineStaticMetaException |
||
| 458 | */ |
||
| 459 | public function getOwningInterfaceFqn(string $hasType, string $ownedEntityFqn): string |
||
| 460 | { |
||
| 461 | return $this->namespaceHelper->getOwningInterfaceFqn( |
||
| 462 | $hasType, |
||
| 463 | $ownedEntityFqn, |
||
| 464 | $this->projectRootNamespace, |
||
| 465 | $this->srcSubFolderName |
||
| 466 | ); |
||
| 467 | } |
||
| 468 | |||
| 469 | /** |
||
| 470 | * Add the specified trait to the specified class |
||
| 471 | * |
||
| 472 | * @param string $classPath |
||
| 473 | * @param string $traitPath |
||
| 474 | * |
||
| 475 | * @throws DoctrineStaticMetaException |
||
| 476 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
| 477 | */ |
||
| 478 | protected function useRelationTraitInClass(string $classPath, string $traitPath): void |
||
| 479 | { |
||
| 480 | try { |
||
| 481 | $class = PhpClass::fromFile($classPath); |
||
| 482 | } catch (Error $e) { |
||
| 483 | throw new DoctrineStaticMetaException( |
||
| 484 | 'PHP parsing error when loading class ' . $classPath . ': ' . $e->getMessage(), |
||
| 485 | $e->getCode(), |
||
| 486 | $e |
||
| 487 | ); |
||
| 488 | } |
||
| 489 | try { |
||
| 490 | $trait = PhpTrait::fromFile($traitPath); |
||
| 491 | } catch (Error $e) { |
||
| 492 | throw new DoctrineStaticMetaException( |
||
| 493 | 'PHP parsing error when loading class ' . $classPath . ': ' . $e->getMessage(), |
||
| 494 | $e->getCode(), |
||
| 495 | $e |
||
| 496 | ); |
||
| 497 | } |
||
| 498 | $class->addTrait($trait); |
||
| 499 | $this->codeHelper->generate($class, $classPath); |
||
| 500 | } |
||
| 501 | |||
| 502 | /** |
||
| 503 | * Add the specified interface to the specified entity interface |
||
| 504 | * |
||
| 505 | * @param string $classPath |
||
| 506 | * @param string $interfacePath |
||
| 507 | * |
||
| 508 | * @throws \ReflectionException |
||
| 509 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
| 510 | */ |
||
| 511 | protected function useRelationInterfaceInEntityInterface(string $classPath, string $interfacePath): void |
||
| 512 | { |
||
| 513 | $entityFqn = PhpClass::fromFile($classPath)->getQualifiedName(); |
||
| 514 | $entityInterfaceFqn = $this->namespaceHelper->getEntityInterfaceFromEntityFqn($entityFqn); |
||
| 515 | $entityInterfacePath = (new \ts\Reflection\ReflectionClass($entityInterfaceFqn))->getFileName(); |
||
| 516 | $entityInterface = PhpInterface::fromFile($entityInterfacePath); |
||
| 517 | $relationInterface = PhpInterface::fromFile($interfacePath); |
||
| 518 | $entityInterface->addInterface($relationInterface); |
||
| 519 | $this->codeHelper->generate($entityInterface, $entityInterfacePath); |
||
| 520 | } |
||
| 521 | |||
| 522 | /** |
||
| 523 | * Get the inverse of a hasType |
||
| 524 | * |
||
| 525 | * @param string $hasType |
||
| 526 | * |
||
| 527 | * @return string |
||
| 528 | * @throws DoctrineStaticMetaException |
||
| 529 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) |
||
| 530 | */ |
||
| 531 | protected function getInverseHasType(string $hasType): string |
||
| 532 | { |
||
| 533 | switch ($hasType) { |
||
| 534 | case self::HAS_ONE_TO_ONE: |
||
| 535 | case self::HAS_REQUIRED_ONE_TO_ONE: |
||
| 536 | case self::HAS_MANY_TO_MANY: |
||
| 537 | case self::HAS_REQUIRED_MANY_TO_MANY: |
||
| 538 | return \str_replace( |
||
| 539 | self::PREFIX_OWNING, |
||
| 540 | self::PREFIX_INVERSE, |
||
| 541 | $hasType |
||
| 542 | ); |
||
| 543 | |||
| 544 | case self::HAS_INVERSE_ONE_TO_ONE: |
||
| 545 | case self::HAS_REQUIRED_INVERSE_ONE_TO_ONE: |
||
| 546 | case self::HAS_INVERSE_MANY_TO_MANY: |
||
| 547 | case self::HAS_REQUIRED_INVERSE_MANY_TO_MANY: |
||
| 548 | return \str_replace( |
||
| 549 | self::PREFIX_INVERSE, |
||
| 550 | self::PREFIX_OWNING, |
||
| 551 | $hasType |
||
| 552 | ); |
||
| 553 | |||
| 554 | case self::HAS_MANY_TO_ONE: |
||
| 555 | return self::HAS_ONE_TO_MANY; |
||
| 556 | |||
| 557 | case self::HAS_REQUIRED_MANY_TO_ONE: |
||
| 558 | return self::HAS_REQUIRED_ONE_TO_MANY; |
||
| 559 | |||
| 560 | case self::HAS_ONE_TO_MANY: |
||
| 561 | return self::HAS_MANY_TO_ONE; |
||
| 562 | |||
| 563 | case self::HAS_REQUIRED_ONE_TO_MANY: |
||
| 564 | return self::HAS_REQUIRED_MANY_TO_ONE; |
||
| 565 | |||
| 566 | default: |
||
| 567 | throw new DoctrineStaticMetaException( |
||
| 568 | 'invalid $hasType ' . $hasType . ' when trying to get the inverted relation' |
||
| 569 | ); |
||
| 570 | } |
||
| 571 | } |
||
| 572 | |||
| 573 | /** |
||
| 574 | * Take a relationship and a possibility of being required and ensure it is set as the correct relationship |
||
| 575 | * |
||
| 576 | * @param string $relation |
||
| 577 | * @param bool $required |
||
| 578 | * |
||
| 579 | * @return string |
||
| 580 | */ |
||
| 581 | private function updateHasTypeForPossibleRequired(string $relation, bool $required): string |
||
| 582 | { |
||
| 583 | $inverseIsRequired = \ts\stringContains($relation, self::PREFIX_REQUIRED); |
||
| 584 | if (false === $required) { |
||
| 585 | if (false === $inverseIsRequired) { |
||
| 586 | return $relation; |
||
| 587 | } |
||
| 588 | |||
| 589 | return $this->removeRequiredToRelation($relation); |
||
| 590 | } |
||
| 591 | if (true === $required) { |
||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
| 592 | if (true === $inverseIsRequired) { |
||
| 593 | return $relation; |
||
| 594 | } |
||
| 595 | |||
| 596 | return $this->addRequiredToRelation($relation); |
||
| 597 | } |
||
|
0 ignored issues
–
show
The function implicitly returns
null when the if condition on line 591 is false. This is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.
For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example: interface ReturnsInt {
public function returnsIntHinted(): int;
}
class MyClass implements ReturnsInt {
public function returnsIntHinted(): int
{
if (foo()) {
return 123;
}
// here: null is implicitly returned
}
}
Loading history...
|
|||
| 598 | } |
||
| 599 | |||
| 600 | private function removeRequiredToRelation(string $relation): string |
||
| 601 | { |
||
| 602 | if (0 !== strpos($relation, self::PREFIX_REQUIRED)) { |
||
| 603 | throw new \RuntimeException('Trying to remove the Required prefix, but it is not set: ' . $relation); |
||
| 604 | } |
||
| 605 | |||
| 606 | return substr($relation, 8); |
||
| 607 | } |
||
| 608 | |||
| 609 | private function addRequiredToRelation(string $relation): string |
||
| 610 | { |
||
| 611 | if (0 === strpos($relation, self::PREFIX_REQUIRED)) { |
||
| 612 | throw new \RuntimeException('Trying to add the Required prefix, but it is already set: ' . $relation); |
||
| 613 | } |
||
| 614 | |||
| 615 | return self::PREFIX_REQUIRED . $relation; |
||
| 616 | } |
||
| 617 | } |
||
| 618 |