Completed
Push — master ( ac62eb...633109 )
by Dawid
03:39
created

HydratorFactory::loadHydrator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 4
cts 5
cp 0.8
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
crap 2.032
1
<?php declare(strict_types=1);
2
3
namespace Igni\Storage\Hydration;
4
5
use Igni\Storage\EntityManager;
6
use Igni\Storage\Exception\HydratorException;
7
use Igni\Storage\Mapping\MappingStrategy;
8
use Igni\Storage\Mapping\MetaData\EntityMetaData;
9
use Igni\Utils\ReflectionApi;
10
11
final class HydratorFactory
12
{
13
    public const STRATEGY_ANNOTATION = 1;
14
15
    private $entityManager;
16
    private $autoGenerate;
17
    private $hydrators = [];
18
19 33
    public function __construct(
20
        EntityManager $entityManager,
21
        $autoGenerate = HydratorAutoGenerate::IF_NOT_EXISTS
22
    ) {
23 33
        $this->entityManager = $entityManager;
24 33
        $this->autoGenerate = $autoGenerate;
25 33
    }
26
27 21
    public function get(string $entityClass): ObjectHydrator
28
    {
29 21
        if (isset($this->hydrators[$entityClass])) {
30
            return $this->hydrators[$entityClass];
31
        }
32
33 21
        $entityMeta = $this->entityManager->getMetaData($entityClass);
34 21
        $hydratorClassName = $entityMeta->getHydratorClassName();
35 21
        $namespace = $this->entityManager->getHydratorNamespace();
36 21
        $hydratorClass = $namespace . '\\' . $hydratorClassName;
37
38
        // Fix for already loaded but not initialized hydrator.
39 21
        if (class_exists($hydratorClass)) {
40 19
            $objectHydrator = new $hydratorClass($this->entityManager);
41 19
            if ($entityMeta->definesCustomHydrator()) {
42 18
                $customHydratorClass = $entityMeta->getCustomHydratorClass();
43 18
                $objectHydrator = new $customHydratorClass($objectHydrator);
44
            }
45 19
            return $this->hydrators[$entityMeta->getClass()] = $objectHydrator;
46
        }
47
48 3
        $fileName = $this->entityManager->getHydratorDir() . DIRECTORY_SEPARATOR . str_replace('\\', '', $hydratorClassName) . '.php';
49 3
        switch ($this->autoGenerate) {
50 3
            case HydratorAutoGenerate::ALWAYS:
51 1
                $this->create($entityMeta, true);
52 1
                break;
53
54 2
            case HydratorAutoGenerate::NEVER:
55
56
                require_once $fileName;
57
                break;
58
59 2
            case HydratorAutoGenerate::IF_NOT_EXISTS:
60
            default:
61
62 2
                if (!is_readable($fileName)) {
63 2
                    $hydrator = $this->create($entityMeta, true);
64 2
                    $this->writeHydrator($hydrator, $fileName);
65
                } else {
66
                    require_once $fileName;
67
                }
68 2
                break;
69
        }
70
71 3
        $objectHydrator =  new $hydratorClass($this->entityManager);
72
73 3
        if ($entityMeta->definesCustomHydrator()) {
74 3
            $customHydratorClass = $entityMeta->getCustomHydratorClass();
75 3
            $objectHydrator = new $customHydratorClass($objectHydrator);
76
        }
77
78 3
        return $this->hydrators[$entityMeta->getClass()] = $objectHydrator;
79
    }
80
81 3
    private function create(EntityMetaData $metaData, bool $load = false): ReflectionApi\RuntimeClass
82
    {
83 3
        if ($this->entityManager->getHydratorNamespace() !== '') {
84 1
            $hydratorClass = ReflectionApi::createClass($this->entityManager->getHydratorNamespace() . '\\' . $metaData->getHydratorClassName());
85
        } else {
86 2
            $hydratorClass = ReflectionApi::createClass($metaData->getHydratorClassName());
87
        }
88 3
        $hydratorClass->extends(GenericHydrator::class);
89
90 3
        $getEntityClassMethod = new ReflectionApi\RuntimeMethod('getEntityClass');
91 3
        $getEntityClassMethod->makeStatic();
92 3
        $getEntityClassMethod->makeFinal();
93 3
        $getEntityClassMethod->setReturnType('string');
94 3
        $getEntityClassMethod->setBody(
95 3
            'return \\' . $metaData->getClass() . '::class;'
96
        );
97 3
        $hydratorClass->addMethod($getEntityClassMethod);
98
99 3
        $hydrateMethod = new ReflectionApi\RuntimeMethod('hydrate');
100 3
        $hydrateMethod->addArgument(new ReflectionApi\RuntimeArgument('data', 'array'));
101 3
        $hydrateMethod->setReturnType($metaData->getClass());
102 3
        $hydrateMethod->addLine('$entity = \\' . ReflectionApi::class . '::createInstance(self::getEntityClass());');
103 3
        $hydratorClass->addMethod($hydrateMethod);
104
105 3
        $extractMethod = new ReflectionApi\RuntimeMethod('extract');
106 3
        $extractMethod->addArgument(new ReflectionApi\RuntimeArgument('entity'));
107 3
        $extractMethod->setReturnType('array');
108 3
        $hydratorClass->addMethod($extractMethod);
109
110 3
        foreach ($metaData->getProperties() as $property) {
111
            /** @var MappingStrategy $type */
112 3
            $type = $property->getType();
113 3
            $attributes = $property->getAttributes();
114
115 3
            if (method_exists($type, 'getDefaultAttributes')) {
116 1
                $attributes += $type::getDefaultAttributes();
117
            }
118
119 3
            $attributes = preg_replace('/\s+/', '', var_export($attributes, true));
120
121
            // Build hydrator for property.
122 3
            $hydrateMethod->addLine("// Hydrate {$property->getName()}.");
123 3
            $hydrateMethod->addLine("\$value = \$data['{$property->getFieldName()}'] ?? null;");
124 3
            $hydrateMethod->addLine("\\{$type}::hydrate(\$value, ${attributes}, \$this->entityManager);");
125 3
            $hydrateMethod->addLine('\\' . ReflectionApi::class . "::writeProperty(\$entity, '{$property->getName()}', \$value);");
126
127
            // Store objects hydrated by reference
128 3
            $hydrateMethod->addLine("if (\$this->mode === \Igni\Storage\Hydration\HydrationMode::BY_REFERENCE && \$entity instanceof \Igni\Storage\Entity) {");
129 3
            $hydrateMethod->addLine("\t\$this->entityManager->attach(\$entity);");
130 3
            $hydrateMethod->addLine("}");
131
132
            // Build extractor for property.
133 3
            $extractMethod->addLine("// Extract {$property->getName()}.");
134 3
            $extractMethod->addLine('$value = \\' . ReflectionApi::class . "::readProperty(\$entity, '{$property->getName()}');");
135 3
            $extractMethod->addLine("\\{$type}::extract(\$value, ${attributes}, \$this->entityManager);");
136
            $extractMethod->addLine("\$data['{$property->getFieldName()}'] = \$value;");
137
        }
138
139 3
        $hydrateMethod->addLine('return $entity;');
140 3
        $extractMethod->addLine('return $data;');
141
142 3
        if ($load) {
143 3
            $hydratorClass->load();
144
        }
145
146 3
        return $hydratorClass;
147
    }
148
149
    private function writeHydrator(ReflectionApi\RuntimeClass $hydrator, string $uri): void
150
    {
151 2
        $temp = fopen($uri, 'w');
152
153 2
        if ($temp === false || !fwrite($temp, '<?php declare(strict_types=1);' . PHP_EOL . (string) $hydrator)) {
154
            throw new HydratorException("Could not write hydrator (${uri}) on disk - check for directory permissions.");
155
        }
156
        
157 2
        fclose($temp);
158 2
    }
159
}
160