Passed
Push — master ( e4e152...8fde47 )
by Georgi
02:55
created

ModuleManager::unsatisfiedDependencies()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
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 1
dl 0
loc 2
rs 10
1
<?php
2
3
namespace Epesi\Core\System\Integration\Modules;
4
5
use Illuminate\Support\Facades\Schema;
6
use Epesi\Core\System\Database\Models\Module;
7
use Illuminate\Support\Facades\Cache;
8
9
class ModuleManager
10
{
11
	use Concerns\HasPackageManifest;
12
	
13
	/**
14
	 * @var \Illuminate\Support\Collection
15
	 */
16
	private static $installed;
17
	private static $processing;
18
19
	public static function isInstalled($classOrAlias)
20
	{
21
		return self::getClass($classOrAlias)? 1: 0;
22
	}
23
	
24
	public static function isAvalable($classOrAlias)
25
	{
26
		return class_exists(self::getClass($classOrAlias));
27
	}
28
29
	/**
30
	 * Get the module core class from class or alias
31
	 * 
32
	 * @param string $classOrAlias
33
	 * @return string;
34
	 */
35
	public static function getClass($classOrAlias, $installed = false) {
36
		$allModules = $installed? self::getInstalled(): self::getAll();
37
		
38
		if (collect($allModules)->contains($classOrAlias)) return $classOrAlias;
39
		
40
		return $allModules[$classOrAlias]?? null;
41
	}
42
	
43
	/**
44
	 * Get a collection of installed modules in alias -> class pairs
45
	 * 
46
	 * @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...
47
	 */
48
	public static function getInstalled()
49
	{
50
		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...
51
			$installedModules = Schema::hasTable('modules')? Module::pluck('class', 'alias'): collect();
52
			
53
			// if epesi is not installed fake having the system module to enable its functionality
54
			if ($installedModules->isEmpty()) {
55
				$installedModules['system'] = \Epesi\Core\System\SystemCore::class;
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
		Cache::forget('epesi-modules-installed');
101
		Cache::forget('epesi-modules-available');
102
	}
103
	
104
	/**
105
	 * Collect array of results from $method in all installed module core classes
106
	 *
107
	 * @param string $method
108
	 * @return array
109
	 */
110
	public static function collect($method, $args = [])
111
	{
112
		$args = is_array($args)? $args: [$args];
113
		
114
		$ret = [];
115
		foreach (self::getInstalled() as $module) {
116
			if (! $list = $module::$method(...$args)) continue;
117
			
118
			$ret = array_merge($ret, is_array($list)? $list: [$list]);
119
		}
120
		
121
		return $ret;
122
	}
123
124
	/**
125
	 * Install the module class provided as argument
126
	 * 
127
	 * @param string $classOrAlias
128
	 */
129
	public static function install($classOrAlias)
130
	{
131
		if (self::isInstalled($classOrAlias)) {
132
			print ('Module "' . $classOrAlias . '" already installed!');
133
			
134
			return true;
135
		}
136
		
137
		if (! $moduleClass = self::getClass($classOrAlias)) {			
138
			throw new \Exception('Module "' . $classOrAlias . '" could not be identified');
139
		}
140
		
141
		/**
142
		 * @var ModuleCore $module
143
		 */
144
		$module = new $moduleClass();
145
		
146
		self::satisfyDependencies($moduleClass);
147
148
		$module->migrate();
149
		
150
		try {
151
			$module->install();
152
		} catch (\Exception $e) {
153
			$module->rollback();
154
			
155
			throw $e;
156
		}
157
		
158
		$module->publishAssets();
159
		
160
		// update database
161
		Module::create([
162
				'class' => $moduleClass,
163
				'alias' => $module->alias()
164
		]);
165
		
166
		self::clearCache();
167
		
168
		print ('Module ' . $classOrAlias . ' successfully installed!');
169
		
170
		return true;
171
	}
172
	
173
	/**
174
	 * Install modules that $moduleClass requires
175
	 * Performs operation recursively for all required modules
176
	 * 
177
	 * @param string $moduleClass
178
	 * @throws \Exception
179
	 * @return boolean
180
	 */
181
	protected static function satisfyDependencies($moduleClass) {
182
		self::$processing[$moduleClass] = true;
183
		
184
		try {
185
			while ($unsatisfiedDependencies = self::unsatisfiedDependencies($moduleClass)) {
186
				$parentModule = $unsatisfiedDependencies[0];
187
				
188
				if (self::$processing[$moduleClass]?? false) {
189
					throw new \Exception('Cross dependency: '. $moduleClass);
190
				}
191
				
192
				if (! self::isAvalable($parentModule)) {
193
					throw new \Exception('Module not found: "' . $parentModule . '"');
194
				}
195
	
196
				print("\n\r");
197
				print('Installing required module: "' . $parentModule . '" by "' . $moduleClass . '"');
198
199
				self::install($parentModule);
200
			}
201
		} catch (\Exception $e) {
202
			print("\n\r");
203
			print ($e->getMessage());			
204
			
205
			return false;
206
		}
207
		unset(self::$processing[$moduleClass]);
208
		
209
		return true;
210
	}
211
	
212
	protected static function unsatisfiedDependencies($moduleClass) {
213
		return array_diff($moduleClass::requires(), self::getInstalled());
0 ignored issues
show
Bug introduced by
self::getInstalled() of type Epesi\Core\System\Integr...nate\Support\Collection is incompatible with the type array expected by parameter $array2 of array_diff(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
		return array_diff($moduleClass::requires(), /** @scrutinizer ignore-type */ self::getInstalled());
Loading history...
214
	}	
215
}
216