Completed
Push — master ( 9fe740...ab2421 )
by Matthieu
15s
created

ObjectCreationCompiler::compile()   C

Complexity

Conditions 7
Paths 73

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 69
Code Lines 45

Importance

Changes 0
Metric Value
cc 7
eloc 45
nc 73
nop 1
dl 0
loc 69
rs 6.9081
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
2
3
namespace DI\Compiler;
4
5
use DI\Compiler;
6
use DI\Definition\Exception\InvalidDefinition;
7
use DI\Definition\ObjectDefinition;
8
use DI\Definition\ObjectDefinition\MethodInjection;
9
use ReflectionClass;
10
use ReflectionMethod;
11
use ReflectionParameter;
12
use ReflectionProperty;
13
14
/**
15
 * Compiles an object definition into native PHP code that, when executed, creates the object.
16
 *
17
 * @author Matthieu Napoli <matthieu@mnapoli.fr>
18
 */
19
class ObjectCreationCompiler
20
{
21
    /**
22
     * @var Compiler
23
     */
24
    private $compiler;
25
26
    public function __construct(Compiler $compiler)
27
    {
28
        $this->compiler = $compiler;
29
    }
30
31
    public function compile(ObjectDefinition $definition)
32
    {
33
        $this->assertClassIsNotAnonymous($definition);
34
        $this->assertClassIsInstantiable($definition);
35
36
        // Lazy?
37
        if ($definition->isLazy()) {
38
            return $this->compileLazyDefinition($definition);
39
        }
40
41
        try {
42
            $classReflection = new ReflectionClass($definition->getClassName());
43
            $constructorArguments = $this->resolveParameters($definition->getConstructorInjection(), $classReflection->getConstructor());
44
            $dumpedConstructorArguments = array_map(function ($value) {
45
                return $this->compiler->compileValue($value);
46
            }, $constructorArguments);
47
48
            $code = [];
49
            $code[] = sprintf(
50
                '$object = new %s(%s);',
51
                $definition->getClassName(),
52
                implode(', ', $dumpedConstructorArguments)
53
            );
54
55
            // Property injections
56
            foreach ($definition->getPropertyInjections() as $propertyInjection) {
57
                $value = $propertyInjection->getValue();
58
                $value = $this->compiler->compileValue($value);
59
60
                $className = $propertyInjection->getClassName() ?: $definition->getClassName();
61
                $property = new ReflectionProperty($className, $propertyInjection->getPropertyName());
62
                if ($property->isPublic()) {
63
                    $code[] = sprintf('$object->%s = %s;', $propertyInjection->getPropertyName(), $value);
64
                } else {
65
                    // Private/protected property
66
                    $code[] = sprintf(
67
                        '\DI\Definition\Resolver\ObjectCreator::setPrivatePropertyValue(%s, $object, \'%s\', %s);',
68
                        var_export($propertyInjection->getClassName(), true),
69
                        $propertyInjection->getPropertyName(),
70
                        $value
71
                    );
72
                }
73
            }
74
75
            // Method injections
76
            foreach ($definition->getMethodInjections() as $methodInjection) {
77
                $methodReflection = new \ReflectionMethod($definition->getClassName(), $methodInjection->getMethodName());
78
                $parameters = $this->resolveParameters($methodInjection, $methodReflection);
79
80
                $dumpedParameters = array_map(function ($value) {
81
                    return $this->compiler->compileValue($value);
82
                }, $parameters);
83
84
                $code[] = sprintf(
85
                    '$object->%s(%s);',
86
                    $methodInjection->getMethodName(),
87
                    implode(', ', $dumpedParameters)
88
                );
89
            }
90
        } catch (InvalidDefinition $e) {
91
            throw InvalidDefinition::create($definition, sprintf(
92
                'Entry "%s" cannot be compiled: %s',
93
                $definition->getName(),
94
                $e->getMessage()
95
            ));
96
        }
97
98
        return implode("\n        ", $code);
99
    }
100
101
    public function resolveParameters(MethodInjection $definition = null, ReflectionMethod $method = null)
102
    {
103
        $args = [];
104
105
        if (! $method) {
106
            return $args;
107
        }
108
109
        $definitionParameters = $definition ? $definition->getParameters() : [];
110
111
        foreach ($method->getParameters() as $index => $parameter) {
112
            if (array_key_exists($index, $definitionParameters)) {
113
                // Look in the definition
114
                $value = &$definitionParameters[$index];
115
            } elseif ($parameter->isOptional()) {
116
                // If the parameter is optional and wasn't specified, we take its default value
117
                $args[] = $this->getParameterDefaultValue($parameter, $method);
118
                continue;
119
            } else {
120
                throw new InvalidDefinition(sprintf(
121
                    'Parameter $%s of %s has no value defined or guessable',
122
                    $parameter->getName(),
123
                    $this->getFunctionName($method)
124
                ));
125
            }
126
127
            $args[] = &$value;
128
        }
129
130
        return $args;
131
    }
132
133
    private function compileLazyDefinition(ObjectDefinition $definition) : string
134
    {
135
        $subDefinition = clone $definition;
136
        $subDefinition->setLazy(false);
137
        $subDefinition = $this->compiler->compileValue($subDefinition);
138
139
        return <<<PHP
140
        \$object = \$this->proxyFactory->createProxy(
141
            '{$definition->getClassName()}',
142
            function (&\$wrappedObject, \$proxy, \$method, \$params, &\$initializer) {
143
                \$wrappedObject = $subDefinition;
144
                \$initializer = null; // turning off further lazy initialization
145
                return true;
146
            }
147
        );
148
PHP;
149
    }
150
151
    /**
152
     * Returns the default value of a function parameter.
153
     *
154
     * @throws InvalidDefinition Can't get default values from PHP internal classes and functions
155
     * @return mixed
156
     */
157 View Code Duplication
    private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function)
0 ignored issues
show
Duplication introduced by Matthieu Napoli
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
158
    {
159
        try {
160
            return $parameter->getDefaultValue();
161
        } catch (\ReflectionException $e) {
162
            throw new InvalidDefinition(sprintf(
163
                'The parameter "%s" of %s has no type defined or guessable. It has a default value, '
164
                . 'but the default value can\'t be read through Reflection because it is a PHP internal class.',
165
                $parameter->getName(),
166
                $this->getFunctionName($function)
167
            ));
168
        }
169
    }
170
171
    private function getFunctionName(ReflectionMethod $method) : string
172
    {
173
        return $method->getName() . '()';
174
    }
175
176
    private function assertClassIsNotAnonymous(ObjectDefinition $definition)
177
    {
178
        if (strpos($definition->getClassName(), '@') !== false) {
179
            throw InvalidDefinition::create($definition, sprintf(
180
                'Entry "%s" cannot be compiled: anonymous classes cannot be compiled',
181
                $definition->getName()
182
            ));
183
        }
184
    }
185
186
    private function assertClassIsInstantiable(ObjectDefinition $definition)
187
    {
188 View Code Duplication
        if (! $definition->isInstantiable()) {
189
            // Check that the class exists
190
            if (! $definition->classExists()) {
191
                throw InvalidDefinition::create($definition, sprintf(
192
                    'Entry "%s" cannot be compiled: the class doesn\'t exist',
193
                    $definition->getName()
194
                ));
195
            }
196
            throw InvalidDefinition::create($definition, sprintf(
197
                'Entry "%s" cannot be compiled: the class is not instantiable',
198
                $definition->getName()
199
            ));
200
        }
201
    }
202
}
203