Passed
Push — master ( 370e51...582fe4 )
by Georgi
03:26
created

ModuleManager::call()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
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;
0 ignored issues
show
Bug introduced by
The type Epesi\Core\System\Integr...nate\Support\Collection was not found. Did you mean Illuminate\Support\Collection? If so, make sure to prefix the type with \.
Loading history...
48
	 */
49
	public static function getInstalled()
50
	{
51
		return self::$installed = self::$installed?? self::getCached('epesi-modules-installed', function() {
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::installed =...ion(...) { /* ... */ }) also could return the type Illuminate\Support\Collection which is incompatible with the documented return type Epesi\Core\System\Integr...nate\Support\Collection.
Loading history...
52
			// if epesi is not installed fake having the system module to enable its functionality
53
			try {
54
				$installedModules = Module::pluck('class', 'alias');
55
			} catch (QueryException $e) {
56
// 				$installedModules = collect([
57
// 						'system' => \Epesi\Core\System\SystemCore::class
58
// 				]);
59
				$installedModules = collect();
60
			}
61
			
62
			return $installedModules;
63
		});
64
	}
65
	
66
	/**
67
	 * Get a collection of all manifested modules in alias -> class pairs
68
	 * 
69
	 * @return Illuminate\Support\Collection;
70
	 */
71
	public static function getAll()
72
	{
73
		return self::getCached('epesi-modules-available', function () {
74
			$modules = collect();
75
			foreach (array_merge(config('epesi.modules', []), self::packageManifest()->modules()?: []) as $moduleClass) {
76
				$modules->add(['alias' => $moduleClass::alias(), 'class' => $moduleClass]);
77
			}
78
79
			return $modules->pluck('class', 'alias');
80
		});
81
	}
82
	
83
	/**
84
	 * Common method to use for caching of data within module manager
85
	 * 
86
	 * @param string $key
87
	 * @param \Closure $default
88
	 * @return mixed
89
	 */
90
	protected static function getCached($key, \Closure $default)
91
	{
92
		if (! Cache::has($key)) {
93
			Cache::forever($key, $default());
94
		}
95
96
		return Cache::get($key);
97
	}
98
	
99
	/**
100
	 * Clear module manager cache
101
	 */
102
	public static function clearCache()
103
	{
104
		self::$installed = null;
105
		Cache::forget('epesi-modules-installed');
106
		Cache::forget('epesi-modules-available');
107
	}
108
	
109
	/**
110
	 * Alias for collect when no return values expected
111
	 *
112
	 * @param string $method
113
	 * @return array
114
	 */
115
	public static function call($method, $args = [])
116
	{
117
		return self::collect($method, $args);
118
	}
119
	
120
	/**
121
	 * Collect array of results from $method in all installed module core classes
122
	 *
123
	 * @param string $method
124
	 * @return array
125
	 */
126
	public static function collect($method, $args = [])
127
	{
128
		$args = is_array($args)? $args: [$args];
129
		
130
		$installedModules = self::getInstalled();
131
		
132
		// if epesi is not installed fake having the system module to enable its functionality
133
		if ($installedModules->isEmpty()) {
134
			$installedModules = collect([
135
				'system' => \Epesi\Core\System\SystemCore::class
136
			]);
137
		}
138
		
139
		$ret = [];
140
		foreach ($installedModules as $module) {
141
			if (! $list = $module::$method(...$args)) continue;
142
			
143
			$ret = array_merge($ret, is_array($list)? $list: [$list]);
144
		}
145
		
146
		return $ret;
147
	}
148
149
	/**
150
	 * Install the module class provided as argument
151
	 * 
152
	 * @param string $classOrAlias
153
	 */
154
	public static function install($classOrAlias, $abortIfInstalled = true)
155
	{
156
		if ($abortIfInstalled && self::isInstalled($classOrAlias)) {
157
			print ('Module "' . $classOrAlias . '" already installed!');
158
			
159
			return true;
160
		}
161
		
162
		if (! $moduleClass = self::getClass($classOrAlias)) {			
163
			throw new \Exception('Module "' . $classOrAlias . '" could not be identified');
164
		}
165
		
166
		/**
167
		 * @var ModuleCore $module
168
		 */
169
		$module = new $moduleClass();
170
		
171
		$module->migrate();
172
		
173
		self::satisfyDependencies($moduleClass);
174
		
175
		try {
176
			$module->install();
177
		} catch (\Exception $exception) {
178
			$module->rollback();
179
			
180
			throw $exception;
181
		}
182
		
183
		$module->publishAssets();
184
		
185
		// update database
186
		Module::create([
187
				'class' => $moduleClass,
188
				'alias' => $module->alias()
189
		]);
190
		
191
		self::clearCache();
192
		
193
		print ('Module ' . $classOrAlias . ' successfully installed!');
194
		
195
		return true;
196
	}
197
	
198
	/**
199
	 * Install modules that $moduleClass requires
200
	 * Performs operation recursively for all required modules
201
	 * 
202
	 * @param string $moduleClass
203
	 * @throws \Exception
204
	 * @return boolean
205
	 */
206
	protected static function satisfyDependencies($moduleClass) {
207
		self::$processing[$moduleClass] = true;
208
		
209
		while ($unsatisfiedDependencies = self::unsatisfiedDependencies($moduleClass)) {
210
			$parentModule = array_shift($unsatisfiedDependencies);
211
				
212
			if (self::$processing[$parentModule]?? false) {
213
				throw new Exception('Cross dependency: '. $parentModule);
214
			}
215
				
216
			if (! self::isAvalable($parentModule)) {
217
				throw new Exception('Module not found: "' . $parentModule . '"');
218
			}
219
	
220
			print("\n\r");
221
			print('Installing required module: "' . $parentModule . '" by "' . $moduleClass . '"');
222
223
			self::install($parentModule);
224
		}
225
226
		unset(self::$processing[$moduleClass]);
227
		
228
		return true;
229
	}
230
	
231
	protected static function unsatisfiedDependencies($moduleClass) {
232
		return collect($moduleClass::requires())->diff(self::getInstalled())->filter()->all();
233
	}	
234
}
235