Passed
Pull Request — master (#26)
by Jitendra
01:50
created

Extension   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 31
dl 0
loc 228
rs 9.8
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A instantiate() 0 14 3
A resolve() 0 19 4
A resolveDependencies() 0 20 4
A replace() 0 12 3
A resolveSubClassOrNullable() 0 11 3
A resolveDependency() 0 18 4
A restore() 0 12 3
A inferAlias() 0 7 4
A registerAliases() 0 11 3
1
<?php
2
3
namespace PhalconExt\Di;
4
5
/**
6
 * An extension to phalcon di.
7
 *
8
 * @author  Jitendra Adhikari <[email protected]>
9
 * @license MIT
10
 *
11
 * @link    https://github.com/adhocore/phalcon-ext
12
 */
13
trait Extension
14
{
15
    /** @var array Names of services currently resolving */
16
    protected $resolving = [];
17
18
    /** @var array Original services backup */
19
    protected $original  = [];
20
21
    /** @var array The aliases of services */
22
    protected $aliases   = [];
23
24
    // Implemented by \Phalcon\Di.
25
    abstract public function set($service, $definition, $shared = false);
26
27
    abstract public function get($service, $parameters = null);
28
29
    abstract public function has($service);
30
31
    abstract public function remove($service);
32
33
    /**
34
     * Resolve all the dependencies for a class FQCN and instantiate it.
35
     *
36
     * @param string $class      FQCN
37
     * @param array  $parameters Parameters
38
     *
39
     * @return object
40
     */
41
    public function resolve(string $class, array $parameters = [])
42
    {
43
        if ($this->has($class)) {
44
            return $this->get($class, $parameters);
45
        }
46
47
        if ($this->aliases[$class] ?? null) {
48
            return $this->get($this->aliases[$class]);
49
        }
50
51
        if ($this->resolving[$class] ?? null) {
52
            throw new \RuntimeException('Cyclic dependency for class: ' . $class);
53
        }
54
55
        $this->resolving[$class] = true;
56
        $this->set($class, $resolved = $this->instantiate($class, $parameters), true);
57
        unset($this->resolving[$class]);
58
59
        return $resolved;
60
    }
61
62
    /**
63
     * Instantiate class FQCN by constructor injecting the dependencies from the DI.
64
     *
65
     * @param string $class      FQCN
66
     * @param array  $parameters Parameters
67
     *
68
     * @return object
69
     */
70
    protected function instantiate(string $class, array $parameters = [])
71
    {
72
        $reflector = new \ReflectionClass($class);
73
74
        if (!$reflector->isInstantiable()) {
75
            throw new \RuntimeException('Cannot instantiate class: ' . $class);
76
        }
77
78
        if ([] === $dependencies = $reflector->getConstructor()->getParameters()) {
79
            return $reflector->newInstance();
80
        }
81
82
        return $reflector->newInstanceArgs(
83
            $this->resolveDependencies($dependencies, $parameters)
84
        );
85
    }
86
87
    /**
88
     * Resolve dependencies of a class.
89
     *
90
     * @param array $dependencies
91
     * @param array $parameters
92
     *
93
     * @return mixed
94
     */
95
    protected function resolveDependencies(array $dependencies, array $parameters)
96
    {
97
        $resolved = [];
98
99
        foreach ($dependencies as $dependency) {
100
            $name = $dependency->name;
101
102
            // Already available in parameters.
103
            if (isset($parameters[$name])) {
104
                $resolved[] = $parameters[$name];
105
            }
106
            // Already registered in DI.
107
            elseif ($this->has($name)) {
108
                $resolved[] = $this->get($name);
109
            } else {
110
                $resolved[] = $this->resolveDependency($dependency);
111
            }
112
        }
113
114
        return $resolved;
115
    }
116
117
    /**
118
     * Resolve a dependency.
119
     *
120
     * @param \ReflectionParameter $dependency
121
     *
122
     * @return mixed
123
     */
124
    protected function resolveDependency(\ReflectionParameter $dependency)
125
    {
126
        $allowsNull = $dependency->allowsNull();
127
128
        // Is a class and needs to be resolved OR is nullable.
129
        if ($subClass = $dependency->getClass()) {
130
            return $this->resolveSubClassOrNullable($subClass->name, $allowsNull);
131
        }
132
        if ($allowsNull) {
133
            return null;
134
        }
135
136
        // Use default value.
137
        if ($dependency->isOptional()) {
138
            return $dependency->getDefaultValue();
139
        }
140
141
        throw new \RuntimeException('Cannot resolve dependency: $' . $dependency->name);
142
    }
143
144
    /**
145
     * Resolve subClass or nullable.
146
     *
147
     * @param string $subClass
148
     * @param bool   $allowsNull
149
     *
150
     * @return mixed
151
     */
152
    protected function resolveSubClassOrNullable(string $subClass, bool $allowsNull = false)
153
    {
154
        try {
155
            return $this->resolve($subClass);
156
        } catch (\Throwable $e) {
157
            if (!$allowsNull) {
158
                throw $e;
159
            }
160
        }
161
162
        return null;
163
    }
164
165
    /**
166
     * Replace services with another one. Great for test mocks.
167
     *
168
     * @param array $services
169
     *
170
     * @return self
171
     */
172
    public function replace(array $services): self
173
    {
174
        foreach ($services as $name => $definition) {
175
            if ($this->has($name)) {
176
                $this->original[$name] = $this->get($name);
177
                $this->remove($name);
178
            }
179
180
            $this->set($name, $definition);
181
        }
182
183
        return $this;
184
    }
185
186
    /**
187
     * Restore given service or all.
188
     *
189
     * @param array|null $name
190
     *
191
     * @return self
192
     */
193
    public function restore(array $names = null): self
194
    {
195
        foreach ($names ?? \array_keys($this->original) as $name) {
196
            if ($this->has($name)) {
197
                $this->remove($name);
198
                $this->set($name, $this->original[$name], true);
199
            }
200
201
            unset($this->original[$name]);
202
        }
203
204
        return $this;
205
    }
206
207
    /**
208
     * Register aliases for services.
209
     *
210
     * @param array $aliases
211
     *
212
     * @return self
213
     */
214
    public function registerAliases(array $aliases = []): self
215
    {
216
        $this->aliases += $aliases;
217
218
        foreach ($this->_services as $name => $service) {
219
            if ('' !== $alias = $this->inferAlias($service->getDefinition())) {
220
                $this->aliases[$alias] = $name;
221
            }
222
        }
223
224
        return $this;
225
    }
226
227
    /**
228
     * Infer alias of service definition.
229
     *
230
     * @param mixed $definition
231
     *
232
     * @return string
233
     */
234
    protected function inferAlias($definition): string
235
    {
236
        if (\is_object($definition) && !$definition instanceof \Closure) {
237
            return \get_class($definition);
238
        }
239
240
        return \is_string($definition) ? $definition : '';
241
    }
242
}
243