Completed
Pull Request — master (#11)
by James
10:00 queued 07:54
created

Container   A

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  
B __construct() 0 14 5
B define() 0 25 5
A share() 0 11 2
B 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 45
	public function __construct( array $providers = array() ) {
75
		// array_unique ensures we only register each provider once.
76 45
		$providers = array_unique( array_merge( $this->providers, $providers ) );
77
78 45
		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 45
		}
87 45
	}
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 39
	public function define( $alias, $definition ) {
101 39
		if ( is_array( $alias ) ) {
102 9
			$class = current( $alias );
103 9
			$alias = key( $alias );
104 9
		}
105
106 39
		if ( isset( $this->aliases[ $alias ] ) ) {
107 3
			throw new DefinedAliasException( $alias );
108
		}
109
110 39
		$this->aliases[ $alias ]     = true;
111 39
		$this->definitions[ $alias ] = $definition;
112
113
		// Closures are treated as factories unless
114
		// defined via Container::share.
115 39
		if ( ! $definition instanceof \Closure ) {
116 12
			$this->shared[ $alias ] = true;
117 12
		}
118
119 39
		if ( isset( $class ) ) {
120 9
			$this->classes[ $class ] = $alias;
121 9
		}
122
123 39
		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 9
	public function share( $alias, $definition ) {
137 9
		$this->define( $alias, $definition );
138
139 9
		if ( is_array( $alias ) ) {
140 6
			$alias = key( $alias );
141 6
		}
142
143 9
		$this->shared[ $alias ] = true;
144
145 9
		return $this;
146
	}
147
148
	/**
149
	 * {@inheritdoc}
150
	 *
151
	 * @param string $alias
152
	 *
153
	 * @throws UndefinedAliasException
154
	 *
155
	 * @return mixed
156
	 */
157 24
	public function fetch( $alias ) {
158 24
		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 24
		if ( ! isset( $this->aliases[ $alias ] ) ) {
167 3
			throw new UndefinedAliasException( $alias );
168
		}
169
170 21
		$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 21
		if ( $value instanceof \Closure ) {
176 18
			$factory = $value;
177 18
			$value   = $factory( $this );
178 18
		}
179
180
		// If the value is shared, save the shared value.
181 21
		if ( isset( $this->shared[ $alias ] ) ) {
182 12
			$this->definitions[ $alias ] = $value;
183 12
		}
184
185
		// Return the fetched value.
186 21
		return $value;
187
	}
188
189
	/**
190
	 * {@inheritdoc}
191
	 *
192
	 * @param  string $alias
193
	 *
194
	 * @return bool
195
	 */
196 15
	public function has( $alias ) {
197 15
		return isset( $this->aliases[ $alias ] );
198
	}
199
200
	/**
201
	 * {@inheritDoc}
202
	 *
203
	 * @param string $alias
204
	 *
205
	 * @return $this
206
	 */
207 9
	public function remove( $alias ) {
208 9
		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 9
			unset( $this->aliases[ $alias ] );
224 9
		}
225
226 9
		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 ) {
0 ignored issues
show
Coding Style introduced by
The function name offsetSet is in camel caps, but expected offset_set instead as per the coding standard.
Loading history...
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 3
	public function offsetGet( $id ) {
0 ignored issues
show
Coding Style introduced by
The function name offsetGet is in camel caps, but expected offset_get instead as per the coding standard.
Loading history...
266 3
		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 3
	public function offsetExists( $id ) {
0 ignored issues
show
Coding Style introduced by
The function name offsetExists is in camel caps, but expected offset_exists instead as per the coding standard.
Loading history...
279 3
		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 ) {
0 ignored issues
show
Coding Style introduced by
The function name offsetUnset is in camel caps, but expected offset_unset instead as per the coding standard.
Loading history...
290 3
		$this->remove( $id );
291 3
	}
292
293
	/**
294
	 * Sets the object properties to prepare for the loop.
295
	 */
296 3
	public function rewind() {
297 3
		$this->position = 0;
298 3
		$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 3
	}
300
301
	/**
302
	 * Retrieves the service object for the current step in the loop.
303
	 *
304
	 * @return object
305
	 */
306 3
	public function current() {
307 3
		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 3
	public function key() {
316 3
		return $this->keys[ $this->position ];
317
	}
318
319
	/**
320
	 * Increments to the next step in the loop.
321
	 */
322 3
	public function next() {
323 3
		$this->position ++;
324 3
	}
325
326
	/**
327
	 * Checks if the next step in the loop in valid.
328
	 *
329
	 * @return bool
330
	 */
331 3
	public function valid() {
332 3
		return isset( $this->keys[ $this->position ] );
333
	}
334
}
335