Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.19% covered (warning)
89.19%
66 / 74
54.55% covered (warning)
54.55%
6 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
GeneratedCodeContext
89.19% covered (warning)
89.19%
66 / 74
54.55% covered (warning)
54.55%
6 / 11
38.73
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCurrentObject
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCurrentTable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getCurrentProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 withCurrentObject
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 findInverseProperty
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
6.22
 findParentProperty
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
7.23
 findIndexProperty
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
5.05
 iterateOverTable
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
8.09
 isBlacklisted
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getPrefix
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2namespace Apie\StorageMetadataBuilder\Mediators;
3
4use Apie\Core\Actions\BoundedContextEntityTuple;
5use Apie\Core\FileStorage\StoredFile;
6use Apie\Core\Identifiers\KebabCaseSlug;
7use Apie\Core\Utils\ConverterUtils;
8use Apie\StorageMetadata\Attributes\ManyToOneAttribute;
9use Apie\StorageMetadata\Attributes\OneToManyAttribute;
10use Apie\StorageMetadata\Attributes\OrderAttribute;
11use Apie\StorageMetadata\Attributes\ParentAttribute;
12use Apie\StorageMetadataBuilder\Lists\ReflectionPropertyList;
13use Apie\TypeConverter\ReflectionTypeFactory;
14use Nette\PhpGenerator\ClassType;
15use Psr\Http\Message\UploadedFileInterface;
16use ReflectionClass;
17use ReflectionProperty;
18
19final class GeneratedCodeContext
20{
21    private const INTERFACE_LINKS = [
22        UploadedFileInterface::class => StoredFile::class,
23    ];
24
25    private array $visited = [];
26
27    private array $visitedTables = [];
28
29    private ?ReflectionClass $currentObject;
30
31    public function __construct(
32        public readonly GeneratedCode $generatedCode,
33        public readonly ?BoundedContextEntityTuple $boundedContextEntityTuple
34    ) {
35        $this->currentObject = $boundedContextEntityTuple?->resourceClass;
36    }
37
38    public function getCurrentObject(): ?ReflectionClass
39    {
40        return $this->currentObject;
41    }
42
43    public function getCurrentTable(): ?ClassType
44    {
45        return empty($this->visitedTables) ? null : $this->visitedTables[count($this->visitedTables) - 1];
46    }
47
48    public function getCurrentProperty(): ?ReflectionProperty
49    {
50        return empty($this->visited) ? null : $this->visited[count($this->visited) - 1];
51    }
52
53    public function withCurrentObject(?ReflectionClass $currentObject)
54    {
55        $res = clone $this;
56        $res->currentObject = $currentObject;
57        return $res;
58    }
59
60    public function findInverseProperty(string $tableName, string $currentClass): ?string
61    {
62        $classType = $this->generatedCode->generatedCodeHashmap[$tableName] ?? null;
63        if (!$classType) {
64            return null;
65        }
66        // TODO promoted constructor arguments? In general parents are not set in the constructor.
67        foreach ($classType->getProperties() as $property) {
68            foreach ($property->getAttributes() as $attribute) {
69                if ($attribute->getName() === OneToManyAttribute::class) {
70                    $arguments = $attribute->getArguments();
71                    $storageClass = $arguments['storageClass'] ?? $arguments[1];
72                    if ($storageClass === $currentClass) {
73                        return $property->getName();
74                    }
75                }
76            }
77        }
78
79        return null;
80    }
81
82    public function findParentProperty(string $tableName): ?string
83    {
84        $classType = $this->generatedCode->generatedCodeHashmap[$tableName] ?? null;
85        if (!$classType) {
86            return null;
87        }
88        // TODO promoted constructor arguments? In general parents are not set in the constructor.
89        $foundProperty = null;
90        foreach ($classType->getProperties() as $property) {
91            foreach ($property->getAttributes() as $attribute) {
92                if ($attribute->getName() === ManyToOneAttribute::class) {
93                    if ($property->getType() === $this->currentObject->name) {
94                        return $property->getName();
95                    }
96                }
97                if ($attribute->getName() === ParentAttribute::class) {
98                    $foundProperty = $property;
99                }
100            }
101        }
102
103        return $foundProperty?->getName();
104    }
105
106    public function findIndexProperty(string $tableName): ?string
107    {
108        $classType = $this->generatedCode->generatedCodeHashmap[$tableName] ?? null;
109        if (!$classType) {
110            return null;
111        }
112        // TODO promoted constructor arguments? In general indexes are not set in the constructor.
113        foreach ($classType->getProperties() as $property) {
114            foreach ($property->getAttributes() as $attribute) {
115                if ($attribute->getName() === OrderAttribute::class) {
116                    return $property->getName();
117                }
118            }
119        }
120
121        return null;
122    }
123
124    public function iterateOverTable(ClassType $table): void
125    {
126        $tableName = $table->getName();
127        // we are dealing with something recursive....
128        if (isset($this->generatedCode->generatedCodeHashmap[$tableName])) {
129            return;
130        }
131        $this->generatedCode->generatedCodeHashmap[$tableName] = $table;
132        if ($this->currentObject !== null) {
133            $object = $this->currentObject;
134            if (isset(self::INTERFACE_LINKS[$object->name])) {
135                $object = new ReflectionClass(self::INTERFACE_LINKS[$object->name]);
136            }
137            foreach (ReflectionPropertyList::createFromClass($object) as $property) {
138                if ($property->isStatic() || $this->isBlacklisted($property)) {
139                    continue;
140                }
141                $clone = clone $this;
142                $clone->visited[] = $property;
143                $clone->visitedTables[] = $table;
144                $class = ConverterUtils::toReflectionClass($property->getType() ?? ReflectionTypeFactory::createReflectionType('mixed'));
145                if ($class !== null) {
146                    $clone->currentObject = $class;
147                }
148                $this->generatedCode->addTodo($clone);
149            }
150        }
151    }
152
153    private function isBlacklisted(ReflectionProperty $property): bool
154    {
155        if ($property->getDeclaringClass()->name === StoredFile::class) {
156            return in_array($property->name, ['internalFile', 'removeOnDestruct', 'resource', 'content', 'movedPath']);
157        }
158        return false;
159    }
160
161    public function getPrefix(string $prefix = ''): string
162    {
163        return $prefix
164            . '_'
165            . $this->boundedContextEntityTuple->boundedContext->getId()
166            . '_'
167            . str_replace('-', '_', (string) KebabCaseSlug::fromClass($this->boundedContextEntityTuple->resourceClass))
168            . (empty($this->visited) ? '' : '__')
169            . implode(
170                '__',
171                array_map(
172                    function (ReflectionProperty $property) {
173                        return str_replace('-', '_', (string) KebabCaseSlug::fromClass($property));
174                    },
175                    $this->visited
176                )
177            );
178    }
179}