Test Failed
Push — master ( ca4a51...2e41d8 )
by Jean-Christophe
39:53 queued 25:21
created

Preloader::asPhpArray()   B

Complexity

Conditions 7
Paths 18

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
c 1
b 0
f 0
dl 0
loc 23
rs 8.8333
cc 7
nc 18
nop 4
1
<?php
2
3
namespace Ubiquity\cache;
4
5
/**
6
 * Ubiquity\cache$Preloader
7
 * This class is part of Ubiquity
8
 *
9
 * @author jcheron <[email protected]>
10
 * @version 1.0.0
11
 *
12
 */
13
class Preloader {
14
	private $vendorDir;
15
	private static $libraries = [
16
								'application' => './../app/',
17
								'ubiquity' => 'phpmv/ubiquity/src/Ubiquity/',
18
								'ubiquity-dev' => 'phpmv/ubiquity-dev/src/Ubiquity/',
19
								'ubiquity-webtools' => 'phpmv/ubiquity-webtools/src/Ubiquity/',
20
								'ubiquity-mailer' => 'phpmv/ubiquity-mailer/src/Ubiquity/',
21
								'ubiquity-swoole' => 'phpmv/ubiquity-swoole/src/Ubiquity/',
22
								'ubiquity-workerman' => 'phpmv/ubiquity-workerman/src/Ubiquity/',
23
								'ubiquity-tarantool' => 'phpmv/ubiquity-tarantool/src/Ubiquity/',
24
								'ubiquity-mysqli' => 'phpmv/ubiquity-mysqli/src/Ubiquity/',
25
								'phpmv-ui' => 'phpmv/php-mv-ui/Ajax/' ];
26
	private $excludeds = [ ];
27
	private static $count = 0;
28
	private $classes = [ ];
29
	private $loader;
30
31
	/**
32
	 * Creates a new loader instance for this application.
33
	 *
34
	 * @param string $appRoot The app root
35
	 */
36
	public function __construct($appRoot) {
37
		$this->vendorDir = $appRoot . './../vendor/';
38
		$this->loader = require $this->vendorDir . 'autoload.php';
39
	}
40
41
	/**
42
	 * Adds paths to be scanned during preloading.
43
	 *
44
	 * @param string ...$paths
45
	 * @return Preloader
46
	 */
47
	public function paths(string ...$paths): Preloader {
48
		foreach ( $paths as $path ) {
49
			$this->addDir ( $path );
50
		}
51
		return $this;
52
	}
53
54
	/**
55
	 * Adds namespaces to exclude from preloading.
56
	 *
57
	 * @param string ...$names
58
	 * @return Preloader
59
	 */
60
	public function exclude(string ...$names): Preloader {
61
		$this->excludeds = \array_merge ( $this->excludeds, $names );
62
		return $this;
63
	}
64
65
	/**
66
	 * Adds a class to preload.
67
	 *
68
	 * @param string $class
69
	 * @return bool
70
	 */
71
	public function addClass(string $class): bool {
72
		if (! $this->isExcluded ( $class )) {
73
			if (! isset ( $this->classes [$class] )) {
74
				$path = $this->getPathFromClass ( $class );
75
				if (isset ( $path )) {
76
					$this->classes [$class] = $path;
77
					return true;
78
				}
79
			}
80
		}
81
		return false;
82
	}
83
84
	/**
85
	 * Adds an array of classes to preload.
86
	 *
87
	 * @param array $classes
88
	 */
89
	public function addClasses(array $classes) {
90
		foreach ( $classes as $class ) {
91
			$this->addClass ( $class );
92
		}
93
	}
94
95
	/**
96
	 * Preload all added classes.
97
	 */
98
	public function load() {
99
		foreach ( $this->classes as $class => $file ) {
100
			if (! $this->isExcluded ( $class )) {
101
				$this->loadClass ( $class, $file );
102
			}
103
		}
104
	}
105
106
	/**
107
	 * Returns a generated associative array of classes to preload (key: class, value: file).
108
	 *
109
	 * @return array
110
	 */
111
	public function generateClassesFiles(): array {
112
		$ret = [ ];
113
		foreach ( $this->classes as $class => $file ) {
114
			if (! $this->isExcluded ( $class )) {
115
				$ret [$class] = \realpath ( $file );
116
			}
117
		}
118
		return $ret;
119
	}
120
121
	/**
122
	 * Generate a file containing the associative array of classes to preload (classes-files=>[key: class, value: file}).
123
	 *
124
	 * @param string $filename
125
	 * @param ?bool $preserve
126
	 * @return int
127
	 */
128
	public function generateToFile(string $filename, ?bool $preserve = true): int {
129
		$array = [ ];
130
		if ($preserve && \file_exists ( $filename )) {
131
			$array = include $filename;
132
		}
133
		$array ['classes-files'] = $this->generateClassesFiles ();
134
		$content = "<?php\nreturn " . $this->asPhpArray ( $array, 'array', 1, true ) . ";";
135
		return \file_put_contents ( $filename, $content );
136
	}
137
138
	/**
139
	 * Adds a directory to be scanned during preloading.
140
	 *
141
	 * @param string $dirname
142
	 * @return \Ubiquity\cache\Preloader
143
	 */
144
	public function addDir(string $dirname): Preloader {
145
		$files = $this->glob_recursive ( $dirname . DIRECTORY_SEPARATOR . '*.php' );
146
		foreach ( $files as $file ) {
147
			$class = $this->getClassFullNameFromFile ( $file );
148
			if (isset ( $class )) {
149
				$this->addClassFile ( $class, $file );
150
			}
151
		}
152
		return $this;
153
	}
154
155
	/**
156
	 * Adds a part of an existing library to be preloaded.
157
	 * The available libraries can be obtained with the getLibraries method.
158
	 *
159
	 * @param string $library
160
	 * @param ?string $part
161
	 * @return bool
162
	 */
163
	public function addLibraryPart(string $library, ?string $part = ''): bool {
164
		if (isset ( self::$libraries [$library] )) {
165
			$dir = $this->vendorDir . self::$libraries [$library] . $part;
166
			if (\file_exists ( $dir )) {
167
				$this->addDir ( $dir );
168
				return true;
169
			}
170
		}
171
		return false;
172
	}
173
174
	/**
175
	 * Adds Ubiquity framework controller and routing classes preload.
176
	 *
177
	 * @return \Ubiquity\cache\Preloader
178
	 */
179
	public function addUbiquityControllers() {
180
		$this->addLibraryPart ( 'ubiquity', 'controllers' );
181
		$this->addClass ( 'Ubiquity\\events\\EventManager' );
182
		$this->addClass ( 'Ubiquity\\log\\Logger' );
183
		$this->exclude ( 'Ubiquity\\controllers\\crud', 'Ubiquity\\controllers\\rest', 'Ubiquity\\controllers\\seo', 'Ubiquity\\controllers\\auth' );
184
		return $this;
185
	}
186
187
	/**
188
	 * Adds Ubiquity framework cache system classes to preload.
189
	 *
190
	 * @return \Ubiquity\cache\Preloader
191
	 */
192
	public function addUbiquityCache() {
193
		$this->addClass ( 'Ubiquity\\cache\\CacheManager' );
194
		$this->addClass ( 'Ubiquity\\cache\\system\\ArrayCache' );
195
		return $this;
196
	}
197
198
	/**
199
	 * Adds Ubiquity framework PDO classes to preload.
200
	 *
201
	 * @return \Ubiquity\cache\Preloader
202
	 */
203
	public function addUbiquityPdo() {
204
		$this->addClass ( 'Ubiquity\\db\\Database' );
205
		$this->addClass ( 'Ubiquity\\cache\\database\\DbCache' );
206
		$this->addClass ( 'Ubiquity\\db\\SqlUtils' );
207
		$this->addLibraryPart ( 'ubiquity', 'db/providers' );
208
		return $this;
209
	}
210
211
	/**
212
	 * Adds Ubiquity framework ORM classes to preload.
213
	 *
214
	 * @return \Ubiquity\cache\Preloader
215
	 */
216
	public function addUbiquityORM() {
217
		$this->addLibraryPart ( 'ubiquity', 'orm' );
218
		$this->addClass ( 'Ubiquity\\events\\DAOEvents' );
219
		return $this;
220
	}
221
222
	/**
223
	 * Adds Ubiquity framework Http classes to preload.
224
	 *
225
	 * @return \Ubiquity\cache\Preloader
226
	 */
227
	public function addUbiquityHttpUtils() {
228
		$this->addClass ( 'Ubiquity\\utils\\http\\URequest' );
229
		$this->addClass ( 'Ubiquity\\utils\\http\\UResponse' );
230
		$this->addClass ( 'Ubiquity\\utils\\http\\foundation\\PhpHttp' );
231
		return $this;
232
	}
233
234
	/**
235
	 * Adds Ubiquity framework MicroTemplateEngine classes to preload.
236
	 *
237
	 * @return \Ubiquity\cache\Preloader
238
	 */
239
	public function addUbiquityViews() {
240
		$this->addClass ( 'Ubiquity\\views\\View' );
241
		$this->addClass ( 'Ubiquity\\views\\engine\\micro\\MicroTemplateEngine' );
242
		return $this;
243
	}
244
245
	/**
246
	 * Adds Ubiquity framework Translations classes to preload.
247
	 *
248
	 * @return \Ubiquity\cache\Preloader
249
	 */
250
	public function addUbiquityTranslations() {
251
		$this->addLibraryPart ( 'ubiquity', 'translation' );
252
		return $this;
253
	}
254
255
	/**
256
	 * Adds Ubiquity-workerman classes to preload.
257
	 *
258
	 * @return \Ubiquity\cache\Preloader
259
	 */
260
	public function addUbiquityWorkerman() {
261
		$this->addLibraryPart ( 'ubiquity-workerman' );
262
		return $this;
263
	}
264
265
	/**
266
	 * Adds classes from an application part (app folder) to preload.
267
	 *
268
	 * @param string $part
269
	 * @return boolean
270
	 */
271
	public function addApplicationPart(?string $part = '') {
272
		return $this->addLibraryPart ( 'application', $part );
273
	}
274
275
	/**
276
	 *
277
	 * @param string $dir
278
	 * @return boolean
279
	 */
280
	public function addApplicationModels($dir = 'models') {
281
		return $this->addLibraryPart ( 'application', $dir );
282
	}
283
284
	/**
285
	 *
286
	 * @param string $dir
287
	 * @return boolean
288
	 */
289
	public function addApplicationCache($dir = 'cache') {
290
		return $this->addLibraryPart ( 'application', $dir );
291
	}
292
293
	/**
294
	 *
295
	 * @param string $dir
296
	 * @return boolean
297
	 */
298
	public function addApplicationControllers($dir = 'controllers') {
299
		$this->addLibraryPart ( 'application', $dir );
300
		$this->exclude ( $dir . '\\MaintenanceController', $dir . '\\Admin' );
301
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Ubiquity\cache\Preloader which is incompatible with the documented return type boolean.
Loading history...
302
	}
303
304
	/**
305
	 * Add Ubiquity hot classes for preloading
306
	 *
307
	 * @param boolean $hasDatabase
308
	 */
309
	public function addUbiquityBasics($hasDatabase = true) {
310
		$this->addUbiquityCache ();
311
		$this->addUbiquityControllers ();
312
		$this->addUbiquityHttpUtils ();
313
		if ($hasDatabase) {
314
			$this->addUbiquityPdo ();
315
			$this->addUbiquityORM ();
316
		}
317
	}
318
319
	/**
320
	 * Adds Twig templating system classes to preload.
321
	 *
322
	 * @return \Ubiquity\cache\Preloader
323
	 */
324
	public function addUbiquityTwig() {
325
		$this->addClasses ( [ 'Ubiquity\\views\\engine\\Twig','Twig\\Cache\\FilesystemCache','Twig\\Extension\\CoreExtension','Twig\\Extension\\EscaperExtension','Twig\\Extension\\OptimizerExtension','Twig\\Extension\\StagingExtension','Twig\\ExtensionSet','Twig\\Template','Twig\\TemplateWrapper' ] );
326
		return $this;
327
	}
328
329
	/**
330
	 * Defines classes to be preloaded in a file returning an associative array keys : (classes-files, excludeds, paths, classes, libraries-parts, callback).
331
	 *
332
	 * @param string $appRoot
333
	 * @param string $filename
334
	 * @return bool
335
	 */
336
	public static function fromFile(string $appRoot, string $filename): bool {
337
		if (\file_exists ( $filename )) {
338
			$array = include $filename;
339
			return self::fromArray ( $appRoot, $array );
340
		}
341
		return false;
342
	}
343
344
	/**
345
	 * Defines classes to be preloaded with an associative array keys : (classes-files, excludeds, paths, classes, libraries-parts, callback).
346
	 *
347
	 * @param string $appRoot
348
	 * @param array $array
349
	 * @return bool
350
	 */
351
	public static function fromArray(string $appRoot, array $array): bool {
352
		$pre = new self ( $appRoot );
353
		self::$count = 0;
354
		if (isset ( $array ['classes-files'] )) {
355
			$pre->classes = $array ['classes-files'];
356
		}
357
		if (isset ( $array ['excludeds'] )) {
358
			$pre->excludeds = $array ['excludeds'];
359
		}
360
		if (isset ( $array ['paths'] )) {
361
			foreach ( $array ['paths'] as $path ) {
362
				$pre->addDir ( $path );
363
			}
364
		}
365
		if (isset ( $array ['classes'] )) {
366
			foreach ( $array ['classes'] as $class ) {
367
				$pre->addClass ( $class );
368
			}
369
		}
370
		if (isset ( $array ['libraries-parts'] )) {
371
			foreach ( $array ['libraries-parts'] as $library => $parts ) {
372
				foreach ( $parts as $part ) {
373
					$pre->addLibraryPart ( $library, $part );
374
				}
375
			}
376
		}
377
		if (isset ( $array ['callback'] )) {
378
			if (\is_callable ( $array ['callback'] )) {
379
				$call = $array ['callback'];
380
				$call ( $pre );
381
			}
382
		}
383
		$pre->load ();
384
		return self::$count > 0;
385
	}
386
387
	/**
388
	 * Generates a preload classes-files array from cached files
389
	 *
390
	 * @param boolean $resetExisting
391
	 */
392
	public function generateClassesFromRunning($resetExisting = true) {
393
		$cache = \opcache_get_status ( true );
394
		if ($resetExisting) {
395
			$this->classes = [ ];
396
		}
397
		foreach ( $cache ['scripts'] as $script ) {
398
			$path = $script ['full_path'];
399
			$class = $this->getClassFullNameFromFile ( $path );
400
			if (isset ( $class )) {
401
				$this->addClassFile ( $class, $path );
402
			}
403
		}
404
	}
405
406
	/**
407
	 * Returns an array of available libraries to be preloaded
408
	 *
409
	 * @return array
410
	 */
411
	public static function getLibraries() {
412
		return \array_keys ( self::$libraries );
413
	}
414
415
	private function addClassFile($class, $file) {
416
		if (! isset ( $this->classes [$class] )) {
417
			$this->classes [$class] = $file;
418
		}
419
	}
420
421
	private function loadClass($class, $file = null) {
422
		if (! \class_exists ( $class, false )) {
423
			$file = $file ?? $this->getPathFromClass ( $class );
424
			if (isset ( $file )) {
425
				$this->loadFile ( $file );
426
			}
427
		}
428
		if (\class_exists ( $class, false )) {
429
			echo "$class loaded !\n";
430
		}
431
	}
432
433
	private function getPathFromClass(string $class): ?string {
434
		$classPath = $this->loader->findFile ( $class );
435
		if (false !== $classPath) {
436
			return \realpath ( $classPath );
437
		}
438
		return null;
439
	}
440
441
	private function loadFile(string $file): void {
442
		require_once ($file);
443
		self::$count ++;
444
	}
445
446
	private function isExcluded(string $name): bool {
447
		foreach ( $this->excludeds as $excluded ) {
448
			if (\strpos ( $name, $excluded ) === 0) {
449
				return true;
450
			}
451
		}
452
		return false;
453
	}
454
455
	private function glob_recursive($pattern, $flags = 0) {
456
		$files = \glob ( $pattern, $flags );
457
		foreach ( \glob ( \dirname ( $pattern ) . '/*', GLOB_ONLYDIR | GLOB_NOSORT ) as $dir ) {
458
			$files = \array_merge ( $files, $this->glob_recursive ( $dir . '/' . \basename ( $pattern ), $flags ) );
1 ignored issue
show
Bug introduced by
It seems like $this->glob_recursive($d...name($pattern), $flags) can also be of type false; however, parameter $array2 of array_merge() does only seem to accept array|null, maybe add an additional type check? ( Ignorable by Annotation )

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

458
			$files = \array_merge ( $files, /** @scrutinizer ignore-type */ $this->glob_recursive ( $dir . '/' . \basename ( $pattern ), $flags ) );
Loading history...
459
		}
460
		return $files;
461
	}
462
463
	private function getClassFullNameFromFile($filePathName, $backSlash = false) {
464
		$phpCode = \file_get_contents ( $filePathName );
465
		$class = $this->getClassNameFromPhpCode ( $phpCode );
466
		if (isset ( $class )) {
467
			$ns = $this->getClassNamespaceFromPhpCode ( $phpCode );
468
			if ($backSlash && $ns != null) {
1 ignored issue
show
Bug introduced by
It seems like you are loosely comparing $ns of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
469
				$ns = "\\" . $ns;
470
			}
471
			return $ns . '\\' . $class;
472
		}
473
		return null;
474
	}
475
476
	private function getClassNamespaceFromPhpCode($phpCode) {
477
		$tokens = \token_get_all ( $phpCode );
478
		$count = \count ( $tokens );
479
		$i = 0;
480
		$namespace = '';
481
		$namespace_ok = false;
482
		while ( $i < $count ) {
483
			$token = $tokens [$i];
484
			if (\is_array ( $token ) && $token [0] === T_NAMESPACE) {
485
				// Found namespace declaration
486
				while ( ++ $i < $count ) {
487
					if ($tokens [$i] === ';') {
488
						$namespace_ok = true;
489
						$namespace = \trim ( $namespace );
490
						break;
491
					}
492
					$namespace .= \is_array ( $tokens [$i] ) ? $tokens [$i] [1] : $tokens [$i];
493
				}
494
				break;
495
			}
496
			$i ++;
497
		}
498
		if (! $namespace_ok) {
499
			return null;
500
		}
501
		return $namespace;
502
	}
503
504
	private function getClassNameFromPhpCode($phpCode) {
505
		$classes = array ();
506
		$tokens = \token_get_all ( $phpCode );
507
		$count = count ( $tokens );
508
		for($i = 2; $i < $count; $i ++) {
509
			$elm = $tokens [$i - 2] [0];
510
			if ($elm == T_CLASS && $tokens [$i - 1] [0] == T_WHITESPACE && $tokens [$i] [0] == T_STRING) {
511
				$class_name = $tokens [$i] [1];
512
				$classes [] = $class_name;
513
			}
514
		}
515
		if (isset ( $classes [0] ))
516
			return $classes [0];
517
		return null;
518
	}
519
520
	private function asPhpArray($array, $prefix = "", $depth = 1, $format = false) {
521
		$exts = array ();
522
		$extsStr = "";
523
		$tab = "";
524
		$nl = "";
525
		if ($format) {
526
			$tab = \str_repeat ( "\t", $depth );
527
			$nl = PHP_EOL;
528
		}
529
		foreach ( $array as $k => $v ) {
530
			if (\is_string ( $k )) {
531
				$exts [] = "\"" . $this->doubleBackSlashes ( $k ) . "\"=>" . $this->parseValue ( $v, 'array', $depth + 1, $format );
532
			} else {
533
				$exts [] = $this->parseValue ( $v, $prefix, $depth + 1, $format );
534
			}
535
		}
536
		if (\sizeof ( $exts ) > 0 || $prefix !== "") {
537
			$extsStr = "(" . \implode ( "," . $nl . $tab, $exts ) . ")";
538
			if (\sizeof ( $exts ) > 0) {
539
				$extsStr = "(" . $nl . $tab . \implode ( "," . $nl . $tab, $exts ) . $nl . $tab . ")";
540
			}
541
		}
542
		return $prefix . $extsStr;
543
	}
544
545
	private function parseValue($v, $prefix = "", $depth = 1, $format = false) {
546
		if (\is_array ( $v )) {
547
			$result = $this->asPhpArray ( $v, $prefix, $depth + 1, $format );
548
		} elseif ($v instanceof \Closure) {
549
			$result = $this->closure_dump ( $v );
550
		} else {
551
			$result = $this->doubleBackSlashes ( $v );
552
			$result = "\"" . \str_replace ( [ '$','"' ], [ '\$','\"' ], $result ) . "\"";
553
		}
554
		return $result;
555
	}
556
557
	private function closure_dump(\Closure $c) {
558
		$str = 'function (';
559
		$r = new \ReflectionFunction ( $c );
560
		$params = array ();
561
		foreach ( $r->getParameters () as $p ) {
562
			$s = '';
563
			if ($p->isArray ()) {
564
				$s .= 'array ';
565
			} else if ($p->getClass ()) {
566
				$s .= $p->getClass ()->name . ' ';
567
			}
568
			if ($p->isPassedByReference ()) {
569
				$s .= '&';
570
			}
571
			$s .= '$' . $p->name;
572
			if ($p->isOptional ()) {
573
				$s .= ' = ' . \var_export ( $p->getDefaultValue (), TRUE );
574
			}
575
			$params [] = $s;
576
		}
577
		$str .= \implode ( ', ', $params );
578
		$str .= ')';
579
		$lines = file ( $r->getFileName () );
580
		$sLine = $r->getStartLine ();
581
		$eLine = $r->getEndLine ();
582
		if ($eLine === $sLine) {
583
			$match = \strstr ( $lines [$sLine - 1], "function" );
584
			$str .= \strstr ( \strstr ( $match, "{" ), "}", true ) . "}";
585
		} else {
586
			$str .= \strrchr ( $lines [$sLine - 1], "{" );
587
			for($l = $sLine; $l < $eLine - 1; $l ++) {
588
				$str .= $lines [$l];
589
			}
590
			$str .= \strstr ( $lines [$eLine - 1], "}", true ) . "}";
591
		}
592
		$vars = $r->getStaticVariables ();
593
		foreach ( $vars as $k => $v ) {
594
			$str = \str_replace ( '$' . $k, \var_export ( $v, true ), $str );
595
		}
596
		return $str;
597
	}
598
599
	private function doubleBackSlashes($value) {
600
		if (\is_string ( $value ))
601
			return \str_replace ( "\\", "\\\\", $value );
602
		return $value;
603
	}
604
}
605
606