Completed
Pull Request — master (#4)
by Joao
01:15
created

DependencyInjection::getInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
4
namespace ByJG\Config;
5
6
use ByJG\Config\Exception\DependencyInjectionException;
7
use ByJG\Config\Exception\KeyNotFoundException;
8
use Psr\Container\ContainerInterface;
9
use ReflectionClass;
10
use ReflectionException;
11
use ReflectionMethod;
12
13
class DependencyInjection
14
{
15
    /**
16
     * @var ContainerInterface
17
     */
18
    protected $containerInterface;
19
20
    protected $class;
21
22
    protected $args = [];
23
24
    protected $instance;
25
26
    protected $singleton = false;
27
28
    protected $factory = null;
29
30
    protected $methodCall = [];
31
32
    /**
33
     * @param $containerInterface ContainerInterface
34
     * @return DependencyInjection
35
     */
36
    public function injectContainer($containerInterface)
37
    {
38
        $this->containerInterface = $containerInterface;
39
        return $this;
40
    }
41
42
    /**
43
     * @return mixed
44
     */
45
    protected function getClass()
46
    {
47
        return $this->class;
48
    }
49
50
    /**
51
     * @param mixed $class
52
     * @throws DependencyInjectionException
53
     */
54
    protected function setClass($class)
55
    {
56
        if (!class_exists($class)) {
57
            throw new DependencyInjectionException("Class $class does not exists");
58
        }
59
        $this->class = $class;
60
    }
61
62
    /**
63
     * @return mixed
64
     */
65
    protected function getArgs()
66
    {
67
            return array_map(function ($value) {
68
                if ($value instanceof Param) {
69
                    try {
70
                        return $this->containerInterface->get($value->getParam());
71
                    } catch (KeyNotFoundException $ex) {
72
                        throw new KeyNotFoundException($ex->getMessage() . " injected from '" . $this->getClass() . "'");
73
                    }
74
                }
75
                return $value;
76
            }, $this->args);
77
    }
78
79
    /**
80
     * @param mixed $args
81
     * @return DependencyInjection
82
     * @throws DependencyInjectionException
83
     */
84 View Code Duplication
    public function withConstructorArgs($args)
0 ignored issues
show
Duplication introduced by
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...
85
    {
86
        if (!is_null($args) && !is_array($args)) {
87
            throw new DependencyInjectionException("Arguments should be an array");
88
        }
89
        $this->args = $args;
0 ignored issues
show
Documentation Bug introduced by
It seems like $args can be null. However, the property $args is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
90
91
        return $this;
92
    }
93
94
    /**
95
     * @param mixed $args
96
     * @return DependencyInjection
97
     * @throws DependencyInjectionException
98
     */
99 View Code Duplication
    public function withFactoryMethod($method, $args = [])
0 ignored issues
show
Duplication introduced by
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...
100
    {
101
        if (!is_null($args) && !is_array($args)) {
102
            throw new DependencyInjectionException("Arguments should be an array");
103
        }
104
        $this->args = $args;
0 ignored issues
show
Documentation Bug introduced by
It seems like $args can be null. However, the property $args is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
105
106
        $this->factory = $method;
107
108
        return $this;
109
    }
110
111
    /**
112
     * DependencyInjection constructor.
113
     * @param $class
114
     * @throws DependencyInjectionException
115
     */
116
    protected function __construct($class)
117
    {
118
        $this->setClass($class);
119
    }
120
121
    /**
122
     * @param $class
123
     * @return DependencyInjection
124
     * @throws DependencyInjectionException
125
     */
126
    public static function bind($class)
127
    {
128
        return new DependencyInjection($class);
129
    }
130
131
    /**
132
     * @return DependencyInjection
133
     * @throws DependencyInjectionException
134
     * @throws ReflectionException
135
     */
136
    public function withInjectedConstructor()
137
    {
138
        $reflection = new ReflectionMethod($this->getClass(), "__construct");
139
        $params = $reflection->getParameters();
140
141
        if (count($params) > 0) {
142
            $args = [];
143
            foreach ($params as $param) {
144
                $type = $param->getType();
145
                if (is_null($type)) {
146
                    throw new DependencyInjectionException("The parameter '$" . $param->getName() . "' has no type defined in class '" . $this->getClass() . "'");
0 ignored issues
show
Bug introduced by
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
147
                }
148
                $args[] = Param::get(ltrim($type, "\\"));
149
            }
150
            return $this->withConstructorArgs($args);
151
        }
152
153
        return $this->withNoConstructor();
154
    }
155
156
    /**
157
     * @return DependencyInjection
158
     * @throws DependencyInjectionException
159
     * @throws ReflectionException
160
     */
161
    public function withInjectedLegacyConstructor()
162
    {
163
        $reflection = new ReflectionMethod($this->getClass(), "__construct");
164
165
        $docComments = str_replace("\n", " ", $reflection->getDocComment());
166
167
        $methodParams = $reflection->getParameters();
168
169
        $params = [];
170
        $result = preg_match_all('/@param\s+([\d\w_\\\\]+)\s+\$[\w_\d]+/', $docComments, $params);
171
172
        if (count($methodParams) <> $result) {
173
            throw new DependencyInjectionException("The class " . $this->getClass() . " does not have annotations with the param type.");
174
        }
175
176
        if (count($methodParams) > 0) {
177
            $args = [];
178
            foreach ($params[1] as $param) {
179
                $args[] = Param::get(ltrim($param, "\\"));
180
            }
181
            return $this->withConstructorArgs($args);
182
        }
183
184
        return $this->withNoConstructor();
185
    }
186
187
    /**
188
     * @return DependencyInjection
189
     */
190
    public function withNoConstructor()
191
    {
192
        $this->args = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $args.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
193
        return $this;
194
    }
195
196
    public function withMethodCall($method, $args = [])
197
    {
198
        $this->methodCall[] = [$method, $args];
199
        return $this;
200
    }
201
202
    /**
203
     * @return DependencyInjection
204
     */
205
    public function toSingleton()
206
    {
207
        $this->singleton = true;
208
        return $this;
209
    }
210
211
    /**
212
     * @return DependencyInjection
213
     * @throws DependencyInjectionException
214
     * @throws ReflectionException
215
     */
216
    public function toEagerSingleton()
217
    {
218
        $this->singleton = true;
219
        $this->getInstance();
220
        return $this;
221
    }
222
223
    /**
224
     * @return DependencyInjection
225
     */
226
    public function toInstance()
227
    {
228
        $this->singleton = false;
229
        return $this;
230
    }
231
232
    /**
233
     * @return object
234
     * @throws DependencyInjectionException
235
     * @throws ReflectionException
236
     */
237
    public function getInstance()
238
    {
239
        $instance = $this->getInternalInstance();
240
241
        if (is_null($instance)) {
242
            throw new DependencyInjectionException("Could not get a instance of " . $this->getClass());
243
        }
244
245
        return $instance;
246
    }
247
248
    /**
249
     * @return object
250
     * @throws ReflectionException
251
     */
252
    protected function getInternalInstance()
253
    {
254
        if ($this->singleton) {
255
            return $this->getSingletonInstace();
256
        }
257
258
        return $this->getNewInstance();
259
    }
260
261
    /**
262
     * @return object
263
     * @throws ReflectionException
264
     */
265
    protected function getNewInstance()
266
    {
267
        if (!empty($this->factory)) {
268
            return $this->callMethods(call_user_func_array([$this->getClass(), $this->factory], $this->getArgs()));
269
        }
270
271
        $reflectionClass = new ReflectionClass($this->getClass());
272
273
        if (is_null($this->args)) {
274
            return $this->callMethods($reflectionClass->newInstanceWithoutConstructor());
275
        }
276
277
        return $this->callMethods($reflectionClass->newInstanceArgs($this->getArgs()));
278
    }
279
280
    /**
281
     * @param $instance
282
     * @return mixed
283
     */
284
    protected function callMethods($instance)
285
    {
286
        foreach ($this->methodCall as $methodDefinition) {
287
            if (is_null($methodDefinition[1])) {
288
                call_user_func([$instance, $methodDefinition[0]]);
289
            } else {
290
                call_user_func_array([$instance, $methodDefinition[0]], $methodDefinition[1]);
291
            }
292
        }
293
294
        return $instance;
295
    }
296
297
    /**
298
     * @return object
299
     * @throws ReflectionException
300
     */
301
    protected function getSingletonInstace()
302
    {
303
        if (empty($this->instance)) {
304
            $this->instance = $this->getNewInstance();
305
        }
306
        return $this->instance;
307
    }
308
}
309