Completed
Branch BUG-9871-email-validation (e62b1a)
by
unknown
350:41 queued 333:27
created

DependencyInjector::getConstructor()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 2
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace EventEspresso\core\services\container;
3
4
use UnexpectedValueException;
5
6
if ( ! defined('EVENT_ESPRESSO_VERSION')) {
7
    exit('No direct script access allowed');
8
}
9
10
11
12
/**
13
 * Class DependencyInjector
14
 * Responsible for recursively resolving and injecting dependencies
15
 * into the arguments array passed to a class upon instantiation.
16
 * Caches all Reflection objects generated so that this work isn't duplicated.
17
 *
18
 * @package       Event Espresso
19
 * @author        Brent Christensen
20
 * @since         4.9.1
21
 */
22
class DependencyInjector implements InjectorInterface
23
{
24
25
    /**
26
     * @var CoffeePotInterface $coffee_pot
27
     */
28
    private $coffee_pot;
29
30
    /**
31
     * @var \EEH_Array $array_helper
32
     */
33
    private $array_helper;
34
35
    /**
36
     * @var \ReflectionClass[] $reflectors
37
     */
38
    private $reflectors;
39
40
    /**
41
     * @var \ReflectionMethod[] $constructors
42
     */
43
    private $constructors;
44
45
    /**
46
     * @var \ReflectionParameter[] $parameters
47
     */
48
    private $parameters;
49
50
51
52
    /**
53
     * DependencyInjector constructor
54
     *
55
     * @param CoffeePotInterface $coffee_pot
56
     * @param \EEH_Array         $array_helper
57
     */
58
    public function __construct(CoffeePotInterface $coffee_pot, \EEH_Array $array_helper)
59
    {
60
        $this->coffee_pot = $coffee_pot;
61
        $this->array_helper = $array_helper;
62
    }
63
64
65
66
    /**
67
     * getReflectionClass
68
     * checks if a ReflectionClass object has already been generated for a class
69
     * and returns that instead of creating a new one
70
     *
71
     * @param string $class_name
72
     * @return \ReflectionClass
73
     */
74 View Code Duplication
    public function getReflectionClass($class_name)
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...
75
    {
76
        if (
77
            ! isset($this->reflectors[$class_name])
78
            || ! $this->reflectors[$class_name] instanceof \ReflectionClass
79
        ) {
80
            $this->reflectors[$class_name] = new \ReflectionClass($class_name);
81
        }
82
        return $this->reflectors[$class_name];
83
    }
84
85
86
87
    /**
88
     * getConstructor
89
     * checks if a ReflectionMethod object has already been generated for the class constructor
90
     * and returns that instead of creating a new one
91
     *
92
     * @param \ReflectionClass $reflector
93
     * @return \ReflectionMethod
94
     */
95
    protected function getConstructor(\ReflectionClass $reflector)
96
    {
97
        if (
98
            ! isset($this->constructors[$reflector->getName()])
99
            || ! $this->constructors[$reflector->getName()] instanceof \ReflectionMethod
100
        ) {
101
            $this->constructors[$reflector->getName()] = $reflector->getConstructor();
102
        }
103
        return $this->constructors[$reflector->getName()];
104
    }
105
106
107
108
    /**
109
     * getParameters
110
     * checks if an array of ReflectionParameter objects has already been generated for the class constructor
111
     * and returns that instead of creating a new one
112
     *
113
     * @param \ReflectionMethod $constructor
114
     * @return \ReflectionParameter[]
115
     */
116
    protected function getParameters(\ReflectionMethod $constructor)
117
    {
118
        if ( ! isset($this->parameters[$constructor->class])) {
119
            $this->parameters[$constructor->class] = $constructor->getParameters();
120
        }
121
        return $this->parameters[$constructor->class];
122
    }
123
124
125
126
    /**
127
     * resolveDependencies
128
     * examines the constructor for the requested class to determine
129
     * if any dependencies exist, and if they can be injected.
130
     * If so, then those classes will be added to the array of arguments passed to the constructor
131
     * PLZ NOTE: this is achieved by type hinting the constructor params
132
     * For example:
133
     *        if attempting to load a class "Foo" with the following constructor:
134
     *        __construct( Bar $bar_class, Fighter $grohl_class )
135
     *        then $bar_class and $grohl_class will be added to the $arguments array,
136
     *        but only IF they are NOT already present in the incoming arguments array,
137
     *        and the correct classes can be loaded
138
     *
139
     * @param RecipeInterface   $recipe
140
     * @param \ReflectionClass  $reflector
141
     * @param array             $arguments
142
     * @return array
143
     */
144
    public function resolveDependencies(RecipeInterface $recipe, \ReflectionClass $reflector, $arguments = array())
145
    {
146
        // if arguments array is numerically and sequentially indexed, then we want it to remain as is,
147
        // else wrap it in an additional array so that it doesn't get split into multiple parameters
148
        $arguments = $this->array_helper->is_array_numerically_and_sequentially_indexed($arguments)
149
            ? $arguments
150
            : array($arguments);
151
        $resolved_parameters = array();
152
        // let's examine the constructor
153
        // let's examine the constructor
154
        $constructor = $this->getConstructor($reflector);
155
        // whu? huh? nothing?
156
        if ( ! $constructor) {
157
            return $arguments;
158
        }
159
        // get constructor parameters
160
        $params = $this->getParameters($constructor);
161
        if (empty($params)) {
162
            return $resolved_parameters;
163
        }
164
        $ingredients = $recipe->ingredients();
165
        // and the keys for the incoming arguments array so that we can compare existing arguments with what is expected
166
        $argument_keys = array_keys($arguments);
167
        // now loop thru all of the constructors expected parameters
168
        foreach ($params as $index => $param) {
0 ignored issues
show
Bug introduced by
The expression $params of type object<ReflectionParamet...t<ReflectionParameter>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
169
            if ( ! $param instanceof \ReflectionParameter) {
170
                continue;
171
            }
172
            // is this a dependency for a specific class ?
173
            $param_class = $param->getClass() ? $param->getClass()->name : null;
174
            if (
175
                // param is specified in the list of ingredients for this Recipe
176
                isset($ingredients[$param_class])
177
            ) {
178
                // attempt to inject the dependency
179
                $resolved_parameters[$index] = $this->injectDependency($ingredients[$param_class]);
180
            } else if (
181
                // param is not even a class
182
                empty($param_class)
183
                // and something already exists in the incoming arguments for this param
184
                && isset($argument_keys[$index], $arguments[$argument_keys[$index]])
185
            ) {
186
                // add parameter from incoming arguments
187
                $resolved_parameters[$index] = $arguments[$argument_keys[$index]];
188
            } else if (
189
                // parameter is type hinted as a class, exists as an incoming argument, AND it's the correct class
190
                ! empty($param_class)
191
                && isset($argument_keys[$index], $arguments[$argument_keys[$index]])
192
                && $arguments[$argument_keys[$index]] instanceof $param_class
193
            ) {
194
                // add parameter from incoming arguments
195
                $resolved_parameters[$index] = $arguments[$argument_keys[$index]];
196
            } else if (
197
                // parameter is type hinted as a class, and should be injected
198
            ! empty($param_class)
199
            ) {
200
                // attempt to inject the dependency
201
                $resolved_parameters[$index] = $this->injectDependency($param_class);
202
            } else if ($param->isOptional()) {
203
                $resolved_parameters[$index] = $param->getDefaultValue();
204
            } else {
205
                $resolved_parameters[$index] = null;
206
            }
207
        }
208
        return $resolved_parameters;
209
    }
210
211
212
213
    /**
214
     * @param string $param_class
215
     * @return mixed
216
     */
217
    private function injectDependency($param_class)
218
    {
219
        $dependency = $this->coffee_pot->brew($param_class);
220
        if ( ! $dependency instanceof $param_class) {
221
            throw new UnexpectedValueException(
222
                sprintf(
223
                    __(
224
                        'Could not resolve dependency for "%1$s" for the "%2$s" class constructor.',
225
                        'event_espresso'
226
                    ),
227
                    $param_class
228
                )
229
            );
230
        }
231
        return $dependency;
232
    }
233
234
}
235
// End of file DependencyInjector.php
236
// Location: /DependencyInjector.php