Container   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 92.68%

Importance

Changes 0
Metric Value
wmc 30
lcom 1
cbo 3
dl 0
loc 320
ccs 76
cts 82
cp 0.9268
rs 10
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 5
A define() 0 25 5
A share() 0 11 2
A fetch() 0 31 5
A has() 0 3 1
A remove() 0 21 2
A register() 0 6 1
A offsetSet() 0 3 1
A offsetGet() 0 3 1
A offsetExists() 0 3 1
A offsetUnset() 0 3 1
A rewind() 0 4 1
A current() 0 3 1
A key() 0 3 1
A next() 0 3 1
A valid() 0 3 1
1
<?php
2
namespace Intraxia\Jaxion\Core;
3
4
use Intraxia\Jaxion\Contract\Core\Container as ContainerContract;
5
use Intraxia\Jaxion\Contract\Core\ServiceProvider;
6
7
/**
8
 * Class Container
9
 *
10
 * Contains, manages, and retrieves service objects.
11
 *
12
 * @package Intraxia\Jaxion
13
 * @subpackage Core
14
 */
15
class Container implements ContainerContract {
16
	/**
17
	 * ServiceProvider names to register with the container.
18
	 *
19
	 * Can be overwritten to include predefined providers.
20
	 *
21
	 * @var string[]
22
	 */
23
	protected $providers = array();
24
25
	/**
26
	 * Registered definitions.
27
	 *
28
	 * @var mixed[]
29
	 */
30
	private $definitions = array();
31
32
	/**
33
	 * Aliases to share between fetches.
34
	 *
35
	 * @var <string, true>[]
36
	 */
37
	private $shared = array();
38
39
	/**
40
	 * Aliases of all the registered services.
41
	 *
42
	 * @var <string, true>[]
43
	 */
44
	private $aliases = array();
45
46
	/**
47
	 * Array of classes registered on the container.
48
	 *
49
	 * @var <string, true>[]
50
	 */
51
	private $classes = array();
52
53
	/**
54
	 * Current position in the loop.
55
	 *
56
	 * @var int
57
	 */
58
	private $position;
59
60
	/**
61
	 * 0-indexed array of aliases for looping.
62
	 *
63
	 * @var string[]
64
	 */
65
	private $keys = array();
66
67
	/**
68
	 * Create a new container with the given providers.
69
	 *
70
	 * Providers can be instances or the class of the provider as a string.
71
	 *
72
	 * @param ServiceProvider[]|string[] $providers
73
	 */
74 69
	public function __construct( array $providers = array() ) {
75
		// array_unique ensures we only register each provider once.
76 69
		$providers = array_unique( array_merge( $this->providers, $providers ) );
77
78 69
		foreach ( $providers as $provider ) {
79
			if ( is_string( $provider ) && class_exists( $provider ) ) {
80
				$provider = new $provider;
81
			}
82
83
			if ( $provider instanceof ServiceProvider ) {
84
				$this->register( $provider );
85
			}
86 46
		}
87 69
	}
88
89
90
	/**
91
	 * {@inheritdoc}
92
	 *
93
	 * @param string|array $alias
94
	 * @param mixed        $definition
95
	 *
96
	 * @throws DefinedAliasException
97
	 *
98
	 * @return $this
99
	 */
100 63
	public function define( $alias, $definition ) {
101 63
		if ( is_array( $alias ) ) {
102 33
			$class = current( $alias );
103 33
			$alias = key( $alias );
104 22
		}
105
106 63
		if ( isset( $this->aliases[ $alias ] ) ) {
107 3
			throw new DefinedAliasException( $alias );
108
		}
109
110 63
		$this->aliases[ $alias ]     = true;
111 63
		$this->definitions[ $alias ] = $definition;
112
113
		// Closures are treated as factories unless
114
		// defined via Container::share.
115 63
		if ( ! $definition instanceof \Closure ) {
116 36
			$this->shared[ $alias ] = true;
117 24
		}
118
119 63
		if ( isset( $class ) ) {
120 33
			$this->classes[ $class ] = $alias;
121 22
		}
122
123 63
		return $this;
124
	}
125
126
	/**
127
	 * {@inheritdoc}
128
	 *
129
	 * @param string|array $alias
130
	 * @param mixed        $definition
131
	 *
132
	 * @throws DefinedAliasException
133
	 *
134
	 * @return $this
135
	 */
136 33
	public function share( $alias, $definition ) {
137 33
		$this->define( $alias, $definition );
138
139 33
		if ( is_array( $alias ) ) {
140 30
			$alias = key( $alias );
141 20
		}
142
143 33
		$this->shared[ $alias ] = true;
144
145 33
		return $this;
146
	}
147
148
	/**
149
	 * {@inheritdoc}
150
	 *
151
	 * @param string $alias
152
	 *
153
	 * @throws UndefinedAliasException
154
	 *
155
	 * @return mixed
156
	 */
157 39
	public function fetch( $alias ) {
158 39
		if ( isset( $this->classes[ $alias ] ) ) {
159
			// If the alias is a class name,
160
			// then retrieve its linked alias.
161
			// This is only registered when
162
			// registering using an array.
163 6
			$alias = $this->classes[ $alias ];
164 6
		}
165
166 39
		if ( ! isset( $this->aliases[ $alias ] ) ) {
167 3
			throw new UndefinedAliasException( $alias );
168
		}
169
170 36
		$value = $this->definitions[ $alias ];
171
172
		// If the shared value is a closure,
173
		// execute it and assign the result
174
		// in place of the closure.
175 36
		if ( $value instanceof \Closure ) {
176 33
			$factory = $value;
177 33
			$value   = $factory( $this );
178 22
		}
179
180
		// If the value is shared, save the shared value.
181 36
		if ( isset( $this->shared[ $alias ] ) ) {
182 27
			$this->definitions[ $alias ] = $value;
183 18
		}
184
185
		// Return the fetched value.
186 36
		return $value;
187
	}
188
189
	/**
190
	 * {@inheritdoc}
191
	 *
192
	 * @param  string $alias
193
	 *
194
	 * @return bool
195
	 */
196 18
	public function has( $alias ) {
197 18
		return isset( $this->aliases[ $alias ] );
198
	}
199
200
	/**
201
	 * {@inheritDoc}
202
	 *
203
	 * @param string $alias
204
	 *
205
	 * @return $this
206
	 */
207 21
	public function remove( $alias ) {
208 21
		if ( isset( $this->aliases[ $alias ] ) ) {
209
			/**
210
			 * If there's no reference in the aliases array,
211
			 * the service won't be found on fetching and
212
			 * can be overwritten on setting.
213
			 *
214
			 * Pros: Quick setting/unsetting, faster
215
			 * performance on those operations when doing
216
			 * a lot of these.
217
			 *
218
			 * Cons: Objects and values set in the container
219
			 * can't get garbage collected.
220
			 *
221
			 * If this is a problem, this may need to be revisited.
222
			 */
223 21
			unset( $this->aliases[ $alias ] );
224 14
		}
225
226 21
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Intraxia\Jaxion\Core\Container) is incompatible with the return type declared by the interface Intraxia\Jaxion\Contract\Core\Container::remove of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
227
	}
228
229
	/**
230
	 * {@inheritDoc}
231
	 *
232
	 * @param ServiceProvider $provider
233
	 *
234
	 * @return $this
235
	 */
236 3
	public function register( ServiceProvider $provider ) {
237
		// @todo make sure provider is only registered once
238 3
		$provider->register( $this );
239
240 3
		return $this;
241
	}
242
243
	/**
244
	 * Set a value into the container.
245
	 *
246
	 * @param  string $id
247
	 * @param  mixed  $value
248
	 *
249
	 * @see    define
250
	 */
251 6
	public function offsetSet( $id, $value ) {
252 6
		$this->define( $id, $value );
253 6
	}
254
255
	/**
256
	 * Get an value from the container.
257
	 *
258
	 * @param  string $id
259
	 *
260
	 * @return object
261
	 * @throws UndefinedAliasException
262
	 *
263
	 * @see    fetch
264
	 */
265 6
	public function offsetGet( $id ) {
266 6
		return $this->fetch( $id );
267
	}
268
269
	/**
270
	 * Checks if a key is set on the container.
271
	 *
272
	 * @param  string $id
273
	 *
274
	 * @return bool
275
	 *
276
	 * @see    has
277
	 */
278 6
	public function offsetExists( $id ) {
279 6
		return $this->has( $id );
280
	}
281
282
	/**
283
	 * Remove a key from the container.
284
	 *
285
	 * @param string $id
286
	 *
287
	 * @see   remove
288
	 */
289 3
	public function offsetUnset( $id ) {
290 3
		$this->remove( $id );
291 3
	}
292
293
	/**
294
	 * Sets the object properties to prepare for the loop.
295
	 */
296 12
	public function rewind() {
297 12
		$this->position = 0;
298 12
		$this->keys     = array_keys( $this->aliases );
0 ignored issues
show
Documentation Bug introduced by
It seems like array_keys($this->aliases) of type array<integer,integer|string> is incompatible with the declared type array<integer,string> of property $keys.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
299 12
	}
300
301
	/**
302
	 * Retrieves the service object for the current step in the loop.
303
	 *
304
	 * @return object
305
	 */
306 12
	public function current() {
307 12
		return $this->fetch( $this->keys[ $this->position ] );
308
	}
309
310
	/**
311
	 * Retrieves the key for the current step in the loop.
312
	 *
313
	 * @return string
314
	 */
315 12
	public function key() {
316 12
		return $this->keys[ $this->position ];
317
	}
318
319
	/**
320
	 * Increments to the next step in the loop.
321
	 */
322 12
	public function next() {
323 12
		$this->position ++;
324 12
	}
325
326
	/**
327
	 * Checks if the next step in the loop in valid.
328
	 *
329
	 * @return bool
330
	 */
331 12
	public function valid() {
332 12
		return isset( $this->keys[ $this->position ] );
333
	}
334
}
335