CallbackContainerBuilder   B
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 278
c 0
b 0
f 0
wmc 53
lcom 1
cbo 9
ccs 113
cts 113
cp 1
rs 7.4757

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A registerCallbackContainer() 0 3 1
A registerFromFile() 0 8 2
A registerCallback() 0 8 2
A registerObject() 0 15 3
A registerExpectedReturnType() 0 8 3
B registerAlias() 0 16 6
A isRegistered() 0 8 3
A create() 0 8 3
A singleton() 0 8 3
A deregister() 0 11 3
A register() 0 12 4
A addRecursiveMarkerFor() 0 16 4
B getReturnValueFromCallbackHandlerFor() 0 24 6
C getReturnValueFromSingletonFor() 0 27 8

How to fix   Complexity   

Complex Class

Complex classes like CallbackContainerBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CallbackContainerBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Onoi\CallbackContainer;
4
5
use Closure;
6
use Onoi\CallbackContainer\Exception\ServiceTypeMismatchException;
7
use Onoi\CallbackContainer\Exception\ServiceCircularReferenceException;
8
use Onoi\CallbackContainer\Exception\InvalidParameterTypeException;
9
use Onoi\CallbackContainer\Exception\FileNotFoundException;
10
use Onoi\CallbackContainer\Exception\ServiceNotFoundException;
11
use Onoi\CallbackContainer\Exception\ServiceAliasCrossAssignmentException;
12
use Onoi\CallbackContainer\Exception\ServiceAliasAssignmentException;
13
use Onoi\CallbackContainer\Exception\ServiceAliasMismatchException;
14
15
/**
16
 * @license GNU GPL v2+
17
 * @since 2.0
18
 *
19
 * @author mwjames
20
 */
21
class CallbackContainerBuilder implements ContainerBuilder {
22
23
	/**
24
	 * @var array
25
	 */
26
	protected $registry = array();
27
28
	/**
29
	 * @var array
30
	 */
31
	protected $singletons = array();
32
33
	/**
34
	 * @var array
35
	 */
36
	protected $expectedReturnTypeByHandler = array();
37
38
	/**
39
	 * @var array
40
	 */
41
	protected $aliases = array();
42
43
	/**
44
	 * @var array
45
	 */
46
	protected $recursiveMarker = array();
47
48
	/**
49
	 * @since 2.0
50
	 *
51
	 * {@inheritDoc}
52
	 */
53 34
	public function __construct( CallbackContainer $callbackContainer = null ) {
54 34
		if ( $callbackContainer !== null ) {
55 3
			$this->registerCallbackContainer( $callbackContainer );
56 3
		}
57 34
	}
58
59
	/**
60
	 * @since 2.0
61
	 *
62
	 * {@inheritDoc}
63
	 */
64 4
	public function registerCallbackContainer( CallbackContainer $callbackContainer ) {
65 4
		$this->register( $callbackContainer->register( $this ) );
66 4
	}
67
68
	/**
69
	 * @since 2.0
70
	 *
71
	 * {@inheritDoc}
72
	 */
73 5
	public function registerFromFile( $file ) {
74
75 5
		if ( !is_readable( ( $file = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $file ) ) ) ) {
76 1
			throw new FileNotFoundException( "Cannot access or read {$file}" );
77
		}
78
79 4
		$this->register( require $file );
80 4
	}
81
82
	/**
83
	 * @since 2.0
84
	 *
85
	 * {@inheritDoc}
86
	 */
87 18
	public function registerCallback( $serviceName, callable $callback ) {
88
89 18
		if ( !is_string( $serviceName ) ) {
90 1
			throw new InvalidParameterTypeException( "Expected a string" );
91
		}
92
93 17
		$this->registry[$serviceName] = $callback;
94 17
	}
95
96
	/**
97
	 * If you are not running PHPUnit or for that matter any other testing
98
	 * environment then you are not suppose to use this function.
99
	 *
100
	 * @since 2.0
101
	 *
102
	 * {@inheritDoc}
103
	 */
104 5
	public function registerObject( $serviceName, $instance ) {
105
106 5
		if ( !is_string( $serviceName ) ) {
107 1
			throw new InvalidParameterTypeException( "Expected a string" );
108
		}
109
110 4
		if ( isset( $this->aliases[$serviceName] ) ) {
111 1
			throw new ServiceAliasMismatchException( $serviceName );
112
		}
113
114 3
		unset( $this->singletons[$serviceName] );
115
116 3
		$this->registry[$serviceName] = $instance;
117 3
		$this->singletons[$serviceName]['#'] = $instance;
118 3
	}
119
120
	/**
121
	 * @since 2.0
122
	 *
123
	 * {@inheritDoc}
124
	 */
125 15
	public function registerExpectedReturnType( $serviceName, $type ) {
126
127 15
		if ( !is_string( $serviceName ) || !is_string( $type ) ) {
128 1
			throw new InvalidParameterTypeException( "Expected a string" );
129
		}
130
131 14
		$this->expectedReturnTypeByHandler[$serviceName] = $type;
132 14
	}
133
134
	/**
135
	 * @since 2.0
136
	 *
137
	 * {@inheritDoc}
138
	 */
139 6
	public function registerAlias( $serviceName, $alias ) {
140
141 6
		if ( !is_string( $serviceName ) || !is_string( $alias ) ) {
142 1
			throw new InvalidParameterTypeException( "Expected a string" );
143
		}
144
145 5
		if ( isset( $this->registry[$alias] ) ) {
146 1
			throw new ServiceAliasAssignmentException( $alias );
147
		}
148
149 4
		if ( isset( $this->aliases[$alias] ) && $this->aliases[$alias] !== $serviceName ) {
150 1
			throw new ServiceAliasCrossAssignmentException( $serviceName, $alias, $this->aliases[$alias] );
151
		}
152
153 4
		$this->aliases[$alias] = $serviceName;
154 4
	}
155
156
	/**
157
	 * @since 2.0
158
	 *
159
	 * {@inheritDoc}
160
	 */
161 3
	public function isRegistered( $serviceName ) {
162
163 3
		if ( is_string( $serviceName ) && isset( $this->aliases[$serviceName] ) ) {
164 1
			$serviceName = $this->aliases[$serviceName];
165 1
		}
166
167 3
		return isset( $this->registry[$serviceName] );
168
	}
169
170
	/**
171
	 * @since 2.0
172
	 *
173
	 * {@inheritDoc}
174
	 */
175 18
	public function create( $serviceName ) {
176
177 18
		if ( is_string( $serviceName ) && isset( $this->aliases[$serviceName] ) ) {
178 1
			$serviceName = $this->aliases[$serviceName];
179 1
		}
180
181 18
		return $this->getReturnValueFromCallbackHandlerFor( $serviceName, func_get_args() );
182
	}
183
184
	/**
185
	 * @since 2.0
186
	 *
187
	 * {@inheritDoc}
188
	 */
189 11
	public function singleton( $serviceName ) {
190
191 11
		if (  is_string( $serviceName ) && isset( $this->aliases[$serviceName] ) ) {
192 1
			$serviceName = $this->aliases[$serviceName];
193 1
		}
194
195 11
		return $this->getReturnValueFromSingletonFor( $serviceName, func_get_args() );
196
	}
197
198
	/**
199
	 * @since 2.0
200
	 *
201
	 * @param string $serviceName
202
	 */
203 1
	public function deregister( $serviceName ) {
204 1
		unset( $this->registry[$serviceName] );
205 1
		unset( $this->singletons[$serviceName] );
206 1
		unset( $this->expectedReturnTypeByHandler[$serviceName] );
207
208 1
		foreach ( $this->aliases as $alias => $service ) {
209 1
			if ( $service === $serviceName ) {
210 1
				unset( $this->aliases[$alias] );
211 1
			}
212 1
		}
213 1
	}
214
215 8
	private function register( $serviceDefinitions ) {
216
217 8
		if ( !is_array( $serviceDefinitions ) ) {
218 1
			return;
219
		}
220
221 7
		foreach ( $serviceDefinitions as $serviceName => $callback ) {
222 7
			if ( is_callable( $callback ) ) {
223 7
				$this->registry[$serviceName] = $callback;
224 7
			}
225 7
		}
226 7
	}
227
228 23
	private function addRecursiveMarkerFor( $serviceName ) {
229
230 23
		if ( !is_string( $serviceName ) ) {
231 2
			throw new InvalidParameterTypeException( "Expected a string" );
232
		}
233
234 21
		if ( !isset( $this->recursiveMarker[$serviceName] ) ) {
235 21
			$this->recursiveMarker[$serviceName] = 0;
236 21
		}
237
238 21
		$this->recursiveMarker[$serviceName]++;
239
240 21
		if ( $this->recursiveMarker[$serviceName] > 1 ) {
241 3
			throw new ServiceCircularReferenceException( $serviceName );
242
		}
243 21
	}
244
245 22
	private function getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ) {
246
247 22
		$this->addRecursiveMarkerFor( $serviceName );
248
249 21
		if ( !isset( $this->registry[$serviceName] ) ) {
250 2
			throw new ServiceNotFoundException( "$serviceName is an unknown service." );
251
		}
252
253
		// Remove the ServiceName
254 19
		array_shift( $parameters );
255
256
		// Shift the ContainerBuilder to the first position in the parameter list
257 19
		array_unshift( $parameters, $this );
258 19
		$service = $this->registry[$serviceName];
259
260 19
		$instance = is_callable( $service ) ? call_user_func_array( $service, $parameters ) : $service;
261 16
		$this->recursiveMarker[$serviceName]--;
262
263 16
		if ( !isset( $this->expectedReturnTypeByHandler[$serviceName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$serviceName] ) ) {
264 15
			return $instance;
265
		}
266
267 1
		throw new ServiceTypeMismatchException( $serviceName, $this->expectedReturnTypeByHandler[$serviceName], ( is_object( $instance ) ? get_class( $instance ) : $instance ) );
268
	}
269
270 11
	private function getReturnValueFromSingletonFor( $serviceName, $parameters ) {
271
272 11
		$instance = null;
273 11
		$fingerprint = $parameters !== array() ? md5( json_encode( $parameters ) ) : '#';
274
275 11
		$this->addRecursiveMarkerFor( $serviceName );
276
277 10
		if ( isset( $this->singletons[$serviceName][$fingerprint] ) ) {
278 3
			$service = $this->singletons[$serviceName][$fingerprint];
279 3
			$instance = is_callable( $service ) ? call_user_func( $service ) : $service;
280 3
		}
281
282 10
		$this->recursiveMarker[$serviceName]--;
283
284 10
		if ( $instance !== null && ( !isset( $this->expectedReturnTypeByHandler[$serviceName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$serviceName] ) ) ) {
285 3
			return $instance;
286
		}
287
288 10
		$instance = $this->getReturnValueFromCallbackHandlerFor( $serviceName, $parameters );
289
290 3
		$this->singletons[$serviceName][$fingerprint] = function() use ( $instance ) {
291 3
			static $singleton;
292 3
			return $singleton = $singleton === null ? $instance : $singleton;
293
		};
294
295 8
		return $instance;
296
	}
297
298
}
299