Passed
Branch master (2fcc75)
by Paul
11:46
created

Container::resolveDependencies()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.7085

Importance

Changes 3
Bugs 0 Features 2
Metric Value
cc 3
eloc 6
c 3
b 0
f 2
nc 3
nop 1
dl 0
loc 9
ccs 4
cts 7
cp 0.5714
crap 3.7085
rs 10
1
<?php
2
3
namespace GeminiLabs\SiteReviews;
4
5
use Closure;
6
use Exception;
7
use GeminiLabs\SiteReviews\Helpers\Arr;
8
use GeminiLabs\SiteReviews\Helpers\Str;
9
use ReflectionClass;
10
use ReflectionParameter;
11
12
abstract class Container
13
{
14
    const PROTECTED_PROPERTIES = [
15
        'instance',
16
        'services',
17
        'session',
18
        'storage',
19
    ];
20
21
    /**
22
     * @var static
23
     */
24
    protected static $instance;
25
26
    /**
27
     * The container's bound services.
28
     * @var array
29
     */
30
    protected $services = [];
31
32
    /**
33
     * @var array
34
     */
35
    protected $session = [];
36
37
    /**
38
     * The container's storage items.
39
     * @var array
40
     */
41
    protected $storage = [];
42
43
    /**
44
     * @return static
45
     */
46 7
    public static function load()
47
    {
48 7
        if (empty(static::$instance)) {
49
            static::$instance = new static();
50
        }
51 7
        return static::$instance;
52
    }
53
54
    /**
55
     * @param string $property
56
     * @return mixed
57
     */
58 7
    public function __get($property)
59
    {
60 7
        if (property_exists($this, $property) && !in_array($property, static::PROTECTED_PROPERTIES)) {
61
            return $this->$property;
62
        }
63 7
        $constant = 'static::'.strtoupper(Str::snakeCase($property));
64 7
        if (defined($constant)) {
65 7
            return constant($constant);
66
        }
67
        return Arr::get($this->storage, $property, null);
68
    }
69
70
    /**
71
     * @param string $property
72
     * @param string $value
73
     * @return void
74
     */
75
    public function __set($property, $value)
76
    {
77
        if (!property_exists($this, $property) || in_array($property, static::PROTECTED_PROPERTIES)) {
78
            $this->storage[$property] = $value;
79
        } elseif (!isset($this->$property)) {
80
            $this->$property = $value;
81
        } else {
82
            throw new Exception(sprintf('The "%s" property cannot be changed once set.', $property));
83
        }
84
    }
85
86
    /**
87
     * Bind a service to the container.
88
     * @param string $alias
89
     * @param mixed $concrete
90
     * @return mixed
91
     */
92
    public function bind($alias, $concrete)
93
    {
94
        $this->services[$alias] = $concrete;
95
    }
96
97
    /**
98
     * Request a service from the container.
99
     * @param mixed $abstract
100
     * @return mixed
101
     */
102 7
    public function make($abstract)
103
    {
104 7
        if (!isset($this->services[$abstract])) {
105 7
            $abstract = $this->addNamespace($abstract);
106
        }
107 7
        if (isset($this->services[$abstract])) {
108 7
            $abstract = $this->services[$abstract];
109
        }
110 7
        if (is_callable($abstract)) {
111
            return call_user_func_array($abstract, [$this]);
112
        }
113 7
        if (is_object($abstract)) {
114 7
            return $abstract;
115
        }
116 7
        return $this->resolve($abstract);
117
    }
118
119
    /**
120
     * @return void
121
     */
122 1
    public function sessionClear()
123
    {
124 1
        $this->session = [];
125 1
    }
126
127
    /**
128
     * @return mixed
129
     */
130 1
    public function sessionGet($key, $fallback = '')
131
    {
132 1
        $value = Arr::get($this->session, $key, $fallback);
133 1
        unset($this->session[$key]);
134 1
        return $value;
135
    }
136
137
    /**
138
     * @return void
139
     */
140 1
    public function sessionSet($key, $value)
141
    {
142 1
        $this->session[$key] = $value;
143 1
    }
144
145
    /**
146
     * Bind a singleton instance to the container.
147
     * @param string $alias
148
     * @param callable|string|null $binding
149
     * @return void
150
     */
151
    public function singleton($alias, $binding)
152
    {
153
        $this->bind($alias, $this->make($binding));
154
    }
155
156
    /**
157
     * Prefix the current namespace to the abstract if absent.
158
     * @param string $abstract
159
     * @return string
160
     */
161 7
    protected function addNamespace($abstract)
162
    {
163 7
        if (!Str::contains($abstract, __NAMESPACE__) && !class_exists($abstract)) {
164 7
            $abstract = __NAMESPACE__.'\\'.$abstract;
165
        }
166 7
        return $abstract;
167
    }
168
169
    /**
170
     * Resolve a service from the container.
171
     * @param mixed $concrete
172
     * @return mixed
173
     * @throws Exception
174
     */
175 7
    protected function resolve($concrete)
176
    {
177 7
        if ($concrete instanceof Closure) {
178
            return $concrete($this);
179
        }
180 7
        $reflector = new ReflectionClass($concrete);
181 7
        if (!$reflector->isInstantiable()) {
182
            throw new Exception('Target ['.$concrete.'] is not instantiable.');
183
        }
184 7
        $constructor = $reflector->getConstructor();
185 7
        if (empty($constructor)) {
186 7
            return new $concrete();
187
        }
188 7
        return $reflector->newInstanceArgs(
189 7
            $this->resolveDependencies($constructor->getParameters())
190
        );
191
    }
192
193
    /**
194
     * Resolve a class based dependency from the container.
195
     * @return mixed
196
     * @throws Exception
197
     */
198
    protected function resolveClass(ReflectionParameter $parameter)
199
    {
200
        try {
201
            return $this->make($parameter->getClass()->name);
202
        } catch (Exception $error) {
203
            if ($parameter->isOptional()) {
204
                return $parameter->getDefaultValue();
205
            }
206
            throw $error;
207
        }
208
    }
209
210
    /**
211
     * Resolve all of the dependencies from the ReflectionParameters.
212
     * @return array
213
     */
214 7
    protected function resolveDependencies(array $dependencies)
215
    {
216 7
        $results = [];
217 7
        foreach ($dependencies as $dependency) {
218
            $results[] = !is_null($class = $dependency->getClass())
219
                ? $this->resolveClass($dependency)
220
                : $this->resolveDependency($dependency);
221
        }
222 7
        return $results;
223
    }
224
225
    /**
226
     * Resolve a single ReflectionParameter dependency.
227
     * @return array|null
228
     */
229
    protected function resolveDependency(ReflectionParameter $parameter)
230
    {
231
        if ($parameter->isArray() && $parameter->isDefaultValueAvailable()) {
232
            return $parameter->getDefaultValue();
233
        }
234
        return null;
235
    }
236
}
237