Completed
Pull Request — master (#423)
by Marco
07:12
created

CallInitializer::propertiesReferenceArrayCode()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 0
cts 17
cp 0
rs 9.488
c 0
b 0
f 0
cc 3
nc 4
nop 1
crap 12
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator;
6
7
use ProxyManager\Generator\MethodGenerator;
8
use ProxyManager\Generator\Util\IdentifierSuffixer;
9
use ProxyManager\ProxyGenerator\Util\Properties;
10
use ReflectionProperty;
11
use Zend\Code\Generator\ParameterGenerator;
12
use Zend\Code\Generator\PropertyGenerator;
13
14
/**
15
 * Implementation for {@see \ProxyManager\Proxy\LazyLoadingInterface::isProxyInitialized}
16
 * for lazy loading value holder objects
17
 *
18
 * @author Marco Pivetta <[email protected]>
19
 * @license MIT
20
 */
21
class CallInitializer extends MethodGenerator
22
{
23
    /**
24
     * Constructor
25
     *
26
     * @param PropertyGenerator $initializerProperty
27
     * @param PropertyGenerator $initTracker
28
     * @param Properties        $properties
29
     */
30
    public function __construct(
31
        PropertyGenerator $initializerProperty,
32
        PropertyGenerator $initTracker,
33
        Properties $properties
34
    ) {
35
        $docBlock = <<<'DOCBLOCK'
36
Triggers initialization logic for this ghost object
37
38
@param string  $methodName
39
@param mixed[] $parameters
40
41
@return mixed
42
DOCBLOCK;
43
44
        parent::__construct(
45
            IdentifierSuffixer::getIdentifier('callInitializer'),
46
            [
47
                new ParameterGenerator('methodName'),
48
                new ParameterGenerator('parameters', 'array'),
49
            ],
50
            static::FLAG_PRIVATE,
51
            null,
52
            $docBlock
53
        );
54
55
        $initializer    = $initializerProperty->getName();
56
        $initialization = $initTracker->getName();
57
58
        $bodyTemplate = <<<'PHP'
59
if ($this->%s || ! $this->%s) {
60
    return;
61
}
62
63
$this->%s = true;
64
65
%s
66
%s
67
68
$result = $this->%s->__invoke($this, $methodName, $parameters, $this->%s, $properties);
69
$this->%s = false;
70
71
return $result;
72
PHP;
73
74
        $this->setBody(sprintf(
75
            $bodyTemplate,
76
            $initialization,
77
            $initializer,
78
            $initialization,
79
            $this->propertiesInitializationCode($properties),
80
            $this->propertiesReferenceArrayCode($properties),
81
            $initializer,
82
            $initializer,
83
            $initialization
84
        ));
85
    }
86
87
    private function propertiesInitializationCode(Properties $properties) : string
88
    {
89
        $assignments = [];
90
91
        foreach ($properties->getAccessibleProperties() as $property) {
92
            $assignments[] = '$this->'
93
                . $property->getName()
94
                . ' = ' . $this->getExportedPropertyDefaultValue($property)
95
                . ';';
96
        }
97
98
        foreach ($properties->getGroupedPrivateProperties() as $className => $privateProperties) {
99
            $cacheKey      = 'cache' . str_replace('\\', '_', $className);
100
            $assignments[] = 'static $' . $cacheKey . ";\n\n"
101
                . '$' . $cacheKey . ' ?: $' . $cacheKey . " = \\Closure::bind(function (\$instance) {\n"
102
                . $this->getPropertyDefaultsAssignments($privateProperties) . "\n"
103
                . '}, null, ' . var_export($className, true) . ");\n\n"
104
                . '$' . $cacheKey . "(\$this);\n\n";
105
        }
106
107
        return implode("\n", $assignments) . "\n\n";
108
    }
109
110
    /**
111
     * @param ReflectionProperty[] $properties
112
     */
113
    private function getPropertyDefaultsAssignments(array $properties) : string
114
    {
115
        return implode(
116
            "\n",
117
            array_map(
118
                function (ReflectionProperty $property) : string {
119
                    return '    $instance->' . $property->getName()
120
                        . ' = ' . $this->getExportedPropertyDefaultValue($property) . ';';
121
                },
122
                $properties
123
            )
124
        );
125
    }
126
127
    private function propertiesReferenceArrayCode(Properties $properties) : string
128
    {
129
        $referenceable = $properties->withoutNonReferenceableProperties();
130
        $assignments   = [];
131
132
        foreach ($referenceable->getAccessibleProperties() as $propertyInternalName => $property) {
133
            $assignments[] = '    '
134
                . var_export($propertyInternalName, true) . ' => & $this->' . $property->getName()
135
                . ',';
136
        }
137
138
        $code = "\$properties = [\n" . implode("\n", $assignments) . "\n];\n\n";
139
140
        // must use assignments, as direct reference during array definition causes a fatal error (not sure why)
141
        foreach ($referenceable->getGroupedPrivateProperties() as $className => $classPrivateProperties) {
142
            $cacheKey  = 'cacheFetch' . str_replace('\\', '_', $className);
143
144
            $code .= 'static $' . $cacheKey . ";\n\n"
145
                . '$' . $cacheKey . ' ?: $' . $cacheKey
146
                . " = \\Closure::bind(function (\$instance, array & \$properties) {\n"
147
                . $this->generatePrivatePropertiesAssignmentsCode($classPrivateProperties)
148
                . "}, \$this, " . var_export($className, true) . ");\n\n"
149
                . '$' . $cacheKey . "(\$this, \$properties);";
150
        }
151
152
        return $code;
153
    }
154
155
    /**
156
     * @param ReflectionProperty[] $properties indexed by internal name
157
     *
158
     * @return string
159
     */
160
    private function generatePrivatePropertiesAssignmentsCode(array $properties) : string
161
    {
162
        $code = '';
163
164
        foreach ($properties as $property) {
165
            $key   = "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName();
166
            $code .= '    $properties[' . var_export($key, true) . '] = '
167
                . '& $instance->' . $property->getName() . ";\n";
168
        }
169
170
        return $code;
171
    }
172
173
    private function getExportedPropertyDefaultValue(ReflectionProperty $property) : string
174
    {
175
        $name     = $property->getName();
176
        $defaults = $property->getDeclaringClass()->getDefaultProperties();
177
178
        return var_export($defaults[$name] ?? null, true);
179
    }
180
}
181