Passed
Push — develop ( 7b2b20...7eda82 )
by Paul
04:02
created

Container   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 0

Test Coverage

Coverage 75%

Importance

Changes 0
Metric Value
dl 0
loc 223
ccs 3
cts 4
cp 0.75
rs 10
c 0
b 0
f 0
wmc 26
lcom 2
cbo 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getInstance() 0 8 2
A bind() 0 4 1
A make() 0 15 4
A singleton() 0 4 1
A __get() 0 6 2
A __set() 0 4 1
A register() 0 4 1
A addNamespace() 0 8 3
A notInstantiable() 0 6 1
A resolve() 0 20 4
A resolveClass() 0 12 3
A resolveDependencies() 0 13 3
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
	 * Throw an exception that the concrete is not instantiable.
153
	 *
154
	 * @param string $concrete
155
	 *
156
	 * @return void
157
	 * @throws Exception
158
	 */
159
	protected function notInstantiable( $concrete )
160
	{
161
		$message = "Target [$concrete] is not instantiable.";
162
163
		throw new Exception( $message );
164
	}
165
166
	/**
167
	 * Resolve a class based dependency from the container.
168
	 *
169
	 * @param mixed $concrete
170
	 *
171
	 * @return mixed
172
	 * @throws Exception
173
	 */
174
	protected function resolve( $concrete )
175
	{
176
		if( $concrete instanceof Closure ) {
177
			return $concrete( $this );
178
		}
179
180
		$reflector = new ReflectionClass( $concrete );
181
182
		if( !$reflector->isInstantiable() ) {
183
			return $this->notInstantiable( $concrete );
184
		}
185
186
		if( is_null(( $constructor = $reflector->getConstructor() ))) {
187
			return new $concrete;
188
		}
189
190
		return $reflector->newInstanceArgs(
191
			$this->resolveDependencies( $constructor->getParameters() )
192
		);
193
	}
194
195
	/**
196
	 * Resolve a class based dependency from the container.
197
	 *
198
	 * @return mixed
199
	 * @throws Exception
200
	 */
201
	protected function resolveClass( ReflectionParameter $parameter )
202
	{
203
		try {
204
			return $this->make( $parameter->getClass()->name );
205
		}
206
		catch( Exception $e ) {
207
			if( $parameter->isOptional() ) {
208
				return $parameter->getDefaultValue();
209
			}
210
			throw $e;
211
		}
212
	}
213
214
	/**
215
	 * Resolve all of the dependencies from the ReflectionParameters.
216
	 *
217
	 * @return array
218
	 */
219
	protected function resolveDependencies( array $dependencies )
220
	{
221
		$results = [];
222
223
		foreach( $dependencies as $dependency ) {
224
			// If the class is null, the dependency is a string or some other primitive type
225
			$results[] = !is_null( $class = $dependency->getClass() )
226
				? $this->resolveClass( $dependency )
227
				: null;
228
		}
229
230
		return $results;
231
	}
232
}
233