Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.59% covered (success)
97.59%
81 / 83
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ItemListCodeGenerator
97.59% covered (success)
97.59%
81 / 83
50.00% covered (danger)
50.00%
2 / 4
33
0.00% covered (danger)
0.00%
0 / 1
 isValidList
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
 isNotHashmap
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 run
98.36% covered (success)
98.36%
60 / 61
0.00% covered (danger)
0.00%
0 / 1
18
 finetune
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
4.07
1<?php
2namespace Apie\StorageMetadataBuilder\CodeGenerators;
3
4use Apie\Core\Attributes\StoreOptions;
5use Apie\Core\Context\ApieContext;
6use Apie\Core\Enums\ScalarType;
7use Apie\Core\Identifiers\KebabCaseSlug;
8use Apie\Core\Metadata\ItemHashmapMetadata;
9use Apie\Core\Metadata\ItemListMetadata;
10use Apie\Core\Metadata\MetadataFactory;
11use Apie\Core\Metadata\MetadataInterface;
12use Apie\Core\Metadata\UnionTypeMetadata;
13use Apie\Core\Utils\ConverterUtils;
14use Apie\StorageMetadata\Attributes\OneToManyAttribute;
15use Apie\StorageMetadata\Attributes\OrderAttribute;
16use Apie\StorageMetadata\Attributes\ParentAttribute;
17use Apie\StorageMetadataBuilder\Factories\ClassTypeFactory;
18use Apie\StorageMetadataBuilder\Interfaces\RunGeneratedCodeContextInterface;
19use Apie\StorageMetadataBuilder\Mediators\GeneratedCodeContext;
20
21/**
22 * Creates the one to many relations for lists.
23 * - create a sub table for the list
24 * - the sub table references the entity with 'parent' property
25 * - an 'order' property is made for the index of the hashmap or the order of the list.
26 */
27final class ItemListCodeGenerator implements RunGeneratedCodeContextInterface
28{
29    private function isValidList(MetadataInterface $metadata): bool
30    {
31        if ($metadata instanceof ItemListMetadata || $metadata instanceof ItemHashmapMetadata) {
32            return true;
33        }
34        if ($metadata instanceof UnionTypeMetadata) {
35            $sub = $metadata->toSkipNull();
36            if ($sub instanceof ItemListMetadata || $sub instanceof ItemHashmapMetadata) {
37                return true;
38            }
39        }
40
41        return false;
42    }
43
44    private function isNotHashmap(MetadataInterface $metadata): bool
45    {
46        if ($metadata instanceof ItemListMetadata) {
47            return true;
48        }
49        if ($metadata instanceof ItemHashmapMetadata) {
50            return false;
51        }
52        if ($metadata instanceof UnionTypeMetadata) {
53            $sub = $metadata->toSkipNull();
54            if ($sub instanceof ItemHashmapMetadata) {
55                return false;
56            }
57        }
58
59        return true;
60    }
61
62    public function run(GeneratedCodeContext $generatedCodeContext): void
63    {
64        $property = $generatedCodeContext->getCurrentProperty();
65        $class = $property ? ConverterUtils::toReflectionClass($property) : null;
66        $currentTable = $generatedCodeContext->getCurrentTable();
67        if (null === $class || null === $currentTable) {
68            return;
69        }
70        $metadata = MetadataFactory::getMetadataStrategyForType($property->getType())
71            ->getResultMetadata(new ApieContext());
72        $propertyName = 'apie_'
73            . str_replace('-', '_', (string) KebabCaseSlug::fromClass($property->getDeclaringClass()))
74            . '_'
75            . str_replace('-', '_', (string) KebabCaseSlug::fromClass($property));
76        if ($currentTable->hasProperty($propertyName)) {
77            return;
78        }
79        if ($this->isValidList($metadata)) {
80            $nullableFieldName = null;
81            $mutableListFieldName = null;
82            foreach ($property->getAttributes(StoreOptions::class) as $attribute) {
83                $options = $attribute->newInstance();
84                if ($options->mutableListField) {
85                    $mutableListFieldName = preg_replace('/^apie_/', 'mute_', $propertyName);
86                }
87            }
88            if ($property->getType() === null || $property->getType()->allowsNull()) {
89                $nullableFieldName = preg_replace('/^apie_/', 'null_', $propertyName);
90            }
91            $tableName = $generatedCodeContext->getPrefix('apie_resource_');
92            $arrayType = $class->getMethod('offsetGet')->getReturnType();
93            $scalar = MetadataFactory::getScalarForType($arrayType, $arrayType->allowsNull());
94            if (!$arrayType || 'mixed' === (string) $arrayType) {
95                return;
96            }
97            $arrayClass = $arrayType ? ConverterUtils::toReflectionClass($arrayType) : null;
98            $table = in_array($scalar, ScalarType::PRIMITIVES)
99                ? ClassTypeFactory::createPrimitiveTable($tableName, $scalar->toReflectionType())
100                : ClassTypeFactory::createStorageTable($tableName, $arrayClass);
101            $table->addProperty('parent')
102                ->setType($currentTable->getName())
103                ->addAttribute(ParentAttribute::class);
104            $table->addProperty('listOrder')
105                ->setType($this->isNotHashmap($metadata) ? 'int' : 'string')
106                ->addAttribute(OrderAttribute::class);
107            if (in_array($scalar, ScalarType::PRIMITIVES)) {
108                $generatedCodeContext->generatedCode->generatedCodeHashmap[$tableName] = $table;
109            } else {
110                $generatedCodeContext->withCurrentObject($arrayClass)->iterateOverTable($table);
111            }
112            if ($nullableFieldName) {
113                $currentTable->addProperty($nullableFieldName)
114                    ->setType('bool')
115                    ->setValue(true);
116            }
117            if ($mutableListFieldName) {
118                $currentTable->addProperty($mutableListFieldName)
119                    ->setType('bool')
120                    ->setValue(true);
121            }
122            $currentTable->addProperty($propertyName)
123                ->addAttribute(
124                    OneToManyAttribute::class,
125                    $this->finetune(
126                        [
127                            $property->name,
128                            $tableName,
129                            $property->getDeclaringClass()->name,
130                            $nullableFieldName,
131                            $mutableListFieldName
132                        ]
133                    )
134                );
135        }
136    }
137
138    /**
139     * @param array<int, string> $args
140     * @return array<int, string>
141     */
142    private function finetune(array $args): array
143    {
144        while (true) {
145            if (count($args) === 0) {
146                return [];
147            }
148            if ($args[count($args) - 1] !== null) {
149                return $args;
150            };
151            array_pop($args);
152        }
153    }
154}