HydratorFactory::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 6
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 53
    public function __construct(
20
        EntityManager $entityManager,
21
        $autoGenerate = HydratorAutoGenerate::ALWAYS
22
    ) {
23 53
        $this->entityManager = $entityManager;
24 53
        $this->autoGenerate = $autoGenerate;
25 53
    }
26
27 25
    public function get(string $entityClass): ObjectHydrator
28
    {
29 25
        if (isset($this->hydrators[$entityClass])) {
30
            return $this->hydrators[$entityClass];
31
        }
32
33 25
        $entityMeta = $this->entityManager->getMetaData($entityClass);
34 25
        $hydratorClassName = $entityMeta->getHydratorClassName();
35 25
        $namespace = $this->entityManager->getHydratorNamespace();
36 25
        $hydratorClass = $namespace . '\\' . $hydratorClassName;
37
38
        // Fix for already loaded but not initialized hydrator.
39 25
        if (class_exists($hydratorClass)) {
40 23
            $objectHydrator = new $hydratorClass($this->entityManager);
41 23
            if ($entityMeta->definesCustomHydrator()) {
42 22
                $customHydratorClass = $entityMeta->getCustomHydratorClass();
43 22
                $objectHydrator = new $customHydratorClass($objectHydrator);
44
            }
45 23
            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::NEVER:
51
52
                require_once $fileName;
53
                break;
54
55 3
            case HydratorAutoGenerate::IF_NOT_EXISTS:
56
57 2
                if (!is_readable($fileName)) {
58 2
                    $hydrator = $this->create($entityMeta, true);
59 2
                    $this->writeHydrator($hydrator, $fileName);
60
                } else {
61
                    require_once $fileName;
62
                }
63 2
                break;
64
65 1
            case HydratorAutoGenerate::ALWAYS:
66
            default:
67 1
                $this->create($entityMeta, true);
68 1
                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
        $extractMethod->addLine("\$data = [];");
109 3
        $hydratorClass->addMethod($extractMethod);
110
111 3
        foreach ($metaData->getProperties() as $property) {
112
            /** @var MappingStrategy $type */
113 3
            $type = $property->getType();
114 3
            $attributes = $property->getAttributes();
115
116 3
            if (method_exists($type, 'getDefaultAttributes')) {
117 1
                $attributes += $type::getDefaultAttributes();
118
            }
119
120 3
            $attributes = preg_replace('/\s+/', '', var_export($attributes, true));
121
122
            // Build hydrator for property.
123 3
            $hydrateMethod->addLine("// Hydrate {$property->getName()}.");
124 3
            $hydrateMethod->addLine("\$value = \$data['{$property->getFieldName()}'] ?? null;");
125 3
            $hydrateMethod->addLine("\\{$type}::hydrate(\$value, ${attributes}, \$this->entityManager);");
126 3
            $hydrateMethod->addLine('\\' . ReflectionApi::class . "::writeProperty(\$entity, '{$property->getName()}', \$value);");
127
128
            // Store objects hydrated by reference
129 3
            $hydrateMethod->addLine("if (\$this->saveMemory === false && \$entity instanceof \Igni\Storage\Storable) {");
130 3
            $hydrateMethod->addLine("\t\$this->entityManager->attach(\$entity);");
131 3
            $hydrateMethod->addLine("}");
132
133
            // Build extractor for property.
134 3
            if (!$property->getAttributes()['readonly']) {
135 3
                $extractMethod->addLine("// Extract {$property->getName()}.");
136 3
                $extractMethod->addLine('$value = \\' . ReflectionApi::class . "::readProperty(\$entity, '{$property->getName()}');");
137 3
                $extractMethod->addLine("\\{$type}::extract(\$value, ${attributes}, \$this->entityManager);");
138 3
                $extractMethod->addLine("\$data['{$property->getFieldName()}'] = \$value;");
139
            }
140
        }
141
142 3
        $hydrateMethod->addLine('return $entity;');
143 3
        $extractMethod->addLine('return $data;');
144
145 3
        if ($load) {
146 3
            $hydratorClass->load();
147
        }
148
149 3
        return $hydratorClass;
150
    }
151
152
    private function writeHydrator(ReflectionApi\RuntimeClass $hydrator, string $uri): void
153
    {
154 2
        $temp = fopen($uri, 'w');
155
156 2
        if ($temp === false || !fwrite($temp, '<?php declare(strict_types=1);' . PHP_EOL . (string) $hydrator)) {
157
            throw new HydratorException("Could not write hydrator (${uri}) on disk - check for directory permissions.");
158
        }
159
        
160 2
        fclose($temp);
161 2
    }
162
}
163