Extension   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 251
Duplicated Lines 0 %

Importance

Changes 15
Bugs 2 Features 1
Metric Value
wmc 35
eloc 73
c 15
b 2
f 1
dl 0
loc 251
rs 9.6

11 Methods

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