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

generateParentExtractor()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
c 0
b 0
f 0
rs 9.9666
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\GenerateExtractor;
9
use GeneratedHydrator\Configuration;
10
use ReflectionClass;
11
use ReflectionException;
12
use stdClass;
13
use Zend\Hydrator\ExtractionInterface;
14
15
final class GenerateRuntimeExtractor implements GenerateExtractor
16
{
17
    /** @var callable[] */
18
    private static $extractorsCache;
19
20
    /** @var ReflectionClass[] */
21
    private static $classesCache;
22
23
    /**
24
     * @return callable function ($object) : array
25
     *
26
     * @throws ReflectionException
27
     */
28
    public function __invoke(string $className) : callable
29
    {
30
        if (isset(self::$extractorsCache[$className])) {
31
            return self::$extractorsCache[$className];
32
        }
33
34
        if ($className === stdClass::class) {
35
            return $this->generateWorkaroundExtractor($className);
36
        }
37
38
        $propertyNames   = $this->getPropertyNames($className);
39
        $parentExtractor = $this->generateParentExtractor($className);
40
        // Kind of micro optimization: avoid to inherit $parentHydrator from this scope and to check it in the closure
41
        if ($parentExtractor === null) {
42
            $extractor = static function (object $object) use ($propertyNames) : array {
43
                $data = [];
44
                foreach ($propertyNames as $propertyName) {
45
                    $data[$propertyName] = $object->{$propertyName};
46
                }
47
48
                return $data;
49
            };
50
        } else {
51
            $extractor = static function (object $object) use ($propertyNames, $parentExtractor) : array {
52
                $data = $parentExtractor($object);
53
                foreach ($propertyNames as $propertyName) {
54
                    $data[$propertyName] = $object->{$propertyName};
55
                }
56
57
                return $data;
58
            };
59
        }
60
61
        return self::$extractorsCache[$className] = Closure::bind($extractor, null, $className);
62
    }
63
64
    /**
65
     * @throws ReflectionException
66
     */
67
    private function generateParentExtractor(string $className) : ?callable
68
    {
69
        $parent = $this->getClass($className)->getParentClass();
70
        if ($parent === false) {
71
            return null;
72
        }
73
74
        return $this($parent->getName());
75
    }
76
77
    /**
78
     * @return string[]
79
     *
80
     * @throws ReflectionException
81
     */
82
    private function getPropertyNames(string $className) : array
83
    {
84
        $class         = $this->getClass($className);
85
        $propertyNames = [];
86
        foreach ($class->getProperties() as $property) {
87
            if ($property->isStatic()) {
88
                continue;
89
            }
90
            $propertyNames[] = $property->getName();
91
        }
92
93
        return $propertyNames;
94
    }
95
96
    /**
97
     * @throws ReflectionException
98
     */
99
    private function getClass(string $className) : ReflectionClass
100
    {
101
        if (isset(self::$classesCache[$className])) {
102
            return self::$classesCache[$className];
103
        }
104
105
        $class = new ReflectionClass($className);
106
107
        return self::$classesCache[$className] = $class;
108
    }
109
110
    private function generateWorkaroundExtractor(string $className) : callable
111
    {
112
        $config = new Configuration($className);
113
114
        $generatedClass = $config->createFactory()->getHydratorClass();
115
        /** @var ExtractionInterface $hydrator */
116
        $hydrator = new $generatedClass();
117
118
        return static function (object $object) use ($hydrator) {
119
            return $hydrator->extract($object);
120
        };
121
    }
122
}
123