Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
71.43% |
30 / 42 |
|
60.00% |
3 / 5 |
CRAP | |
0.00% |
0 / 1 |
| LimitFieldLength | |
71.43% |
30 / 42 |
|
60.00% |
3 / 5 |
39.58 | |
0.00% |
0 / 1 |
| postRun | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| patch | |
66.67% |
16 / 24 |
|
0.00% |
0 / 1 |
19.26 | |||
| fillInMissingStringLength | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
| setNameArgument | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| iterateProperties | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
5 | |||
| 1 | <?php |
| 2 | namespace Apie\DoctrineEntityConverter\CodeGenerators; |
| 3 | |
| 4 | use Apie\StorageMetadataBuilder\Interfaces\PostRunGeneratedCodeContextInterface; |
| 5 | use Apie\StorageMetadataBuilder\Mediators\GeneratedCodeContext; |
| 6 | use Doctrine\ORM\Mapping\Column; |
| 7 | use Doctrine\ORM\Mapping\JoinColumn; |
| 8 | use Doctrine\ORM\Mapping\Table; |
| 9 | use Generator; |
| 10 | use Nette\PhpGenerator\Attribute; |
| 11 | use Nette\PhpGenerator\ClassType; |
| 12 | use Nette\PhpGenerator\PromotedParameter; |
| 13 | use Nette\PhpGenerator\Property; |
| 14 | use ReflectionProperty; |
| 15 | |
| 16 | /** |
| 17 | * Find all doctrine column attributes set by AddDoctrineFields and checks if the generated column name |
| 18 | * is not too long. |
| 19 | * |
| 20 | * Most database vendors have a 64 character limit, Postgres has a 63 character limit. |
| 21 | * Foreign keys get a '_id' suffix, so any property name with more than 57 characters requires a different |
| 22 | * column name. |
| 23 | * |
| 24 | * We want to rename the column to only 57 characters and add 3 digits after it if this column name is already defined. |
| 25 | * |
| 26 | * @see AddDoctrineFields |
| 27 | */ |
| 28 | class LimitFieldLength implements PostRunGeneratedCodeContextInterface |
| 29 | { |
| 30 | public function postRun(GeneratedCodeContext $generatedCodeContext): void |
| 31 | { |
| 32 | foreach ($generatedCodeContext->generatedCode->generatedCodeHashmap as $code) { |
| 33 | $this->patch($generatedCodeContext, $code); |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | private function patch(GeneratedCodeContext $generatedCodeContext, ClassType $classType): void |
| 38 | { |
| 39 | if (strlen($classType->getName()) > 60) { |
| 40 | $found = false; |
| 41 | $suggestedTableName = substr($classType->getName(), 0, 30) . '_' . md5($classType->getName()); |
| 42 | foreach ($classType->getAttributes() as $attribute) { |
| 43 | if ($attribute->getName() === Table::class) { |
| 44 | $found = true; |
| 45 | $this->setNameArgument($attribute, $suggestedTableName); |
| 46 | } |
| 47 | } |
| 48 | if (!$found) { |
| 49 | $classType->addAttribute(Table::class, ['name' => $suggestedTableName]); |
| 50 | } |
| 51 | } |
| 52 | $alreadyDefined = []; |
| 53 | foreach ($this->iterateProperties($classType) as $property) { |
| 54 | foreach ($property->getAttributes() as $attribute) { |
| 55 | if (in_array($attribute->getName(), [Column::class])) { |
| 56 | $this->fillInMissingStringLength($attribute); |
| 57 | } |
| 58 | } |
| 59 | $propertyName = $property->getName(); |
| 60 | if (strlen($property->getName()) < 57 && !isset($alreadyDefined[$propertyName])) { |
| 61 | $alreadyDefined[$propertyName] = true; |
| 62 | continue; |
| 63 | } |
| 64 | $suggestedName = substr($propertyName, 0, 57); |
| 65 | for ($i = 0; !empty($alreadyDefined[$suggestedName]); $i++) { |
| 66 | $suggestedName = sprintf("%s%03u", substr($property->getName(), 0, 57), $i); |
| 67 | } |
| 68 | foreach ($property->getAttributes() as $attribute) { |
| 69 | if (in_array($attribute->getName(), [Column::class, JoinColumn::class])) { |
| 70 | $this->setNameArgument($attribute, $suggestedName); |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | private function fillInMissingStringLength(Attribute $attribute): void |
| 77 | { |
| 78 | $arguments = $attribute->getArguments(); |
| 79 | if (($arguments['type'] ?? 'string')=== 'string' || !isset($arguments['type'])) { |
| 80 | if (!isset($arguments['length'])) { |
| 81 | $arguments['length'] = 255; |
| 82 | } |
| 83 | } |
| 84 | $refl = new ReflectionProperty(Attribute::class, 'args'); |
| 85 | $refl->setValue($attribute, $arguments); |
| 86 | } |
| 87 | |
| 88 | private function setNameArgument(Attribute $attribute, string $suggestedName): void |
| 89 | { |
| 90 | $arguments = $attribute->getArguments(); |
| 91 | $arguments['name'] = $suggestedName; |
| 92 | $refl = new ReflectionProperty(Attribute::class, 'args'); |
| 93 | $refl->setValue($attribute, $arguments); |
| 94 | } |
| 95 | |
| 96 | /** |
| 97 | * @return Generator<int, PromotedParameter|Property> |
| 98 | */ |
| 99 | private function iterateProperties(ClassType $classType): Generator |
| 100 | { |
| 101 | foreach ($classType->getProperties() as $property) { |
| 102 | yield $property; |
| 103 | } |
| 104 | if ($classType->hasMethod('__construct')) { |
| 105 | foreach ($classType->getMethod('__construct')->getParameters() as $parameter) { |
| 106 | if ($parameter instanceof PromotedParameter) { |
| 107 | yield $parameter; |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | } |