Passed
Push — master ( 40bd27...234d7e )
by Georgi
03:20
created

ModuleManager   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 287
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 100
dl 0
loc 287
rs 9.1199
c 3
b 0
f 0
wmc 41

14 Methods

Rating   Name   Duplication   Size   Complexity  
A isInstalled() 0 3 2
A getClass() 0 6 3
A getInstalled() 0 10 2
A isAvailable() 0 3 1
A collect() 0 21 6
A uninstall() 0 37 4
A satisfyDependencies() 0 23 4
B install() 0 50 6
A getCached() 0 7 2
A unsatisfiedDependencies() 0 2 1
A clearCache() 0 5 1
A discoverModuleClasses() 0 16 4
A getAll() 0 11 4
A call() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ModuleManager 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.

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 ModuleManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Epesi\Core\System\Integration\Modules;
4
5
use Epesi\Core\System\Database\Models\Module;
6
use Illuminate\Support\Facades\Cache;
7
use Illuminate\Database\QueryException;
8
use atk4\core\Exception;
9
10
class ModuleManager
11
{
12
	use Concerns\HasPackageManifest;
13
	
14
	/**
15
	 * @var \Illuminate\Support\Collection
16
	 */
17
	private static $installed;
18
	private static $processing;
19
20
	public static function isInstalled($classOrAlias)
21
	{
22
		return self::getClass($classOrAlias, true)? 1: 0;
23
	}
24
	
25
	public static function isAvailable($classOrAlias)
26
	{
27
		return class_exists(self::getClass($classOrAlias));
28
	}
29
30
	/**
31
	 * Get the module core class from class or alias
32
	 * 
33
	 * @param string $classOrAlias
34
	 * @return string;
35
	 */
36
	public static function getClass($classOrAlias, $installedOnly = false) {
37
		$modules = $installedOnly? self::getInstalled(): self::getAll();
38
		
39
		if (collect($modules)->contains($classOrAlias)) return $classOrAlias;
40
		
41
		return $modules[$classOrAlias]?? null;
42
	}
43
	
44
	/**
45
	 * Get a collection of installed modules in alias -> class pairs
46
	 * 
47
	 * @return \Illuminate\Support\Collection;
48
	 */
49
	public static function getInstalled()
50
	{
51
		return self::$installed = self::$installed?? self::getCached('epesi-modules-installed', function() {
52
			try {
53
				$installedModules = Module::pluck('class', 'alias');
54
			} catch (QueryException $e) {
55
				$installedModules = collect();
56
			}
57
			
58
			return $installedModules;
59
		});
60
	}
61
	
62
	/**
63
	 * Get a collection of all manifested modules in alias -> class pairs
64
	 * 
65
	 * @return \Illuminate\Support\Collection;
66
	 */
67
	
68
	public static function getAll()
69
	{
70
		return self::getCached('epesi-modules-available', function () {
71
			$modules = collect();
72
			foreach (array_merge(config('epesi.modules', []), self::packageManifest()->modules()?: []) as $namespace => $path) {
73
				foreach (self::discoverModuleClasses($namespace, $path) as $moduleClass) {
74
					$modules->add(['alias' => $moduleClass::alias(), 'class' => $moduleClass]);
75
				}
76
			}
77
			
78
			return $modules->pluck('class', 'alias');
79
		});
80
	}
81
	
82
	protected static function discoverModuleClasses($namespace, $basePath)
83
	{
84
		$ret = collect();
85
		foreach (glob($basePath . '/*', GLOB_ONLYDIR|GLOB_NOSORT) as $path) {
86
			$moduleNamespace = '\\' . trim($namespace, '\\') . '\\' . basename($path);
87
			
88
			$moduleClass = $moduleNamespace . '\\' . basename($path) . 'Core';
89
90
			$ret = $ret->merge(self::discoverModuleClasses($moduleNamespace, $path));
91
			
92
			if (! class_exists($moduleClass) || !is_a($moduleClass, ModuleCore::class, true)) continue;
93
			
94
			$ret->add($moduleClass);
95
		}
96
		
97
		return $ret;
98
	}
99
	
100
	/**
101
	 * Common method to use for caching of data within module manager
102
	 * 
103
	 * @param string $key
104
	 * @param \Closure $default
105
	 * @return mixed
106
	 */
107
	protected static function getCached($key, \Closure $default)
108
	{
109
		if (! Cache::has($key)) {
110
			Cache::forever($key, $default());
111
		}
112
113
		return Cache::get($key);
114
	}
115
	
116
	/**
117
	 * Clear module manager cache
118
	 */
119
	public static function clearCache()
120
	{
121
		self::$installed = null;
122
		Cache::forget('epesi-modules-installed');
123
		Cache::forget('epesi-modules-available');
124
	}
125
	
126
	/**
127
	 * Alias for collect when no return values expected
128
	 *
129
	 * @param string $method
130
	 * @return array
131
	 */
132
	public static function call($method, $args = [])
133
	{
134
		return self::collect($method, $args);
135
	}
136
	
137
	/**
138
	 * Collect array of results from $method in all installed module core classes
139
	 *
140
	 * @param string $method
141
	 * @return array
142
	 */
143
	public static function collect($method, $args = [])
144
	{
145
		$args = is_array($args)? $args: [$args];
146
		
147
		$installedModules = self::getInstalled();
148
		
149
		// if epesi is not installed fake having the system module to enable its functionality
150
		if ($installedModules->isEmpty()) {
151
			$installedModules = collect([
152
				'system' => \Epesi\Core\System\SystemCore::class
153
			]);
154
		}
155
		
156
		$ret = [];
157
		foreach ($installedModules as $module) {
158
			if (! $list = $module::$method(...$args)) continue;
159
			
160
			$ret = array_merge($ret, is_array($list)? $list: [$list]);
161
		}
162
		
163
		return $ret;
164
	}
165
166
	/**
167
	 * Install the module class provided as argument
168
	 * 
169
	 * @param string $classOrAlias
170
	 */
171
	public static function install($classOrAlias)
172
	{
173
		if (self::isInstalled($classOrAlias)) {
174
			print ('Module "' . $classOrAlias . '" already installed!');
175
			
176
			return true;
177
		}
178
		
179
		if (! $moduleClass = self::getClass($classOrAlias)) {			
180
			throw new \Exception('Module "' . $classOrAlias . '" could not be identified');
181
		}
182
		
183
		/**
184
		 * @var ModuleCore $module
185
		 */
186
		$module = new $moduleClass();
187
		
188
		$module->migrate();
189
		
190
		self::satisfyDependencies($moduleClass);
191
		
192
		try {
193
			$module->install();
194
		} catch (\Exception $exception) {
195
			$module->rollback();
196
			
197
			throw $exception;
198
		}
199
		
200
		$module->publishAssets();
201
		
202
		// update database
203
		Module::create([
204
				'class' => $moduleClass,
205
				'alias' => $module->alias()
206
		]);
207
		
208
		foreach ($module->recommended() as $recommendedModule) {
209
			try {
210
				self::install($recommendedModule);
211
			} catch (Exception $e) {
212
				// just continue, nothing to do if module cannot be installed
213
			}			
214
		}
215
		
216
		self::clearCache();
217
		
218
		print ('Module ' . $classOrAlias . ' successfully installed!');
219
		
220
		return true;
221
	}
222
	
223
	/**
224
	 * Install modules that $moduleClass requires
225
	 * Performs operation recursively for all required modules
226
	 * 
227
	 * @param string $moduleClass
228
	 * @throws \Exception
229
	 * @return boolean
230
	 */
231
	protected static function satisfyDependencies($moduleClass) {
232
		self::$processing[$moduleClass] = true;
233
		
234
		while ($unsatisfiedDependencies = self::unsatisfiedDependencies($moduleClass)) {
235
			$parentModule = array_shift($unsatisfiedDependencies);
236
				
237
			if (self::$processing[$parentModule]?? false) {
238
				throw new Exception('Cross dependency: '. $parentModule);
239
			}
240
				
241
			if (! self::isAvailable($parentModule)) {
242
				throw new Exception('Module not found: "' . $parentModule . '"');
243
			}
244
	
245
			print("\n\r");
246
			print('Installing required module: "' . $parentModule . '" by "' . $moduleClass . '"');
247
248
			self::install($parentModule);
249
		}
250
251
		unset(self::$processing[$moduleClass]);
252
		
253
		return true;
254
	}
255
	
256
	protected static function unsatisfiedDependencies($moduleClass) {
257
		return collect($moduleClass::requires())->diff(self::getInstalled())->filter()->all();
258
	}	
259
	
260
	public static function uninstall($classOrAlias)
261
	{
262
		if (! self::isInstalled($classOrAlias)) {
263
			print ('Module "' . $classOrAlias . '" is not installed!');
264
			
265
			return true;
266
		}
267
		
268
		if (! $moduleClass = self::getClass($classOrAlias)) {
269
			throw new \Exception('Module "' . $classOrAlias . '" could not be identified');
270
		}
271
		
272
		/**
273
		 * @var ModuleCore $module
274
		 */
275
		$module = new $moduleClass();
276
		
277
		$module->rollback();
278
		
279
		try {
280
			$module->uninstall();
281
		} catch (\Exception $exception) {
282
			$module->migrate();
283
			
284
			throw $exception;
285
		}
286
		
287
		$module->unpublishAssets();
288
		
289
		// update database
290
		Module::where('class', $moduleClass)->delete();
291
		
292
		self::clearCache();
293
		
294
		print ('Module ' . $classOrAlias . ' successfully uninstalled!');
295
		
296
		return true;
297
	}
298
}
299