Completed
Push — master ( af288c...9fe740 )
by Matthieu
06:20 queued 04:14
created

CreateDefinitionHelper::getDefinition()   C

Complexity

Conditions 8
Paths 16

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 36
Code Lines 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 20
nc 16
nop 1
dl 0
loc 36
rs 5.3846
c 1
b 0
f 0
1
<?php
2
3
namespace DI\Definition\Helper;
4
5
use DI\Definition\Definition;
6
use DI\Definition\Exception\InvalidDefinition;
7
use DI\Definition\ObjectDefinition;
8
use DI\Definition\ObjectDefinition\MethodInjection;
9
use DI\Definition\ObjectDefinition\PropertyInjection;
10
11
/**
12
 * Helps defining how to create an instance of a class.
13
 *
14
 * @author Matthieu Napoli <[email protected]>
15
 */
16
class CreateDefinitionHelper implements DefinitionHelper
17
{
18
    const DEFINITION_CLASS = ObjectDefinition::class;
19
20
    /**
21
     * @var string|null
22
     */
23
    private $className;
24
25
    /**
26
     * @var bool|null
27
     */
28
    private $lazy;
29
30
    /**
31
     * Array of constructor parameters.
32
     * @var array
33
     */
34
    protected $constructor = [];
35
36
    /**
37
     * Array of properties and their value.
38
     * @var array
39
     */
40
    private $properties = [];
41
42
    /**
43
     * Array of methods and their parameters.
44
     * @var array
45
     */
46
    protected $methods = [];
47
48
    /**
49
     * Helper for defining an object.
50
     *
51
     * @param string|null $className Class name of the object.
52
     *                               If null, the name of the entry (in the container) will be used as class name.
53
     */
54
    public function __construct(string $className = null)
55
    {
56
        $this->className = $className;
57
    }
58
59
    /**
60
     * Define the entry as lazy.
61
     *
62
     * A lazy entry is created only when it is used, a proxy is injected instead.
63
     *
64
     * @return $this
65
     */
66
    public function lazy()
67
    {
68
        $this->lazy = true;
69
70
        return $this;
71
    }
72
73
    /**
74
     * Defines the arguments to use to call the constructor.
75
     *
76
     * This method takes a variable number of arguments, example:
77
     *     ->constructor($param1, $param2, $param3)
78
     *
79
     * @param mixed ... Parameters to use for calling the constructor of the class.
80
     *
81
     * @return $this
82
     */
83
    public function constructor(...$parameters)
84
    {
85
        $this->constructor = $parameters;
86
87
        return $this;
88
    }
89
90
    /**
91
     * Defines a value to inject in a property of the object.
92
     *
93
     * @param string $property Entry in which to inject the value.
94
     * @param mixed  $value    Value to inject in the property.
95
     *
96
     * @return $this
97
     */
98
    public function property(string $property, $value)
99
    {
100
        $this->properties[$property] = $value;
101
102
        return $this;
103
    }
104
105
    /**
106
     * Defines a method to call and the arguments to use.
107
     *
108
     * This method takes a variable number of arguments after the method name, example:
109
     *
110
     *     ->method('myMethod', $param1, $param2)
111
     *
112
     * Can be used multiple times to declare multiple calls.
113
     *
114
     * @param string $method Name of the method to call.
115
     * @param mixed  ...     Parameters to use for calling the method.
116
     *
117
     * @return $this
118
     */
119
    public function method(string $method, ...$parameters)
120
    {
121
        if (! isset($this->methods[$method])) {
122
            $this->methods[$method] = [];
123
        }
124
125
        $this->methods[$method][] = $parameters;
126
127
        return $this;
128
    }
129
130
    /**
131
     * @return ObjectDefinition
132
     */
133
    public function getDefinition(string $entryName) : Definition
134
    {
135
        $class = $this::DEFINITION_CLASS;
136
        /** @var ObjectDefinition $definition */
137
        $definition = new $class($entryName, $this->className);
138
139
        if ($this->lazy !== null) {
140
            $definition->setLazy($this->lazy);
141
        }
142
143
        if (! empty($this->constructor)) {
144
            $parameters = $this->fixParameters($definition, '__construct', $this->constructor);
145
            $constructorInjection = MethodInjection::constructor($parameters);
146
            $definition->setConstructorInjection($constructorInjection);
147
        }
148
149
        if (! empty($this->properties)) {
150
            foreach ($this->properties as $property => $value) {
151
                $definition->addPropertyInjection(
152
                    new PropertyInjection($property, $value)
153
                );
154
            }
155
        }
156
157
        if (! empty($this->methods)) {
158
            foreach ($this->methods as $method => $calls) {
159
                foreach ($calls as $parameters) {
160
                    $parameters = $this->fixParameters($definition, $method, $parameters);
161
                    $methodInjection = new MethodInjection($method, $parameters);
162
                    $definition->addMethodInjection($methodInjection);
163
                }
164
            }
165
        }
166
167
        return $definition;
168
    }
169
170
    /**
171
     * Fixes parameters indexed by the parameter name -> reindex by position.
172
     *
173
     * This is necessary so that merging definitions between sources is possible.
174
     *
175
     * @throws InvalidDefinition
176
     */
177
    private function fixParameters(ObjectDefinition $definition, string $method, array $parameters) : array
178
    {
179
        $fixedParameters = [];
180
181
        foreach ($parameters as $index => $parameter) {
182
            // Parameter indexed by the parameter name, we reindex it with its position
183
            if (is_string($index)) {
184
                $callable = [$definition->getClassName(), $method];
185
                try {
186
                    $reflectionParameter = new \ReflectionParameter($callable, $index);
187
                } catch (\ReflectionException $e) {
188
                    throw InvalidDefinition::create($definition, sprintf("Parameter with name '%s' could not be found. %s.", $index, $e->getMessage()));
189
                }
190
191
                $index = $reflectionParameter->getPosition();
192
            }
193
194
            $fixedParameters[$index] = $parameter;
195
        }
196
197
        return $fixedParameters;
198
    }
199
}
200