Completed
Pull Request — master (#4)
by mw
10:59
created

CallbackContainerBuilder   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 225
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 6
dl 0
loc 225
rs 8.8
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A registerCallbackContainer() 0 3 1
A registerFromFile() 0 17 4
A registerCallback() 0 8 2
A registerObject() 0 11 2
A registerExpectedReturnType() 0 8 3
A isRegistered() 0 3 1
A create() 0 3 1
A singleton() 0 3 1
A deregister() 0 5 1
A addRecursiveMarkerFor() 0 16 4
B getReturnValueFromCallbackHandlerFor() 0 25 6
C getReturnValueFromSingletonFor() 0 27 8
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
12
/**
13
 * @license GNU GPL v2+
14
 * @since 2.0
15
 *
16
 * @author mwjames
17
 */
18
class CallbackContainerBuilder implements ContainerBuilder {
19
20
	/**
21
	 * @var array
22
	 */
23
	protected $registry = array();
24
25
	/**
26
	 * @var array
27
	 */
28
	protected $singletons = array();
29
30
	/**
31
	 * @var array
32
	 */
33
	protected $expectedReturnTypeByHandler = array();
34
35
	/**
36
	 * @var array
37
	 */
38
	protected $recursiveMarker = array();
39
40
	/**
41
	 * @since 2.0
42
	 *
43
	 * @param CallbackContainer|null $callbackContainer
44
	 */
45
	public function __construct( CallbackContainer $callbackContainer = null ) {
46
		if ( $callbackContainer !== null ) {
47
			$this->registerCallbackContainer( $callbackContainer );
48
		}
49
	}
50
51
	/**
52
	 * @since 2.0
53
	 *
54
	 * @param CallbackContainer $callbackContainer
55
	 */
56
	public function registerCallbackContainer( CallbackContainer $callbackContainer ) {
57
		$callbackContainer->register( $this );
58
	}
59
60
	/**
61
	 * @since 2.0
62
	 *
63
	 * @param string $file
64
	 * @throws FileNotFoundException
65
	 */
66
	public function registerFromFile( $file ) {
67
68
		if ( !is_readable( ( $file = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $file ) ) ) ) {
69
			throw new FileNotFoundException( "Cannot access or read {$file}" );
70
		}
71
72
		$defintions = require $file;
73
74
		foreach ( $defintions as $serviceName => $callback ) {
75
76
			if ( !is_callable( $callback ) ) {
77
				continue;
78
			}
79
80
			$this->registerCallback( $serviceName, $callback );
0 ignored issues
show
Documentation introduced by
$callback is of type callable, but the function expects a object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
81
		}
82
	}
83
84
	/**
85
	 * @since 2.0
86
	 *
87
	 * {@inheritDoc}
88
	 */
89
	public function registerCallback( $serviceName, Closure $callback ) {
90
91
		if ( !is_string( $serviceName ) ) {
92
			throw new InvalidParameterTypeException( "Expected a string" );
93
		}
94
95
		$this->registry[$serviceName] = $callback;
96
	}
97
98
	/**
99
	 * If you are not running PHPUnit or for that matter any other testing
100
	 * environment then you are not suppose to use this function.
101
	 *
102
	 * @since 2.0
103
	 *
104
	 * @param string $serviceName
105
	 * @param mixed $instance
106
	 */
107
	public function registerObject( $serviceName, $instance ) {
108
109
		if ( !is_string( $serviceName ) ) {
110
			throw new InvalidParameterTypeException( "Expected a string" );
111
		}
112
113
		unset( $this->singletons[$serviceName] );
114
115
		$this->registry[$serviceName] = $instance;
116
		$this->singletons[$serviceName]['#'] = $instance;
117
	}
118
119
	/**
120
	 * @since 2.0
121
	 *
122
	 * {@inheritDoc}
123
	 */
124
	public function registerExpectedReturnType( $serviceName, $type ) {
125
126
		if ( !is_string( $serviceName ) || !is_string( $type ) ) {
127
			throw new InvalidParameterTypeException( "Expected a string" );
128
		}
129
130
		$this->expectedReturnTypeByHandler[$serviceName] = $type;
131
	}
132
133
	/**
134
	 * @since 2.0
135
	 *
136
	 * {@inheritDoc}
137
	 */
138
	public function isRegistered( $serviceName ) {
139
		return isset( $this->registry[$serviceName] );
140
	}
141
142
	/**
143
	 * @since 2.0
144
	 *
145
	 * {@inheritDoc}
146
	 */
147
	public function create( $serviceName ) {
148
		return $this->getReturnValueFromCallbackHandlerFor( $serviceName, func_get_args() );
149
	}
150
151
	/**
152
	 * @since 2.0
153
	 *
154
	 * {@inheritDoc}
155
	 */
156
	public function singleton( $serviceName ) {
157
		return $this->getReturnValueFromSingletonFor( $serviceName, func_get_args() );
158
	}
159
160
	/**
161
	 * @since 2.0
162
	 *
163
	 * @param string $serviceName
164
	 */
165
	public function deregister( $serviceName ) {
166
		unset( $this->registry[$serviceName] );
167
		unset( $this->singletons[$serviceName] );
168
		unset( $this->expectedReturnTypeByHandler[$serviceName] );
169
	}
170
171
	private function addRecursiveMarkerFor( $serviceName ) {
172
173
		if ( !is_string( $serviceName ) ) {
174
			throw new InvalidParameterTypeException( "Expected a string" );
175
		}
176
177
		if ( !isset( $this->recursiveMarker[$serviceName] ) ) {
178
			$this->recursiveMarker[$serviceName] = 0;
179
		}
180
181
		$this->recursiveMarker[$serviceName]++;
182
183
		if ( $this->recursiveMarker[$serviceName] > 1 ) {
184
			throw new ServiceCircularReferenceException( $serviceName );
185
		}
186
	}
187
188
	private function getReturnValueFromCallbackHandlerFor( $serviceName, $parameters ) {
189
190
		$instance = null;
0 ignored issues
show
Unused Code introduced by
$instance is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
191
		$this->addRecursiveMarkerFor( $serviceName );
192
193
		if ( !isset( $this->registry[$serviceName] ) ) {
194
			throw new ServiceNotFoundException( "$serviceName is an unknown service." );
195
		}
196
197
		// Remove the ServiceName
198
		array_shift( $parameters );
199
200
		// Shift the ContainerBuilder to the first position in the parameter list
201
		array_unshift( $parameters, $this );
202
		$service = $this->registry[$serviceName];
203
204
		$instance = is_callable( $service ) ? call_user_func_array( $service, $parameters ) : $service;
205
		$this->recursiveMarker[$serviceName]--;
206
207
		if ( !isset( $this->expectedReturnTypeByHandler[$serviceName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$serviceName] ) ) {
208
			return $instance;
209
		}
210
211
		throw new ServiceTypeMismatchException( $serviceName, $this->expectedReturnTypeByHandler[$serviceName], ( is_object( $instance ) ? get_class( $instance ) : $instance ) );
212
	}
213
214
	private function getReturnValueFromSingletonFor( $serviceName, $parameters ) {
215
216
		$instance = null;
217
		$fingerprint = $parameters !== array() ? md5( json_encode( $parameters ) ) : '#';
218
219
		$this->addRecursiveMarkerFor( $serviceName );
220
221
		if ( isset( $this->singletons[$serviceName][$fingerprint] ) ) {
222
			$service = $this->singletons[$serviceName][$fingerprint];
223
			$instance = is_callable( $service ) ? call_user_func( $service ) : $service;
224
		}
225
226
		$this->recursiveMarker[$serviceName]--;
227
228
		if ( $instance !== null && ( !isset( $this->expectedReturnTypeByHandler[$serviceName] ) || is_a( $instance, $this->expectedReturnTypeByHandler[$serviceName] ) ) ) {
229
			return $instance;
230
		}
231
232
		$instance = $this->getReturnValueFromCallbackHandlerFor( $serviceName, $parameters );
233
234
		$this->singletons[$serviceName][$fingerprint] = function() use ( $instance ) {
235
			static $singleton;
236
			return $singleton = $singleton === null ? $instance : $singleton;
237
		};
238
239
		return $instance;
240
	}
241
242
}
243