Passed
Pull Request — master (#32)
by Jitendra
02:52
created

Extension   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 238
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 34
eloc 70
dl 0
loc 238
rs 9.68
c 0
b 0
f 0

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