Passed
Push — master ( 836088...195422 )
by Georgi
02:55
created

ModuleManager::uninstall()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 37
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 17
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 37
rs 9.7
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 isAvalable($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
	public static function getAll()
68
	{
69
		return self::getCached('epesi-modules-available', function () {
70
			$modules = collect();
71
			foreach (array_merge(config('epesi.modules', []), self::packageManifest()->modules()?: []) as $moduleClass) {
72
				$modules->add(['alias' => $moduleClass::alias(), 'class' => $moduleClass]);
73
			}
74
75
			return $modules->pluck('class', 'alias');
76
		});
77
	}
78
	
79
	/**
80
	 * Common method to use for caching of data within module manager
81
	 * 
82
	 * @param string $key
83
	 * @param \Closure $default
84
	 * @return mixed
85
	 */
86
	protected static function getCached($key, \Closure $default)
87
	{
88
		if (! Cache::has($key)) {
89
			Cache::forever($key, $default());
90
		}
91
92
		return Cache::get($key);
93
	}
94
	
95
	/**
96
	 * Clear module manager cache
97
	 */
98
	public static function clearCache()
99
	{
100
		self::$installed = null;
101
		Cache::forget('epesi-modules-installed');
102
		Cache::forget('epesi-modules-available');
103
	}
104
	
105
	/**
106
	 * Alias for collect when no return values expected
107
	 *
108
	 * @param string $method
109
	 * @return array
110
	 */
111
	public static function call($method, $args = [])
112
	{
113
		return self::collect($method, $args);
114
	}
115
	
116
	/**
117
	 * Collect array of results from $method in all installed module core classes
118
	 *
119
	 * @param string $method
120
	 * @return array
121
	 */
122
	public static function collect($method, $args = [])
123
	{
124
		$args = is_array($args)? $args: [$args];
125
		
126
		$installedModules = self::getInstalled();
127
		
128
		// if epesi is not installed fake having the system module to enable its functionality
129
		if ($installedModules->isEmpty()) {
130
			$installedModules = collect([
131
				'system' => \Epesi\Core\System\SystemCore::class
132
			]);
133
		}
134
		
135
		$ret = [];
136
		foreach ($installedModules as $module) {
137
			if (! $list = $module::$method(...$args)) continue;
138
			
139
			$ret = array_merge($ret, is_array($list)? $list: [$list]);
140
		}
141
		
142
		return $ret;
143
	}
144
145
	/**
146
	 * Install the module class provided as argument
147
	 * 
148
	 * @param string $classOrAlias
149
	 */
150
	public static function install($classOrAlias)
151
	{
152
		if (self::isInstalled($classOrAlias)) {
153
			print ('Module "' . $classOrAlias . '" already installed!');
154
			
155
			return true;
156
		}
157
		
158
		if (! $moduleClass = self::getClass($classOrAlias)) {			
159
			throw new \Exception('Module "' . $classOrAlias . '" could not be identified');
160
		}
161
		
162
		/**
163
		 * @var ModuleCore $module
164
		 */
165
		$module = new $moduleClass();
166
		
167
		$module->migrate();
168
		
169
		self::satisfyDependencies($moduleClass);
170
		
171
		try {
172
			$module->install();
173
		} catch (\Exception $exception) {
174
			$module->rollback();
175
			
176
			throw $exception;
177
		}
178
		
179
		$module->publishAssets();
180
		
181
		// update database
182
		Module::create([
183
				'class' => $moduleClass,
184
				'alias' => $module->alias()
185
		]);
186
		
187
		self::clearCache();
188
		
189
		print ('Module ' . $classOrAlias . ' successfully installed!');
190
		
191
		return true;
192
	}
193
	
194
	/**
195
	 * Install modules that $moduleClass requires
196
	 * Performs operation recursively for all required modules
197
	 * 
198
	 * @param string $moduleClass
199
	 * @throws \Exception
200
	 * @return boolean
201
	 */
202
	protected static function satisfyDependencies($moduleClass) {
203
		self::$processing[$moduleClass] = true;
204
		
205
		while ($unsatisfiedDependencies = self::unsatisfiedDependencies($moduleClass)) {
206
			$parentModule = array_shift($unsatisfiedDependencies);
207
				
208
			if (self::$processing[$parentModule]?? false) {
209
				throw new Exception('Cross dependency: '. $parentModule);
210
			}
211
				
212
			if (! self::isAvalable($parentModule)) {
213
				throw new Exception('Module not found: "' . $parentModule . '"');
214
			}
215
	
216
			print("\n\r");
217
			print('Installing required module: "' . $parentModule . '" by "' . $moduleClass . '"');
218
219
			self::install($parentModule);
220
		}
221
222
		unset(self::$processing[$moduleClass]);
223
		
224
		return true;
225
	}
226
	
227
	protected static function unsatisfiedDependencies($moduleClass) {
228
		return collect($moduleClass::requires())->diff(self::getInstalled())->filter()->all();
229
	}	
230
	
231
	public static function uninstall($classOrAlias)
232
	{
233
		if (! self::isInstalled($classOrAlias)) {
234
			print ('Module "' . $classOrAlias . '" is not installed!');
235
			
236
			return true;
237
		}
238
		
239
		if (! $moduleClass = self::getClass($classOrAlias)) {
240
			throw new \Exception('Module "' . $classOrAlias . '" could not be identified');
241
		}
242
		
243
		/**
244
		 * @var ModuleCore $module
245
		 */
246
		$module = new $moduleClass();
247
		
248
		$module->rollback();
249
		
250
		try {
251
			$module->uninstall();
252
		} catch (\Exception $exception) {
253
			$module->migrate();
254
			
255
			throw $exception;
256
		}
257
		
258
		$module->unpublishAssets();
259
		
260
		// update database
261
		Module::where('class', $moduleClass)->delete();
262
		
263
		self::clearCache();
264
		
265
		print ('Module ' . $classOrAlias . ' successfully uninstalled!');
266
		
267
		return true;
268
	}
269
}
270