Passed
Push — master ( b97c16...98e719 )
by Dawid
04:21
created

HydratorFactory::get()   B

Complexity

Conditions 9
Paths 13

Size

Total Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 10.2655

Importance

Changes 0
Metric Value
cc 9
nc 13
nop 1
dl 0
loc 52
ccs 24
cts 32
cp 0.75
crap 10.2655
rs 7.4917
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace Igni\Storage\Hydration;
4
5
use Igni\Storage\Manager;
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 29
    public function __construct(
20
        Manager $entityManager,
21
        $autoGenerate = HydratorAutoGenerate::ALWAYS
22
    ) {
23 29
        $this->entityManager = $entityManager;
24 29
        $this->autoGenerate = $autoGenerate;
25 29
    }
26
27 17
    public function get(string $entityClass): ObjectHydrator
28
    {
29 17
        if (isset($this->hydrators[$entityClass])) {
30
            return $this->hydrators[$entityClass];
31
        }
32
33 17
        $entityMeta = $this->entityManager->getMetaData($entityClass);
34 17
        $hydratorClassName = $entityMeta->getHydratorClassName();
35 17
        $namespace = $this->entityManager->getHydratorNamespace();
36 17
        $hydratorClass = $namespace . '\\' . $hydratorClassName;
37
38
        // Fix for already loaded but not initialized hydrator.
39 17
        if (class_exists($hydratorClass)) {
40 15
            $objectHydrator = new $hydratorClass($this->entityManager);
41 15
            if ($entityMeta->definesCustomHydrator()) {
42 14
                $customHydratorClass = $entityMeta->getCustomHydratorClass();
43 14
                $objectHydrator = new $customHydratorClass($objectHydrator);
44
            }
45 15
            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
                if (!is_readable($fileName)) {
58
                    $hydrator = $this->create($entityMeta, true);
59
                    $this->writeHydrator($hydrator, $fileName);
60
                } else {
61
                    require_once $fileName;
62
                }
63
                break;
64
65 3
            case HydratorAutoGenerate::ALWAYS:
66
            default:
67 3
                $this->create($entityMeta, true);
68 3
                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
        $temp = fopen($uri, 'w');
152
153
        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
        fclose($temp);
158
    }
159
}
160