Container   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 233
Duplicated Lines 0 %

Test Coverage

Coverage 4.76%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 51
c 2
b 0
f 0
dl 0
loc 233
ccs 3
cts 63
cp 0.0476
rs 10
wmc 28

13 Methods

Rating   Name   Duplication   Size   Complexity  
A singleton() 0 3 1
A register() 0 3 1
A __get() 0 5 2
A __set() 0 3 1
A bind() 0 3 1
A make() 0 14 4
A getInstance() 0 7 2
A addNamespace() 0 7 3
A getClass() 0 6 2
A resolveDependencies() 0 12 3
A resolveClass() 0 10 3
A notInstantiable() 0 5 1
A resolve() 0 18 4
1
<?php
2
3
namespace GeminiLabs\Pollux;
4
5
use Closure;
6
use Exception;
7
use ReflectionClass;
8
use ReflectionParameter;
9
10
abstract class Container
11
{
12
	/**
13
	 * The current globally available container (if any).
14
	 *
15
	 * @var static
16
	 */
17
	protected static $instance;
18
19
    /**
20
     * The container's bound services.
21
     *
22
     * @var array
23
     */
24
	protected $services = [];
25
26
    /**
27
     * The container's bucket items
28
     *
29
     * @var array
30
     */
31
	protected $bucket = [];
32
33
	/**
34
	 * Set the globally available instance of the container.
35
	 *
36
	 * @return static
37
	 */
38 14
	public static function getInstance()
39
	{
40 14
		if( is_null( static::$instance )) {
41
			static::$instance = new static;
42
		}
43
44 14
		return static::$instance;
45
	}
46
47
	/**
48
	 * Bind a service to the container.
49
	 *
50
	 * @param string $alias
51
	 * @param mixed  $concrete
52
	 *
53
	 * @return mixed
54
	 */
55
	public function bind( $alias, $concrete )
56
	{
57
		$this->services[$alias] = $concrete;
58
	}
59
60
	/**
61
	 * Resolve the given type from the container.
62
	 * Allow unbound aliases that omit the root namespace
63
	 * i.e. 'Controller' translates to 'GeminiLabs\Pollux\Controller'
64
	 *
65
	 * @param mixed $abstract
66
	 *
67
	 * @return mixed
68
	 */
69
	public function make( $abstract )
70
	{
71
		$service = isset( $this->services[$abstract] )
72
			? $this->services[$abstract]
73
			: $this->addNamespace( $abstract );
74
75
		if( is_callable( $service )) {
76
			return call_user_func_array( $service, [$this] );
77
		}
78
		if( is_object( $service )) {
79
			return $service;
80
		}
81
82
		return $this->resolve( $service );
83
	}
84
85
	/**
86
	 * Register a shared binding in the container.
87
	 *
88
	 * @param string               $abstract
89
	 * @param \Closure|string|null $concrete
90
	 *
91
	 * @return void
92
	 */
93
	public function singleton( $abstract, $concrete )
94
	{
95
		$this->bind( $abstract, $this->make( $concrete ));
96
	}
97
98
	/**
99
	 * Dynamically access container bucket items.
100
	 *
101
	 * @param string $item
102
	 *
103
	 * @return mixed
104
	 */
105
	public function __get( $item )
106
	{
107
		return isset( $this->bucket[$item] )
108
			? $this->bucket[$item]
109
			: null;
110
	}
111
112
	/**
113
	 * Dynamically set container bucket items.
114
	 *
115
	 * @param string $item
116
	 * @param mixed  $value
117
	 *
118
	 * @return void
119
	 */
120
	public function __set( $item, $value )
121
	{
122
		$this->bucket[$item] = $value;
123
	}
124
125
   /**
126
	 * Register a Provider.
127
	 *
128
	 * @return void
129
	 */
130
	public function register( $provider )
131
	{
132
		$provider->register( $this );
133
	}
134
135
	/**
136
	 * Prefix the current namespace to the abstract if absent
137
	 *
138
	 * @param string $abstract
139
	 *
140
	 * @return string
141
	 */
142
	protected function addNamespace( $abstract )
143
	{
144
		if( strpos( $abstract, __NAMESPACE__ ) === false && !class_exists( $abstract )) {
145
			$abstract = __NAMESPACE__ . "\\$abstract";
146
		}
147
148
		return $abstract;
149
	}
150
151
    /**
152
     * @param \ReflectionParameter $parameter
153
     * @return null|\ReflectionClass|\ReflectionNamedType|\ReflectionType
154
     */
155
    protected function getClass($parameter)
156
    {
157
        if (version_compare(phpversion(), '8', '<')) {
158
            return $parameter->getClass(); // @compat PHP < 8
159
        }
160
        return $parameter->getType();
161
    }
162
163
	/**
164
	 * Throw an exception that the concrete is not instantiable.
165
	 *
166
	 * @param string $concrete
167
	 *
168
	 * @return void
169
	 * @throws Exception
170
	 */
171
	protected function notInstantiable( $concrete )
172
	{
173
		$message = "Target [$concrete] is not instantiable.";
174
175
		throw new Exception( $message );
176
	}
177
178
	/**
179
	 * Resolve a class based dependency from the container.
180
	 *
181
	 * @param mixed $concrete
182
	 *
183
	 * @return mixed
184
	 * @throws Exception
185
	 */
186
	protected function resolve( $concrete )
187
	{
188
		if( $concrete instanceof Closure ) {
189
			return $concrete( $this );
190
		}
191
192
		$reflector = new ReflectionClass( $concrete );
193
194
		if( !$reflector->isInstantiable() ) {
195
			return $this->notInstantiable( $concrete );
196
		}
197
198
		if( is_null(( $constructor = $reflector->getConstructor() ))) {
199
			return new $concrete;
200
		}
201
202
		return $reflector->newInstanceArgs(
203
			$this->resolveDependencies( $constructor->getParameters() )
204
		);
205
	}
206
207
	/**
208
	 * Resolve a class based dependency from the container.
209
	 *
210
	 * @return mixed
211
	 * @throws Exception
212
	 */
213
	protected function resolveClass( ReflectionParameter $parameter )
214
	{
215
		try {
216
			return $this->make( $this->getClass($parameter)->getName() );
217
		}
218
		catch( Exception $e ) {
219
			if( $parameter->isOptional() ) {
220
				return $parameter->getDefaultValue();
221
			}
222
			throw $e;
223
		}
224
	}
225
226
	/**
227
	 * Resolve all of the dependencies from the ReflectionParameters.
228
	 *
229
	 * @return array
230
	 */
231
	protected function resolveDependencies( array $dependencies )
232
	{
233
		$results = [];
234
235
		foreach( $dependencies as $dependency ) {
236
			// If the class is null, the dependency is a string or some other primitive type
237
			$results[] = !is_null( $class = $this->getClass($dependency) )
238
				? $this->resolveClass( $dependency )
239
				: null;
240
		}
241
242
		return $results;
243
	}
244
}
245