Prado   F
last analyzed

Complexity

Total Complexity 178

Size/Duplication

Total Lines 886
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 297
dl 0
loc 886
ccs 0
cts 0
cp 0
rs 2
c 1
b 0
f 0
wmc 178

43 Methods

Rating   Name   Duplication   Size   Complexity  
A phpFatalErrorHandler() 0 7 4
A phpErrorHandler() 0 6 2
A exceptionHandler() 0 8 3
A getPathOfAlias() 0 3 1
A getApplication() 0 3 1
A init() 0 4 1
A setApplication() 0 6 3
A fatal() 0 6 2
A getPathAliases() 0 3 1
A warning() 0 6 2
B createComponent() 0 40 11
A varDump() 0 3 1
A getDefaultPermissions() 0 3 1
A initErrorHandlers() 0 18 1
B trace() 0 18 8
A profileEnd() 0 6 2
A debug() 0 13 6
B getPathOfNamespace() 0 22 8
A getFrameworkPath() 0 3 1
A poweredByPrado() 0 10 3
B localize() 0 31 8
B method_visible() 0 13 7
A initAutoloader() 0 5 1
A info() 0 6 2
A isCallingSelf() 0 4 4
A getDefaultFilePermissions() 0 3 1
A getUserLanguages() 0 20 6
A isCallingSelfClass() 0 4 5
A setPathOfAlias() 0 12 6
A getVersion() 0 3 1
A autoload() 0 3 1
A notice() 0 6 2
A error() 0 6 2
A alert() 0 6 2
A getLogger() 0 6 2
A log() 0 9 3
D fatalError() 0 59 20
A prado3NamespaceToPhpNamespace() 0 10 3
A callingObject() 0 7 3
A getDefaultDirPermissions() 0 3 1
A getPreferredLanguage() 0 13 4
D using() 0 59 30
A profileBegin() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like Prado 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 Prado, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Prado class file.
5
 *
6
 * This is the file that establishes the PRADO component model
7
 * and error handling mechanism.
8
 *
9
 * @author Qiang Xue <[email protected]>
10
 * @link https://github.com/pradosoft/prado
11
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
12
 */
13
14
namespace Prado;
15
16
use Prado\Exceptions\TInvalidDataValueException;
17
use Prado\Exceptions\TInvalidOperationException;
18
use Prado\Exceptions\TPhpErrorException;
19
use Prado\Exceptions\TPhpFatalErrorException;
20
use Prado\Util\TLogger;
21
use Prado\Util\TVarDumper;
22
use Prado\I18N\Translation;
23
24
// Defines the initialization run time of Prado if no start time is defined.
25
if (empty($_SERVER["REQUEST_TIME_FLOAT"])) {
26
	$time = $_SERVER["REQUEST_TIME_FLOAT"] = microtime(true);
27
	$_SERVER["REQUEST_TIME"] = (int) $time;
28
}
29
30
// Defines the PRADO framework installation path.
31
if (!defined('PRADO_DIR')) {
32
	define('PRADO_DIR', __DIR__);
33
}
34
35
// Defines the default permission for writable directories
36
// @todo, the test on PRADO_CHMOD must be remove in the next major release
37
if (!defined('PRADO_DIR_CHMOD')) {
38
	define('PRADO_DIR_CHMOD', !defined('PRADO_CHMOD') ? 0o755 : PRADO_CHMOD);
39
}
40
41
// Defines the default permission for writable files
42
// @todo, the test on PRADO_CHMOD must be removed in the next major release
43
if (!defined('PRADO_FILE_CHMOD')) {
44
	define('PRADO_FILE_CHMOD', !defined('PRADO_CHMOD') ? 0o644 : PRADO_CHMOD);
45
}
46
47
// Defines the default permission for writable directories and files
48
// @todo, adding this define must be removed in the next major release
49
if (!defined('PRADO_CHMOD')) {
50
	define('PRADO_CHMOD', 0o777);
51
}
52
53
// Defines the Composer's vendor/ path.
54
if (!defined('PRADO_VENDORDIR')) {
55
	$reflector = new \ReflectionClass('\Composer\Autoload\ClassLoader');
56
	define('PRADO_VENDORDIR', dirname((string) $reflector->getFileName(), 2));
57
	unset($reflector);
58
}
59
60
/**
61
 * Prado class.
62
 *
63
 * Prado implements a few fundamental static methods.
64
 *
65
 * To use the static methods, Use Prado as the class name rather than Prado.
66
 * Prado is meant to serve as the base class of Prado. The latter might be
67
 * rewritten for customization.
68
 *
69
 * @author Qiang Xue <[email protected]>
70
 * @since 3.0
71
 */
72
class Prado
73
{
74
	/**
75
	 * File extension for Prado class files.
76
	 */
77
	public const CLASS_FILE_EXT = '.php';
78
	/**
79
	 * @var array<string, string> list of path aliases
80
	 */
81
	private static $_aliases = [
82
		'Prado' => PRADO_DIR,
83
		'Vendor' => PRADO_VENDORDIR,
84
		];
85
	/**
86
	 * @var array<string, string> list of namespaces currently in use
87
	 */
88
	private static $_usings = [
89
		'Prado' => PRADO_DIR,
90
		];
91
	/**
92
	 * @var array<string, string> list of namespaces currently in use
93
	 */
94
	public static $classMap = [];
95
	/**
96
	 * @var null|TApplication the application instance
97
	 */
98
	private static $_application;
99
	/**
100
	 * @var null|TLogger logger instance
101
	 */
102
	private static $_logger;
103
	/**
104
	 * @var array<string, bool> list of class exists checks
105
	 */
106
	protected static $classExists = [];
107
	/**
108
	 * @return string the version of Prado framework
109
	 */
110
	public static function getVersion(): string
111
	{
112
		return '4.3.1';
113
	}
114
115
	/**
116
	 * Initializes the Prado static class
117
	 */
118
	public static function init(): void
119
	{
120
		static::initAutoloader();
121
		static::initErrorHandlers();
122
	}
123
124
	/**
125
	 * Loads the static classmap and registers the autoload function.
126
	 */
127
	public static function initAutoloader(): void
128
	{
129
		self::$classMap = require(__DIR__ . '/classes.php');
130
131
		spl_autoload_register([static::class, 'autoload']);
132
	}
133
134
	/**
135
	 * Initializes error handlers.
136
	 * This method set error and exception handlers to be functions
137
	 * defined in this class.
138
	 */
139
	public static function initErrorHandlers(): void
140
	{
141
		/**
142
		 * Sets error handler to be Prado::phpErrorHandler
143
		 */
144
		set_error_handler([static::class, 'phpErrorHandler']);
145
		/**
146
		 * Sets shutdown function to be Prado::phpFatalErrorHandler
147
		 */
148
		register_shutdown_function([static::class, 'phpFatalErrorHandler']);
149
		/**
150
		 * Sets exception handler to be Prado::exceptionHandler
151
		 */
152
		set_exception_handler([static::class, 'exceptionHandler']);
153
		/**
154
		 * Disable php's builtin error reporting to avoid duplicated reports
155
		 */
156
		ini_set('display_errors', '0');
157
	}
158
159
	/**
160
	 * Class autoload loader.
161
	 * This method is provided to be registered within an spl_autoload_register() method.
162
	 * @param string $className class name
163
	 */
164
	public static function autoload($className): void
165
	{
166
		static::using($className);
167
	}
168
169
	/**
170
	 * @param int $logoType the type of "powered logo". Valid values include 0 and 1.
171
	 * @return string a string that can be displayed on your Web page showing powered-by-PRADO information
172
	 */
173
	public static function poweredByPrado($logoType = 0): string
174
	{
175
		$logoName = $logoType == 1 ? 'powered2' : 'powered';
176
		if (self::$_application !== null) {
177
			$am = self::$_application->getAssetManager();
178
			$url = $am->publishFilePath((string) self::getPathOfNamespace('Prado\\' . $logoName, '.gif'));
179
		} else {
180
			$url = 'http://pradosoft.github.io/docs/' . $logoName . '.gif';
181
		}
182
		return '<a title="Powered by PRADO" href="https://github.com/pradosoft/prado" target="_blank"><img src="' . $url . '" style="border-width:0px;" alt="Powered by PRADO" /></a>';
183
	}
184
185
	/**
186
	 * PHP error handler.
187
	 * This method should be registered as PHP error handler using
188
	 * {@see set_error_handler}. The method throws an exception that
189
	 * contains the error information.
190
	 * @param int $errno the level of the error raised
191
	 * @param string $errstr the error message
192
	 * @param string $errfile the filename that the error was raised in
193
	 * @param int $errline the line number the error was raised at
194
	 */
195
	public static function phpErrorHandler($errno, $errstr, $errfile, $errline): bool
196
	{
197
		if (error_reporting() & $errno) {
198
			throw new TPhpErrorException($errno, $errstr, $errfile, $errline);
199
		}
200
		return true;
201
	}
202
203
	/**
204
	 * PHP shutdown function used to catch fatal errors.
205
	 * This method should be registered as PHP error handler using
206
	 * {@see register_shutdown_function}. The method throws an exception that
207
	 * contains the error information.
208
	 */
209
	public static function phpFatalErrorHandler(): void
210
	{
211
		$error = error_get_last();
212
		if ($error &&
213
			TPhpErrorException::isFatalError($error) &&
214
			error_reporting() & $error['type']) {
215
			self::exceptionHandler(new TPhpFatalErrorException($error['type'], $error['message'], $error['file'], $error['line']));
216
		}
217
	}
218
219
	/**
220
	 * Default exception handler.
221
	 * This method should be registered as default exception handler using
222
	 * {@see set_exception_handler}. The method tries to use the errorhandler
223
	 * module of the Prado application to handle the exception.
224
	 * If the application or the module does not exist, it simply echoes the
225
	 * exception.
226
	 * @param \Throwable $exception exception that is not caught
227
	 */
228
	public static function exceptionHandler($exception): void
229
	{
230
		if (self::$_application !== null && ($errorHandler = self::$_application->getErrorHandler()) !== null) {
231
			$errorHandler->handleError(null, $exception);
232
		} else {
233
			echo $exception;
234
		}
235
		exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
236
	}
237
238
	/**
239
	 * Stores the application instance in the class static member.
240
	 * This method helps implement a singleton pattern for TApplication.
241
	 * Repeated invocation of this method or the application constructor
242
	 * will cause the throw of an exception.
243
	 * This method should only be used by framework developers.
244
	 * @param TApplication $application the application instance
245
	 * @throws TInvalidOperationException if this method is invoked twice or more.
246
	 */
247
	public static function setApplication($application): void
248
	{
249
		if (self::$_application !== null && !defined('PRADO_TEST_RUN')) {
250
			throw new TInvalidOperationException('prado_application_singleton_required');
251
		}
252
		self::$_application = $application;
253
	}
254
255
	/**
256
	 * @return null|TApplication the application singleton, null if the singleton has not be created yet.
257
	 */
258
	public static function getApplication(): ?TApplication
259
	{
260
		return self::$_application;
261
	}
262
263
	/**
264
	 * @return string the path of the framework
265
	 */
266
	public static function getFrameworkPath(): string
267
	{
268
		return PRADO_DIR;
269
	}
270
271
	/**
272
	 * @deprecated deprecated since version 4.2.2, replaced by @getDefaultDirPermissions and @getDefaultFilePermissions
273
	 * @return int chmod permissions, defaults to 0777
274
	 */
275
	public static function getDefaultPermissions(): int
276
	{
277
		return PRADO_CHMOD;
278
	}
279
280
	/**
281
	 * @return int chmod dir permissions, defaults to 0755
282
	 */
283
	public static function getDefaultDirPermissions(): int
284
	{
285
		return PRADO_DIR_CHMOD;
286
	}
287
288
	/**
289
	 * @return int chmod file permissions, defaults to 0644
290
	 */
291
	public static function getDefaultFilePermissions(): int
292
	{
293
		return PRADO_FILE_CHMOD;
294
	}
295
296
	/**
297
	 * Convert old Prado namespaces to PHP namespaces
298
	 * @param string $type old class name in Prado3 namespace format
299
	 * @return string Equivalent class name in PHP namespace format
300
	 */
301
	protected static function prado3NamespaceToPhpNamespace($type): string
302
	{
303
		if (substr($type, 0, 6) === 'System') {
304
			$type = 'Prado' . substr($type, 6);
305
		}
306
307
		if (false === strpos($type, '\\')) {
308
			return str_replace('.', '\\', $type);
309
		} else {
310
			return $type;
311
		}
312
	}
313
314
	/**
315
	 * Creates a component with the specified type.
316
	 * A component type can be either the component class name
317
	 * or a namespace referring to the path of the component class file.
318
	 * For example, 'TButton', '\Prado\Web\UI\WebControls\TButton' are both
319
	 * valid component type.
320
	 * This method can also pass parameters to component constructors.
321
	 * All parameters passed to this method except the first one (the component type)
322
	 * will be supplied as component constructor parameters.
323
	 * @template T
324
	 * @param class-string<T> $requestedType component type
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
325
	 * @param array<mixed> $params
326
	 * @throws TInvalidDataValueException if the component type is unknown
327
	 * @return T component instance of the specified type
328
	 */
329
	public static function createComponent($requestedType, ...$params)
330
	{
331
		$properties = null;
332
		if (is_array($requestedType)) {
333
			$properties = $requestedType;
334
			$requestedType = $properties['class'];
335
			unset($properties['class']);
336
		}
337
		$type = static::prado3NamespaceToPhpNamespace($requestedType);
338
		if (!isset(self::$classExists[$type])) {
339
			self::$classExists[$type] = class_exists($type, false);
340
		}
341
342
		if (!isset(self::$_usings[$type]) && !self::$classExists[$type]) {
343
			static::using($type);
344
			self::$classExists[$type] = class_exists($type, false);
345
		}
346
347
		/*
348
		 * Old apps compatibility support: if the component name has been specified using the
349
		 * old namespace syntax (eg. Application.Common.MyDataModule), assume that the calling
350
		 * code expects the class not to be php5.3-namespaced (eg: MyDataModule instead of
351
		 * \Application\Common\MyDataModule)
352
		 * Skip this if the class is inside the Prado\* namespace, since all Prado classes are now namespaced
353
		 */
354
		if (($pos = strrpos($type, '\\')) !== false && ($requestedType != $type) && strpos($type, 'Prado\\') !== 0) {
355
			$type = substr($type, $pos + 1);
356
		}
357
358
		if (count($params) > 0) {
359
			$object = new $type(...$params);
360
		} else {
361
			$object = new $type();
362
		}
363
		if ($properties) {
364
			foreach ($properties as $property => $value) {
365
				$object->setSubProperty($property, $value);
366
			}
367
		}
368
		return $object;
369
	}
370
371
	/**
372
	 * Uses a namespace.
373
	 * A namespace ending with an asterisk '*' refers to a directory, otherwise it represents a PHP file.
374
	 * If the namespace corresponds to a directory, the directory will be appended
375
	 * to the include path. If the namespace corresponds to a file, it will be included (include_once).
376
	 * @param string $namespace namespace to be used
377
	 * @throws TInvalidDataValueException if the namespace is invalid
378
	 */
379
	public static function using($namespace): void
380
	{
381
		$namespace = static::prado3NamespaceToPhpNamespace($namespace);
382
383
		if (isset(self::$_usings[$namespace]) ||
384
			class_exists($namespace, false) ||
385
			interface_exists($namespace, false) ||
386
			trait_exists($namespace, false)) {
387
			return;
388
		}
389
390
		if (array_key_exists($namespace, self::$classMap)) {
391
			// fast autoload a Prado3 class name
392
			$phpNamespace = self::$classMap[$namespace];
393
			if (class_exists($phpNamespace, true) ||
394
				interface_exists($phpNamespace, true) ||
395
				trait_exists($phpNamespace, true)) {
396
				if (!class_exists($namespace) &&
397
					!interface_exists($namespace) &&
398
					!trait_exists($namespace)) {
399
					class_alias($phpNamespace, $namespace);
400
				}
401
				return;
402
			}
403
		} elseif (($pos = strrpos($namespace, '\\')) === false) {
404
			// trying to autoload an old class name
405
			foreach (self::$_usings as $k => $v) {
406
				$path = $v . DIRECTORY_SEPARATOR . $namespace . self::CLASS_FILE_EXT;
407
				if (file_exists($path)) {
408
					$phpNamespace = '\\' . $k . '\\' . $namespace;
409
					if (class_exists($phpNamespace, true) ||
410
						interface_exists($phpNamespace, true) ||
411
						trait_exists($phpNamespace, true)) {
412
						if (!class_exists($namespace) &&
413
							!interface_exists($namespace) &&
414
							!trait_exists($namespace)) {
415
							class_alias($phpNamespace, $namespace);
416
						}
417
						return;
418
					}
419
				}
420
			}
421
		} elseif (($path = self::getPathOfNamespace($namespace, self::CLASS_FILE_EXT)) !== null) {
422
			$className = substr($namespace, $pos + 1);
423
			if ($className === '*') {  // a directory
424
				self::$_usings[substr($namespace, 0, $pos)] = $path;
425
			} else {  // a file
426
				if (class_exists($className, false) ||
427
					interface_exists($className, false) ||
428
					trait_exists($className, false)) {
429
					return;
430
				}
431
432
				if (file_exists($path)) {
433
					include_once($path);
434
					if (!class_exists($className, false) &&
435
						!interface_exists($className, false) &&
436
						!trait_exists($className, false)) {
437
						class_alias($namespace, $className);
438
					}
439
				}
440
			}
441
		}
442
	}
443
444
	/**
445
	 * This conforms PHP's Magic Methods to PHP's Visibility standards for public,
446
	 * protected, and private properties, methods, and constants. This method checks
447
	 * if the object calling your method can access your object's property, method,
448
	 * or constant based upon the defined visibility.  External objects can only access
449
	 * public properties, methods, and constants.  When calling the self, private
450
	 * properties, methods, and constants are allowed to be accessed by the same class.
451
	 * @param object|string $object_or_class The object to check for the method within
452
	 *   and for visibility to the calling object.
453
	 * @param string $method
454
	 * @return bool Does the method exist and is publicly callable.
455
	 * @since 4.3.0
456
	 */
457
	public static function method_visible($object_or_class, string $method): bool
458
	{
459
		if (method_exists($object_or_class, $method)) {
460
			$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 3);
461
			$reflection = new \ReflectionMethod($object_or_class, $method);
462
			if (empty($trace[2]) || empty($trace[1]['object']) || empty($trace[2]['object']) || $trace[1]['object'] !== $trace[2]['object']) {
463
				return $reflection->isPublic();
464
			} elseif ($reflection->isPrivate()) {
465
				return $trace[2]['class'] === $reflection->class;
466
			}
467
			return true;
468
		}
469
		return false;
470
	}
471
472
	/**
473
	 * This method return the object that is calling your method.
474
	 * @return ?object The parent object calling your code block.
475
	 * @since 4.3.0
476
	 */
477
	public static function callingObject(): ?object
478
	{
479
		$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 3);
480
		if (!empty($trace[2]) && !empty($trace[2]['object'])) {
481
			return $trace[2]['object'];
482
		}
483
		return null;
484
	}
485
486
	/**
487
	 * This checks if object calling your object method is the same object.  In effect,
488
	 * this signifies if self, parents, and children have visibility to "protected"
489
	 * properties, methods, and constants.
490
	 * @return bool Does the method exist and is publicly callable.
491
	 * @since 4.3.0
492
	 */
493
	public static function isCallingSelf(): bool
494
	{
495
		$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 3);
496
		return isset($trace[2]) && isset($trace[1]['object']) && isset($trace[2]['object']) && $trace[1]['object'] === $trace[2]['object'];
497
	}
498
499
	/**
500
	 * This checks if object calling your object method is the same object and same class.
501
	 * In effect, this allows only the self to have visibility to "private" properties,
502
	 * methods, and constants.
503
	 * @return bool Does the method exist and is publicly callable.
504
	 * @since 4.3.0
505
	 */
506
	public static function isCallingSelfClass(): bool
507
	{
508
		$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 3);
509
		return isset($trace[2]) && isset($trace[1]['object']) && isset($trace[2]['object']) && $trace[1]['object'] === $trace[2]['object'] && $trace[1]['class'] === $trace[2]['class'];
510
	}
511
512
	/**
513
	 * Translates a namespace into a file path.
514
	 * The first segment of the namespace is considered as a path alias
515
	 * which is replaced with the actual path. The rest segments are
516
	 * subdirectory names appended to the aliased path.
517
	 * If the namespace ends with an asterisk '*', it represents a directory;
518
	 * Otherwise it represents a file whose extension name is specified by the second parameter (defaults to empty).
519
	 * Note, this method does not ensure the existence of the resulting file path.
520
	 * @param string $namespace namespace
521
	 * @param string $ext extension to be appended if the namespace refers to a file
522
	 * @return null|string file path corresponding to the namespace, null if namespace is invalid
523
	 */
524
	public static function getPathOfNamespace($namespace, $ext = ''): ?string
525
	{
526
		$namespace = static::prado3NamespaceToPhpNamespace($namespace);
527
528
		if (self::CLASS_FILE_EXT === $ext || empty($ext)) {
529
			if (isset(self::$_usings[$namespace])) {
530
				return self::$_usings[$namespace];
531
			}
532
533
			if (isset(self::$_aliases[$namespace])) {
534
				return self::$_aliases[$namespace];
535
			}
536
		}
537
538
		$segs = explode('\\', $namespace);
539
		$alias = array_shift($segs);
540
541
		if (null !== ($file = array_pop($segs)) && null !== ($root = self::getPathOfAlias($alias))) {
542
			return rtrim($root . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $segs), '/\\') . (($file === '*') ? '' : DIRECTORY_SEPARATOR . $file . $ext);
543
		}
544
545
		return null;
546
	}
547
548
	/**
549
	 * @param string $alias alias to the path
550
	 * @return null|string the path corresponding to the alias, null if alias not defined.
551
	 */
552
	public static function getPathOfAlias($alias): ?string
553
	{
554
		return self::$_aliases[$alias] ?? null;
555
	}
556
557
	/**
558
	 * @return array<string, string> list of path aliases
559
	 */
560
	protected static function getPathAliases(): array
561
	{
562
		return self::$_aliases;
563
	}
564
565
	/**
566
	 * @param string $alias alias to the path
567
	 * @param string $path the path corresponding to the alias
568
	 * @throws TInvalidOperationException $alias if the alias is already defined
569
	 * @throws TInvalidDataValueException $path if the path is not a valid file path
570
	 */
571
	public static function setPathOfAlias($alias, $path): void
572
	{
573
		if (isset(self::$_aliases[$alias]) && !defined('PRADO_TEST_RUN')) {
574
			throw new TInvalidOperationException('prado_alias_redefined', $alias);
575
		} elseif (($rp = realpath($path)) !== false && is_dir($rp)) {
576
			if (strpos($alias, '.') === false) {
577
				self::$_aliases[$alias] = $rp;
578
			} else {
579
				throw new TInvalidDataValueException('prado_aliasname_invalid', $alias);
580
			}
581
		} else {
582
			throw new TInvalidDataValueException('prado_alias_invalid', $alias, $path);
583
		}
584
	}
585
586
	/**
587
	 * Fatal error handler.
588
	 * This method displays an error message together with the current call stack.
589
	 * The application will exit after calling this method.
590
	 * @param string $msg error message
591
	 */
592
	public static function fatalError($msg): void
593
	{
594
		echo '<h1>Fatal Error</h1>';
595
		echo '<p>' . $msg . '</p>';
596
		if (!function_exists('debug_backtrace')) {
597
			return;
598
		}
599
		echo '<h2>Debug Backtrace</h2>';
600
		echo '<pre>';
601
		$index = -1;
602
		foreach (debug_backtrace() as $t) {
603
			$index++;
604
			if ($index == 0) {  // hide the backtrace of this function
605
				continue;
606
			}
607
			echo '#' . $index . ' ';
608
			if (isset($t['file'])) {
609
				echo basename($t['file']) . ':' . $t['line'];
610
			} else {
611
				echo '<PHP inner-code>';
612
			}
613
			echo ' -- ';
614
			if (isset($t['class'])) {
615
				echo $t['class'] . $t['type'];
616
			}
617
			echo $t['function'] . '(';
618
			if (isset($t['args']) && count($t['args']) > 0) {
619
				$count = 0;
620
				foreach ($t['args'] as $item) {
621
					if (is_string($item)) {
622
						$str = htmlentities(str_replace("\r\n", "", $item), ENT_QUOTES);
623
						if (strlen($item) > 70) {
624
							echo "'" . substr($str, 0, 70) . "...'";
625
						} else {
626
							echo "'" . $str . "'";
627
						}
628
					} elseif (is_int($item) || is_float($item)) {
629
						echo $item;
630
					} elseif (is_object($item)) {
631
						echo $item::class;
632
					} elseif (is_array($item)) {
633
						echo 'array(' . count($item) . ')';
634
					} elseif (is_bool($item)) {
635
						echo $item ? 'true' : 'false';
636
					} elseif ($item === null) {
637
						echo 'NULL';
638
					} elseif (is_resource($item)) {
639
						echo get_resource_type($item);
640
					}
641
					$count++;
642
					if (count($t['args']) > $count) {
643
						echo ', ';
644
					}
645
				}
646
			}
647
			echo ")\n";
648
		}
649
		echo '</pre>';
650
		exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
651
	}
652
653
	/**
654
	 * Returns a list of user preferred languages.
655
	 * The languages are returned as an array. Each array element
656
	 * represents a single language preference. The languages are ordered
657
	 * according to user preferences. The first language is the most preferred.
658
	 * @return array<string> list of user preferred languages.
659
	 */
660
	public static function getUserLanguages(): array
661
	{
662
		static $languages = null;
663
		if ($languages === null) {
664
			if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
665
				$languages[0] = 'en';
666
			} else {
667
				$languages = [];
668
				foreach (explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $language) {
669
					$array = explode(';q=', trim($language));
670
					$languages[trim($array[0])] = isset($array[1]) ? (float) $array[1] : 1.0;
671
				}
672
				arsort($languages);
673
				$languages = array_keys($languages);
674
				if (count($languages) == 0) {
675
					$languages[0] = 'en';
676
				}
677
			}
678
		}
679
		return $languages;
680
	}
681
682
	/**
683
	 * Returns the most preferred language by the client user.
684
	 * @return string the most preferred language by the client user, defaults to English.
685
	 */
686
	public static function getPreferredLanguage(): string
687
	{
688
		static $language = null;
689
		if ($language === null) {
690
			$langs = Prado::getUserLanguages();
691
			$lang = explode('-', $langs[0]);
692
			if (empty($lang[0]) || !ctype_alpha($lang[0])) {
693
				$language = 'en';
694
			} else {
695
				$language = $lang[0];
696
			}
697
		}
698
		return $language;
699
	}
700
701
	/**
702
	 * Writes a profile begin message.
703
	 * @param string $token The profile token.
704
	 * @param ?string $category category of the message, default null for the calling class.
705
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message, default null
706
	 */
707
	public static function profileBegin($token, $category = null, $ctl = null): void
708
	{
709
		if ($category === null) {
710
			$category = self::callingObject()::class;
711
		}
712
		self::log($token, TLogger::PROFILE_BEGIN, $category, $ctl);
713
	}
714
715
	/**
716
	 * Writes a profile end message.
717
	 * @param string $token The profile token.
718
	 * @param ?string $category category of the message, default null for the calling class.
719
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message, default null
720
	 * @return ?float The time spent since the PROFILE_BEGIN of the same token.
721
	 */
722
	public static function profileEnd($token, $category = null, $ctl = null): ?float
723
	{
724
		if ($category === null) {
725
			$category = self::callingObject()::class;
726
		}
727
		return self::log($token, TLogger::PROFILE_END, $category, $ctl);
728
	}
729
730
	/**
731
	 * Writes a log message.
732
	 * This method wraps {@see log()} by checking the application mode.
733
	 * When the application is in Debug mode, debug backtrace information is appended
734
	 * to the message and the message is logged at DEBUG level.
735
	 * When the application is in Performance mode, this method does nothing.
736
	 * Otherwise, the message is logged at INFO level.
737
	 * @param string $msg message to be logged
738
	 * @param ?string $category category of the message, null fills is with the calling class
739
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message
740
	 * @see log, getLogger
741
	 */
742
	public static function trace($msg, $category = null, $ctl = null): void
743
	{
744
		if (self::$_application && self::$_application->getMode() === TApplicationMode::Performance) {
745
			return;
746
		}
747
		if ($category === null) {
748
			$category = self::callingObject()::class;
749
		}
750
		if (!self::$_application || self::$_application->getMode() === TApplicationMode::Debug) {
751
			$trace = debug_backtrace();
752
			if (isset($trace[0]['file']) && isset($trace[0]['line'])) {
753
				$msg .= " (line {$trace[0]['line']}, {$trace[0]['file']})";
754
			}
755
			$level = TLogger::DEBUG;
756
		} else {
757
			$level = TLogger::INFO;
758
		}
759
		self::log($msg, $level, $category, $ctl);
760
	}
761
762
	/**
763
	 * Writes a debug log message.
764
	 * @param string $msg message to be logged
765
	 * @param ?string $category category of the message, default null for the calling class.
766
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message, default null
767
	 */
768
	public static function debug($msg, $category = null, $ctl = null): void
769
	{
770
		if (self::$_application && self::$_application->getMode() !== TApplicationMode::Debug) {
771
			return;
772
		}
773
		if ($category === null) {
774
			$category = self::callingObject()::class;
775
		}
776
		$trace = debug_backtrace();
777
		if (isset($trace[0]['file']) && isset($trace[0]['line'])) {
778
			$msg .= " (line {$trace[0]['line']}, {$trace[0]['file']})";
779
		}
780
		self::log($msg, TLogger::DEBUG, $category, $ctl);
781
	}
782
783
	/**
784
	 * Writes an info log message.
785
	 * @param string $msg message to be logged
786
	 * @param ?string $category category of the message, default null for the calling class.
787
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message, default null
788
	 */
789
	public static function info($msg, $category = null, $ctl = null): void
790
	{
791
		if ($category === null) {
792
			$category = self::callingObject()::class;
793
		}
794
		self::log($msg, TLogger::INFO, $category, $ctl);
795
	}
796
797
	/**
798
	 * Writes a notice log message.
799
	 * @param string $msg message to be logged
800
	 * @param ?string $category category of the message, default null for the calling class.
801
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message, default null
802
	 */
803
	public static function notice($msg, $category = null, $ctl = null): void
804
	{
805
		if ($category === null) {
806
			$category = self::callingObject()::class;
807
		}
808
		self::log($msg, TLogger::NOTICE, $category, $ctl);
809
	}
810
811
	/**
812
	 * Writes a warning log message.
813
	 * @param string $msg message to be logged
814
	 * @param ?string $category category of the message, default null for the calling class.
815
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message, default null
816
	 */
817
	public static function warning($msg, $category = null, $ctl = null): void
818
	{
819
		if ($category === null) {
820
			$category = self::callingObject()::class;
821
		}
822
		self::log($msg, TLogger::WARNING, $category, $ctl);
823
	}
824
825
	/**
826
	 * Writes an error log message.
827
	 * @param string $msg message to be logged
828
	 * @param ?string $category category of the message, default null for the calling class.
829
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message, default null
830
	 */
831
	public static function error($msg, $category = null, $ctl = null): void
832
	{
833
		if ($category === null) {
834
			$category = self::callingObject()::class;
835
		}
836
		self::log($msg, TLogger::ERROR, $category, $ctl);
837
	}
838
839
	/**
840
	 * Writes an alert log message.
841
	 * @param string $msg message to be logged
842
	 * @param ?string $category category of the message, default null for the calling class.
843
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message, default null
844
	 */
845
	public static function alert($msg, $category = null, $ctl = null): void
846
	{
847
		if ($category === null) {
848
			$category = self::callingObject()::class;
849
		}
850
		self::log($msg, TLogger::ALERT, $category, $ctl);
851
	}
852
853
	/**
854
	 * Writes a fatal log message.
855
	 * @param string $msg message to be logged
856
	 * @param ?string $category category of the message, default null for the calling class.
857
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message, default null
858
	 */
859
	public static function fatal($msg, $category = null, $ctl = null): void
860
	{
861
		if ($category === null) {
862
			$category = self::callingObject()::class;
863
		}
864
		self::log($msg, TLogger::FATAL, $category, $ctl);
865
	}
866
867
	/**
868
	 * Logs a message.
869
	 * Messages logged by this method may be retrieved via {@see \Prado\Util\TLogger::getLogs}
870
	 * and may be recorded in different media, such as file, email, database, using
871
	 * {@see \Prado\Util\TLogRouter}.
872
	 * @param string $msg message to be logged
873
	 * @param int $level level of the message. Valid values include
874
	 * TLogger::DEBUG, TLogger::INFO, TLogger::NOTICE, TLogger::WARNING,
875
	 * TLogger::ERROR, TLogger::ALERT, TLogger::FATAL.
876
	 * @param string $category category of the message
877
	 * @param null|\Prado\Web\UI\TControl|string $ctl control of the message
878
	 * @return ?float When the $level is PROFILE_END, this returns the delta time
879
	 *   since the PROFILE_BEGIN of the same $msg.
880
	 */
881
	public static function log($msg, $level = TLogger::INFO, $category = 'Uncategorized', $ctl = null): ?float
882
	{
883
		if (self::$_logger === null) {
884
			self::$_logger = new TLogger();
885
		}
886
		if ($category === null) {
0 ignored issues
show
introduced by
The condition $category === null is always false.
Loading history...
887
			$category = self::callingObject()::class;
888
		}
889
		return self::$_logger->log($msg, $level, $category, $ctl);
890
	}
891
892
	/**
893
	 * @return TLogger message logger
894
	 */
895
	public static function getLogger(): TLogger
896
	{
897
		if (self::$_logger === null) {
898
			self::$_logger = new TLogger();
899
		}
900
		return self::$_logger;
901
	}
902
903
	/**
904
	 * Converts a variable into a string representation.
905
	 * This method achieves the similar functionality as var_dump and print_r
906
	 * but is more robust when handling complex objects such as PRADO controls.
907
	 * @param mixed $var variable to be dumped
908
	 * @param int $depth maximum depth that the dumper should go into the variable. Defaults to 10.
909
	 * @param bool $highlight whether to syntax highlight the output. Defaults to false.
910
	 * @return string the string representation of the variable
911
	 */
912
	public static function varDump($var, $depth = 10, $highlight = false): string
913
	{
914
		return TVarDumper::dump($var, $depth, $highlight);
915
	}
916
917
	/**
918
	 * Localize a text to the locale/culture specified in the globalization handler.
919
	 * @param string $text text to be localized.
920
	 * @param array<string, string> $parameters a set of parameters to substitute.
921
	 * @param string $catalogue a different catalogue to find the localize text.
922
	 * @param string $charset the input AND output charset.
923
	 * @return string localized text.
924
	 * @see TTranslate::formatter()
925
	 * @see TTranslate::init()
926
	 */
927
	public static function localize($text, $parameters = [], $catalogue = null, $charset = null): string
928
	{
929
		$params = [];
930
		foreach ($parameters as $key => $value) {
931
			$params['{' . $key . '}'] = $value;
932
		}
933
934
		// no translation handler provided
935
		if (self::$_application === null
936
			|| ($app = self::$_application->getGlobalization(false)) === null
937
			|| ($config = $app->getTranslationConfiguration()) === null) {
938
			return strtr($text, $params);
939
		}
940
941
		if ($catalogue === null) {
942
			$catalogue = $config['catalogue'] ?? 'messages';
943
		}
944
945
		Translation::init($catalogue);
946
947
		//globalization charset
948
		if (empty($charset)) {
949
			$charset = $app->getCharset();
950
		}
951
952
		//default charset
953
		if (empty($charset)) {
954
			$charset = $app->getDefaultCharset();
955
		}
956
957
		return Translation::formatter($catalogue)->format($text, $params, $catalogue, $charset);
958
	}
959
}
960
961
/**
962
 * Initialize Prado autoloader and error handler
963
 */
964
Prado::init();
965
966
/**
967
 * Defines Prado in global namespace
968
 */
969
class_alias(\Prado\Prado::class, 'Prado');
970