Completed
Push — master ( 813212...3951a6 )
by Peter
02:10
created

PluginFactory   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 257
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 11
Bugs 3 Features 0
Metric Value
wmc 36
c 11
b 3
f 0
lcom 2
cbo 4
dl 0
loc 257
rs 8.8

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A fly() 0 8 2
A create() 0 9 2
C _implements() 0 31 8
A _getClassName() 0 8 2
C _getConfigs() 0 27 7
B _instantiate() 0 25 4
B instance() 0 32 6
A getKey() 0 18 4
1
<?php
2
3
/**
4
 * This software package is licensed under `AGPL, Commercial` license[s].
5
 *
6
 * @package maslosoft/gazebo
7
 * @license AGPL, Commercial
8
 *
9
 * @copyright Copyright (c) Peter Maselkowski <[email protected]>
10
 *
11
 */
12
13
namespace Maslosoft\Gazebo;
14
15
use Maslosoft\EmbeDi\EmbeDi;
16
use Maslosoft\Gazebo\Exceptions\ConfigurationException;
17
use Maslosoft\Gazebo\Storage\PluginsStorage;
18
use ReflectionClass;
19
20
/**
21
 * PluginFactory
22
 *
23
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
24
 */
25
class PluginFactory
26
{
27
28
	/**
29
	 * Configured plugins instances
30
	 * @var PluginsStorage
31
	 */
32
	private $instances = null;
33
34
	/**
35
	 * Single plugins instances
36
	 * @var Storage\PluginStorage
37
	 */
38
	private $plugins = null;
39
40
	/**
41
	 * EmbeDi instance
42
	 * @var EmbeDi
43
	 */
44
	private $di = null;
45
46
	/**
47
	 * Static instances of plugin factories
48
	 * @var PluginFactory[]
49
	 */
50
	private static $_pf = [];
51
52
	/**
53
	 * Class constructor with optional instanceid which is passed to EmbeDi
54
	 * @param string $instanceId
55
	 */
56
	public function __construct($instanceId = Gazebo::DefaultInstanceId)
57
	{
58
		$this->instances = new PluginsStorage($this, $instanceId);
59
		$this->plugins = new Storage\PluginStorage($this, $instanceId);
60
		$this->di = new EmbeDi($instanceId);
61
	}
62
63
	/**
64
	 * Flyweight accessor for `PluginFactory` with optional instanceid.
65
	 * This will create only one runtime wide instance of `PluginFactory` for each `$instanceId`.
66
	 * @param string $instanceId
67
	 * @return PluginFactory Flyweight instance of PluginFactory
68
	 */
69
	public static function fly($instanceId = Gazebo::DefaultInstanceId)
70
	{
71
		if (empty(self::$_pf[$instanceId]))
72
		{
73
			self::$_pf[$instanceId] = new static($instanceId);
74
		}
75
		return self::$_pf[$instanceId];
76
	}
77
78
	/**
79
	 * Create plugin set from `$configuration` for `$object`
80
	 * optionally implementing one or more `$interfaces`.
81
	 *
82
	 * @param mixed[][] $configuration Configuration arrays
83
	 * @param string|object $object Object or class name
84
	 * @param null|string|string[] $interfaces Array or string of interface names or class names
85
	 * @return object[] Array of plugin instances
86
	 */
87
	public function create($configuration, $object, $interfaces = null)
88
	{
89
		$plugins = [];
90
		foreach ($this->_getConfigs($configuration, $object, $interfaces) as $config)
91
		{
92
			$plugins[] = $this->_instantiate($config);
93
		}
94
		return $plugins;
95
	}
96
97
	/**
98
	 * Get instance of plugin set from `$config` for `$object`
99
	 * optionally implementing one or more `$interfaces`.
100
	 *
101
	 * This will create instances unique for each object and interfaces set.
102
	 * This will create only **one instance** of each plugin per config.
103
	 *
104
	 * @param mixed[][] $config
105
	 * @param string|object $object
106
	 * @param null|string|string[] $interfaces
107
	 * @return object[] Array of plugin instances
108
	 */
109
	public function instance($config, $object, $interfaces = null)
110
	{
111
		if (is_string($object))
112
		{
113
			$key = $object;
114
		}
115
		else
116
		{
117
			$key = get_class($object);
118
		}
119
		if (null !== $interfaces)
120
		{
121
			if (!is_array($interfaces))
122
			{
123
				$interfaces = [
124
					(string) $interfaces
125
				];
126
			}
127
			$key .= '.' . implode('.', $interfaces);
128
		}
129
		$key .= $this->getKey($config);
130
		if (!isset($this->instances[$key]))
131
		{
132
			$plugins = [];
133
			foreach ($this->_getConfigs($config, $object, $interfaces) as $config)
0 ignored issues
show
Bug introduced by
It seems like $interfaces can also be of type array; however, Maslosoft\Gazebo\PluginFactory::_getConfigs() does only seem to accept null|string|array<integer,string>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
134
			{
135
				$plugins[] = $this->_instantiate($config, true);
136
			}
137
			$this->instances[$key] = $plugins;
138
		}
139
		return $this->instances[$key];
140
	}
141
142
	/**
143
	 * Check if object or class implements one or more interfaces
144
	 * @param object|string $object
145
	 * @param null|string|string[] $interfaces Interfaces to check
146
	 * @return boolean
147
	 */
148
	private function _implements($object, $interfaces = null)
149
	{
150
		if (null === $interfaces)
151
		{
152
			return true;
153
		}
154
		if (!is_array($interfaces))
155
		{
156
			$interfaces = [
157
				(string) $interfaces
158
			];
159
		}
160
		foreach ($interfaces as $interface)
161
		{
162
			$objectInfo = new ReflectionClass($object);
163
			$interfaceInfo = new ReflectionClass($interface);
164
			if ($objectInfo->name === $interfaceInfo->name)
165
			{
166
				return true;
167
			}
168
			if ($objectInfo->isSubclassOf($interface))
169
			{
170
				return true;
171
			}
172
			if ($interfaceInfo->isInterface() && $objectInfo->implementsInterface($interface))
173
			{
174
				return true;
175
			}
176
		}
177
		return false;
178
	}
179
180
	/**
181
	 * Get class name from configuration
182
	 * @param string|mixed[] $config
183
	 * @return string
184
	 */
185
	private function _getClassName($config)
186
	{
187
		if (is_string($config))
188
		{
189
			return $config;
190
		}
191
		return $config[$this->di->classField];
192
	}
193
194
	/**
195
	 * Config generator
196
	 *
197
	 * @param mixed[][] $configuration Configuration arrays
198
	 * @param string|object $object Object or class name
199
	 * @param null|string|string[] $interfaces Array or string of interface names or class names
200
	 * @return mixed[] Array of plugin configs
201
	 * @yield mixed[] Array of plugin configs
202
	 */
203
	private function _getConfigs($configuration, $object, $interfaces = null)
204
	{
205
		foreach ($configuration as $interface => $configs)
206
		{
207
			if (!is_string($interface))
208
			{
209
				throw new ConfigurationException(sprintf('Wrong configuration for key `%s` - key must be class name', $interface));
210
			}
211
			if (!is_array($configs))
212
			{
213
				throw new ConfigurationException(sprintf('Wrong configuration for key `%s`, configuration should be array, `%s` given', $interface, gettype($configs)));
214
			}
215
			if (!$this->_implements($object, $interface))
216
			{
217
				continue;
218
			}
219
			foreach ($configs as $config)
220
			{
221
				$pluginClass = $this->_getClassName($config);
222
				if (!$this->_implements($pluginClass, $interfaces))
223
				{
224
					continue;
225
				}
226
				yield $config;
227
			}
228
		}
229
	}
230
231
	/**
232
	 * Instantiate object based on configuration
233
	 * @param string|mixed[] $config
234
	 * @return object
235
	 */
236
	private function _instantiate($config, $fly = false)
237
	{
238
		$key = $this->getKey($config);
239
		$className = $this->_getClassName($config);
240
		if ($fly)
241
		{
242
			if (isset($this->plugins[$key]))
243
			{
244
				$plugin = $this->plugins[$key];
245
			}
246
			else
247
			{
248
				$plugin = $this->plugins[$key] = new $className;
249
			}
250
		}
251
		else
252
		{
253
			$plugin = new $className;
254
		}
255
		if (is_array($config))
256
		{
257
			$plugin = $this->di->apply($config, $plugin);
258
		}
259
		return $plugin;
260
	}
261
262
	private function getKey($config)
263
	{
264
		if (is_array($config))
265
		{
266
			// Only class field, use class name as a key
267
			if (!empty($config['class']) && count($config) === 1)
268
			{
269
				return $config['class'];
270
			}
271
272
			// Complex config
273
			return md5(json_encode($config));
274
		}
275
		else
276
		{
277
			return $config;
278
		}
279
	}
280
281
}
282