Completed
Pull Request — master (#78)
by
unknown
21:08
created

GenerateRuntimeHydrator::getClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
rs 9.9332
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GeneratedHydrator\ClosureGenerator\Runtime;
6
7
use Closure;
8
use GeneratedHydrator\ClosureGenerator\GenerateHydrator;
9
use GeneratedHydrator\Configuration;
10
use ReflectionClass;
11
use ReflectionException;
12
use RuntimeException;
13
use stdClass;
14
use Zend\Hydrator\HydratorInterface;
15
use function array_key_exists;
16
17
final class GenerateRuntimeHydrator implements GenerateHydrator
18
{
19
    /** @var callable[] */
20
    private static $hydratorsCache;
21
22
    /** @var ReflectionClass[] */
23
    private static $classesCache;
24
25
    /**
26
     * @throws ReflectionException
27
     */
28
    public function __invoke(string $className) : callable
29
    {
30
        if (isset(self::$hydratorsCache[$className])) {
31
            return self::$hydratorsCache[$className];
32
        }
33
34
        if ($className === stdClass::class) {
35
            return $this->generateWorkaroundExtractor($className);
36
        }
37
38
        $propertyNames = $this->getPropertyNames($className);
39
40
        $parentHydrator = $this->generateParentHydrator($className);
41
        // Kind of micro optimization :
42
        // in case of class without a parent avoid to inherit $parentHydrator from this scope and to check it in the closure
43
        if ($parentHydrator === null) {
44
            $extractor = static function ($data, $object) use ($propertyNames) {
45
                foreach ($propertyNames as $propertyName) {
46
                    if (! array_key_exists($propertyName, $data)) {
47
                        continue;
48
                    }
49
                    $object->{$propertyName} = $data[$propertyName];
50
                }
51
52
                return $object;
53
            };
54
        } else {
55
            $extractor = static function (
56
                $data,
57
                $object
58
            ) use (
59
                $propertyNames,
60
                $parentHydrator
61
            ) {
62
                $parentHydrator($data, $object);
63
                foreach ($propertyNames as $propertyName) {
64
                    if (! array_key_exists($propertyName, $data)) {
65
                        continue;
66
                    }
67
                    $object->{$propertyName} = $data[$propertyName];
68
                }
69
70
                return $object;
71
            };
72
        }
73
74
        return self::$hydratorsCache[$className] = Closure::bind($extractor, null, $className);
75
    }
76
77
    /**
78
     * @throws ReflectionException
79
     */
80
    private function generateParentHydrator(string $className) : ?callable
81
    {
82
        $parent = $this->getClass($className)->getParentClass();
83
        if ($parent === false) {
84
            return null;
85
        }
86
87
        return $this($parent->getName());
88
    }
89
90
    /**
91
     * @return string[]
92
     *
93
     * @throws ReflectionException
94
     */
95
    private function getPropertyNames(string $className) : array
96
    {
97
        $class         = $this->getClass($className);
98
        $propertyNames = [];
99
        foreach ($class->getProperties() as $property) {
100
            if ($property->isStatic()) {
101
                continue;
102
            }
103
            $propertyNames[] = $property->getName();
104
        }
105
106
        return $propertyNames;
107
    }
108
109
    /**
110
     * @throws ReflectionException
111
     * @throws RuntimeException
112
     */
113
    private function getClass(string $className) : ReflectionClass
114
    {
115
        if (isset(self::$classesCache[$className])) {
116
            return self::$classesCache[$className];
117
        }
118
119
        $class = new ReflectionClass($className);
120
121
        return self::$classesCache[$className] = $class;
122
    }
123
124
    private function generateWorkaroundExtractor(string $className) : callable
125
    {
126
        $config = new Configuration($className);
127
128
        $generatedClass = $config->createFactory()->getHydratorClass();
129
        /** @var HydratorInterface $hydrator */
130
        $hydrator = new $generatedClass();
131
132
        return static function (array $data, object $object) use ($hydrator) {
133
            return $hydrator->hydrate($data, $object);
134
        };
135
    }
136
}
137