Passed
Push — master ( 91641e...97f11f )
by Fabio
07:24
created

TApplication::singleton()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
/**
3
 * TApplication class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado;
11
12
use Prado\Exceptions\TErrorHandler;
13
use Prado\Exceptions\TExitException;
14
use Prado\Exceptions\THttpException;
15
use Prado\Exceptions\TConfigurationException;
16
use Prado\I18N\TGlobalization;
17
use Prado\Security\TSecurityManager;
18
use Prado\Web\TAssetManager;
19
use Prado\Web\THttpRequest;
20
use Prado\Web\THttpResponse;
21
use Prado\Web\THttpSession;
22
use Prado\Util\TLogger;
23
use Prado\Web\UI\TTemplateManager;
24
use Prado\Web\UI\TThemeManager;
25
26
/**
27
 * TApplication class.
28
 *
29
 * TApplication coordinates modules and services, and serves as a configuration
30
 * context for all Prado components.
31
 *
32
 * TApplication uses a configuration file to specify the settings of
33
 * the application, the modules, the services, the parameters, and so on.
34
 *
35
 * TApplication adopts a modular structure. A TApplication instance is a composition
36
 * of multiple modules. A module is an instance of class implementing
37
 * {@see \Prado\IModule} interface. Each module accomplishes certain functionalities
38
 * that are shared by all Prado components in an application.
39
 * There are default modules, composer modules, and user-defined modules. The latter
40
 * offers extreme flexibility of extending TApplication in a plug-and-play fashion.
41
 * Modules cooperate with each other to serve a user request by following
42
 * a sequence of lifecycles predefined in TApplication.
43
 *
44
 * TApplicationConfiguration loads the composer.json for each installed composer extension
45
 * and checks the extra field for a "bootstrap" class for the package.
46
 * Packages can be specified as a configuration module (without a class) to load the
47
 * composer extension module.  The ID of the module is the name of the package.
48
 *
49
 * TApplication has four modes that can be changed by setting {@see setMode Mode}
50
 * property (in the application configuration file).
51
 * - <b>Off</b> mode will prevent the application from serving user requests.
52
 * - <b>Debug</b> mode is mainly used during application development. It ensures
53
 *   the cache is always up-to-date if caching is enabled. It also allows
54
 *   exceptions are displayed with rich context information if they occur.
55
 * - <b>Normal</b> mode is mainly used during production stage. Exception information
56
 *   will only be recorded in system error logs. The cache is ensured to be
57
 *   up-to-date if it is enabled.
58
 * - <b>Performance</b> mode is similar to <b>Normal</b> mode except that it
59
 *   does not ensure the cache is up-to-date.
60
 *
61
 * TApplication dispatches each user request to a particular service which
62
 * finishes the actual work for the request with the aid from the application
63
 * modules.
64
 *
65
 * TApplication maintains a lifecycle with the following stages:
66
 * - [construct] : construction of the application instance
67
 * - [initApplication] : load application configuration and instantiate modules and the requested service
68
 * - onInitComplete : this event happens right after after module and service initialization. This event is particularly useful for CLI/Shell applications
69
 * - onBeginRequest : this event happens right after application initialization
70
 * - onAuthentication : this event happens when authentication is needed for the current request
71
 * - onAuthenticationComplete : this event happens right after the authentication is done for the current request
72
 * - onAuthorization : this event happens when authorization is needed for the current request
73
 * - onAuthorizationComplete : this event happens right after the authorization is done for the current request
74
 * - onLoadState : this event happens when application state needs to be loaded
75
 * - onLoadStateComplete : this event happens right after the application state is loaded
76
 * - onPreRunService : this event happens right before the requested service is to run
77
 * - runService : the requested service runs
78
 * - onSaveState : this event happens when application needs to save its state
79
 * - onSaveStateComplete : this event happens right after the application saves its state
80
 * - onPreFlushOutput : this event happens right before the application flushes output to client side.
81
 * - flushOutput : the application flushes output to client side.
82
 * - onEndRequest : this is the last stage a request is being completed
83
 * - [destruct] : destruction of the application instance
84
 * Modules and services can attach their methods to one or several of the above
85
 * events and do appropriate processing when the events are raised. By this way,
86
 * the application is able to coordinate the activities of modules and services
87
 * in the above order. To terminate an application before the whole lifecycle
88
 * completes, call {@see completeRequest}.
89
 *
90
 * Examples:
91
 * - Create and run a Prado application:
92
 * ```php
93
 * $application=new TApplication($configFile);
94
 * $application->run();
95
 * ```
96
 *
97
 * @author Qiang Xue <[email protected]>
98
 * @since 3.0
99
 */
100
class TApplication extends \Prado\TComponent implements ISingleton
101
{
102
	/**
103
	 * Page service ID
104
	 */
105
	public const PAGE_SERVICE_ID = 'page';
106
	/**
107
	 * Application configuration file name
108
	 */
109
	public const CONFIG_FILE_XML = 'application.xml';
110
	/**
111
	 * File extension for external config files
112
	 */
113
	public const CONFIG_FILE_EXT_XML = '.xml';
114
	/**
115
	 * Configuration file type, application.xml and config.xml
116
	 */
117
	public const CONFIG_TYPE_XML = 'xml';
118
	/**
119
	 * Application configuration file name
120
	 */
121
	public const CONFIG_FILE_PHP = 'application.php';
122
	/**
123
	 * File extension for external config files
124
	 */
125
	public const CONFIG_FILE_EXT_PHP = '.php';
126
	/**
127
	 * Configuration file type, application.php and config.php
128
	 */
129
	public const CONFIG_TYPE_PHP = 'php';
130
	/**
131
	 * Runtime directory name
132
	 */
133
	public const RUNTIME_PATH = 'runtime';
134
	/**
135
	 * Config cache file
136
	 */
137
	public const CONFIGCACHE_FILE = 'config.cache';
138
	/**
139
	 * Global data file
140
	 */
141
	public const GLOBAL_FILE = 'global.cache';
142
143
	/**
144
	 * @var array list of events that define application lifecycles
145
	 */
146
	private static $_steps = [
147
		'onBeginRequest',
148
		'onLoadState',
149
		'onLoadStateComplete',
150
		'onAuthentication',
151
		'onAuthenticationComplete',
152
		'onAuthorization',
153
		'onAuthorizationComplete',
154
		'onPreRunService',
155
		'runService',
156
		'onSaveState',
157
		'onSaveStateComplete',
158
		'onPreFlushOutput',
159
		'flushOutput',
160
	];
161
162
	/**
163
	 * @var string application ID
164
	 */
165
	private $_id;
166
	/**
167
	 * @var string unique application ID
168
	 */
169
	private $_uniqueID;
170
	/**
171
	 * @var bool whether the request is completed
172
	 */
173
	private $_requestCompleted = false;
174
	/**
175
	 * @var int application state
176
	 */
177
	private $_step;
178
	/**
179
	 * @var array available services and their configurations indexed by service IDs
180
	 */
181
	private $_services;
182
	/**
183
	 * @var IService current service instance
184
	 */
185
	private $_service;
186
	/**
187
	 * @var array list of loaded application modules
188
	 */
189
	private $_modules = [];
190
	/**
191
	 * @var array list of application modules yet to be loaded
192
	 */
193
	private $_lazyModules = [];
194
	/**
195
	 * @var \Prado\Collections\TMap list of application parameters
196
	 */
197
	private $_parameters;
198
	/**
199
	 * @var string configuration file
200
	 */
201
	private $_configFile;
202
	/**
203
	 * @var string configuration file extension
204
	 */
205
	private $_configFileExt;
206
	/**
207
	 * @var string configuration type
208
	 */
209
	private $_configType;
210
	/**
211
	 * @var string application base path
212
	 */
213
	private $_basePath;
214
	/**
215
	 * @var string directory storing application state
216
	 */
217
	private $_runtimePath;
218
	/**
219
	 * @var bool if any global state is changed during the current request
220
	 */
221
	private $_stateChanged = false;
222
	/**
223
	 * @var array global variables (persistent across sessions, requests)
224
	 */
225
	private $_globals = [];
226
	/**
227
	 * @var string cache file
228
	 */
229
	private $_cacheFile;
230
	/**
231
	 * @var TErrorHandler error handler module
232
	 */
233
	private $_errorHandler;
234
	/**
235
	 * @var THttpRequest request module
236
	 */
237
	private $_request;
238
	/**
239
	 * @var THttpResponse response module
240
	 */
241
	private $_response;
242
	/**
243
	 * @var THttpSession session module, could be null
244
	 */
245
	private $_session;
246
	/**
247
	 * @var \Prado\Caching\ICache cache module, could be null
248
	 */
249
	private $_cache;
250
	/**
251
	 * @var IStatePersister application state persister
252
	 */
253
	private $_statePersister;
254
	/**
255
	 * @var \Prado\Security\IUser user instance, could be null
256
	 */
257
	private $_user;
258
	/**
259
	 * @var TGlobalization module, could be null
260
	 */
261
	private $_globalization;
262
	/**
263
	 * @var TSecurityManager security manager module
264
	 */
265
	private $_security;
266
	/**
267
	 * @var TAssetManager asset manager module
268
	 */
269
	private $_assetManager;
270
	/**
271
	 * @var \Prado\Web\UI\TTemplateManager template manager module
272
	 */
273
	private $_templateManager;
274
	/**
275
	 * @var \Prado\Web\UI\TThemeManager theme manager module
276
	 */
277
	private $_themeManager;
278
	/**
279
	 * @var \Prado\Security\TAuthorizationRuleCollection collection of authorization rules
280
	 */
281
	private $_authRules;
282
	/**
283
	 * @var string|TApplicationMode application mode
284
	 */
285
	private $_mode = TApplicationMode::Debug;
286
287
	/**
288
	 * @var string Customizable page service ID
289
	 */
290
	private $_pageServiceID = self::PAGE_SERVICE_ID;
291
292
	/**
293
	 * Constructor.
294
	 * Sets application base path and initializes the application singleton.
295
	 * Application base path refers to the root directory storing application
296 55
	 * data and code not directly accessible by Web users.
297
	 * By default, the base path is assumed to be the <b>protected</b>
298
	 * directory under the directory containing the current running script.
299 55
	 * @param string $basePath application base path or configuration file path.
300 55
	 *        If the parameter is a file, it is assumed to be the application
301 55
	 *        configuration file, and the directory containing the file is treated
302
	 *        as the application base path.
303 55
	 *        If it is a directory, it is assumed to be the application base path,
304 26
	 *        and within that directory, a file named <b>application.xml</b>
305
	 *        will be looked for. If found, the file is considered as the application
306
	 *        configuration file.
307
	 * @param bool $cacheConfig whether to cache application configuration. Defaults to true.
308 55
	 * @param string $configType configuration type. Defaults to CONFIG_TYPE_XML.
309 55
	 * @throws TConfigurationException if configuration file cannot be read or the runtime path is invalid.
310 55
	 */
311
	public function __construct($basePath = 'protected', $cacheConfig = true, $configType = self::CONFIG_TYPE_XML)
312 55
	{
313 55
		// register application as a singleton
314
		Prado::setApplication($this);
315
		$this->setConfigurationType($configType);
316
		$this->resolvePaths($basePath);
317
318
		if ($cacheConfig) {
319
			$this->_cacheFile = $this->_runtimePath . DIRECTORY_SEPARATOR . self::CONFIGCACHE_FILE;
320
		}
321
322
		// generates unique ID by hashing the runtime path
323
		$this->_uniqueID = md5($this->_runtimePath);
324
		$this->_parameters = new \Prado\Collections\TMap();
325 55
		$this->_services = [$this->getPageServiceID() => ['TPageService', [], null]];
326
327
		Prado::setPathOfAlias('Application', $this->_basePath);
328 55
		parent::__construct();
329
	}
330
331 55
	/**
332
	 * Returns the current Prado application.  This enables application behaviors to
333 55
	 * be used for undefined static function calls via {@see \Prado\TComponent::__callStatic}.
334
	 * @param bool $create This is ignored and returns Prado::getApplication().
335
	 * @return ?object The singleton instance of the class.
336
	 * @since 4.2.3
337 55
	 */
338
	public static function singleton(bool $create = true): ?object
339
	{
340
		return Prado::getApplication();
341 55
	}
342 55
343 55
	/**
344
	 * Resolves application-relevant paths.
345
	 * This method is invoked by the application constructor
346
	 * to determine the application configuration file,
347
	 * application root path and the runtime path.
348
	 * @param string $basePath the application root path or the application configuration file
349
	 * @see setBasePath
350
	 * @see setRuntimePath
351
	 * @see setConfigurationFile
352
	 */
353 55
	protected function resolvePaths($basePath)
354 55
	{
355
		// determine configuration path and file
356
		if (empty($errValue = $basePath) || ($basePath = realpath($basePath)) === false) {
357
			throw new TConfigurationException('application_basepath_invalid', $errValue);
358 55
		}
359
		if (is_dir($basePath) && is_file($basePath . DIRECTORY_SEPARATOR . $this->getConfigurationFileName())) {
360
			$configFile = $basePath . DIRECTORY_SEPARATOR . $this->getConfigurationFileName();
361
		} elseif (is_file($basePath)) {
362
			$configFile = $basePath;
363
			$basePath = dirname($configFile);
364
		} else {
365
			$configFile = null;
366
		}
367
368
		// determine runtime path
369
		$runtimePath = $basePath . DIRECTORY_SEPARATOR . self::RUNTIME_PATH;
370
		if (is_writable($runtimePath)) {
371
			if ($configFile !== null) {
372
				$runtimePath .= DIRECTORY_SEPARATOR . basename($configFile) . '-' . Prado::getVersion();
373
				if (!is_dir($runtimePath)) {
374
					if (@mkdir($runtimePath) === false) {
375
						throw new TConfigurationException('application_runtimepath_failed', $runtimePath);
376
					}
377
					@chmod($runtimePath, Prado::getDefaultDirPermissions()); //make it deletable
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

377
					/** @scrutinizer ignore-unhandled */ @chmod($runtimePath, Prado::getDefaultDirPermissions()); //make it deletable

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
378
				}
379
				$this->setConfigurationFile($configFile);
380
			}
381
			$this->setBasePath($basePath);
382
			$this->setRuntimePath($runtimePath);
383
		} else {
384
			throw new TConfigurationException('application_runtimepath_invalid', $runtimePath);
385
		}
386
	}
387
388
	/**
389
	 * Executes the lifecycles of the application.
390
	 * This is the main entry function that leads to the running of the whole
391
	 * Prado application.
392
	 */
393
	public function run()
394
	{
395
		try {
396
			$this->initApplication();
397
			$n = count(self::$_steps);
398
			$this->_step = 0;
399
			$this->_requestCompleted = false;
400
			while ($this->_step < $n) {
401
				if ($this->_mode === TApplicationMode::Off) {
402
					throw new THttpException(503, 'application_unavailable');
403
				}
404
				if ($this->_requestCompleted) {
405
					break;
406
				}
407
				$method = self::$_steps[$this->_step];
408
				Prado::trace("Executing $method()", TApplication::class);
409
				$this->$method();
410
				$this->_step++;
411
			}
412
		} catch (TExitException $e) {
413
			$this->onEndRequest();
414
			exit($e->getExitCode());
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...
415
		} catch (\Exception $e) {
416 3
			$this->onError($e);
417
		}
418 3
		$this->onEndRequest();
419
	}
420
421
	/**
422
	 * Completes current request processing.
423
	 * This method can be used to exit the application lifecycles after finishing
424
	 * the current cycle.
425
	 */
426
	public function completeRequest()
427
	{
428
		$this->_requestCompleted = true;
429
	}
430
431 3
	/**
432
	 * @return bool whether the current request is processed.
433 3
	 */
434 3
	public function getRequestCompleted()
435
	{
436
		return $this->_requestCompleted;
437 3
	}
438
439 3
	/**
440 3
	 * Returns a global value.
441
	 *
442 3
	 * A global value is one that is persistent across users sessions and requests.
443
	 * @param string $key the name of the value to be returned
444
	 * @param mixed $defaultValue the default value. If $key is not found, $defaultValue will be returned
445
	 * @return mixed the global value corresponding to $key
446
	 */
447
	public function getGlobalState($key, $defaultValue = null)
448
	{
449
		return $this->_globals[$key] ?? $defaultValue;
450
	}
451
452
	/**
453
	 * Sets a global value.
454
	 *
455
	 * A global value is one that is persistent across users sessions and requests.
456
	 * Make sure that the value is serializable and unserializable.
457
	 * @param string $key the name of the value to be set
458
	 * @param mixed $value the global value to be set
459
	 * @param null|mixed $defaultValue the default value. If $key is not found, $defaultValue will be returned
460
	 * @param bool $forceSave wheter to force an immediate GlobalState save. defaults to false
461
	 */
462
	public function setGlobalState($key, $value, $defaultValue = null, $forceSave = false)
463
	{
464
		$this->_stateChanged = true;
465
		if ($value === $defaultValue) {
466
			unset($this->_globals[$key]);
467
		} else {
468
			$this->_globals[$key] = $value;
469
		}
470
		if ($forceSave) {
471 3
			$this->saveGlobals();
472
		}
473 3
	}
474 3
475 3
	/**
476
	 * Clears a global value.
477 3
	 *
478
	 * The value cleared will no longer be available in this request and the following requests.
479
	 * @param string $key the name of the value to be cleared
480
	 */
481
	public function clearGlobalState($key)
482 3
	{
483
		$this->_stateChanged = true;
484 3
		unset($this->_globals[$key]);
485
	}
486
487
	/**
488
	 * Loads global values from persistent storage.
489
	 * This method is invoked when {@see onLoadState OnLoadState} event is raised.
490
	 * After this method, values that are stored in previous requests become
491
	 * available to the current request via {@see getGlobalState}.
492
	 */
493
	protected function loadGlobals()
494
	{
495
		$this->_globals = $this->getApplicationStatePersister()->load();
496
	}
497
498 55
	/**
499
	 * Saves global values into persistent storage.
500 55
	 * This method is invoked when {@see onSaveState OnSaveState} event is raised.
501
	 */
502
	protected function saveGlobals()
503
	{
504
		if ($this->_stateChanged) {
505
			$this->_stateChanged = false;
506
			$this->getApplicationStatePersister()->save($this->_globals);
507
		}
508
	}
509
510
	/**
511
	 * @return string application ID
512
	 */
513
	public function getID()
514 7
	{
515
		return $this->_id;
516 7
	}
517
518
	/**
519
	 * @param string $value application ID
520
	 */
521
	public function setID($value)
522 32
	{
523
		$this->_id = $value;
524 32
	}
525
526
	/**
527
	 * @return string page service ID
528
	 */
529
	public function getPageServiceID()
530
	{
531
		return $this->_pageServiceID;
532
	}
533
534
	/**
535
	 * @param string $value page service ID
536
	 */
537
	public function setPageServiceID($value)
538
	{
539
		$this->_pageServiceID = $value;
540
	}
541
542
	/**
543
	 * @return string an ID that uniquely identifies this Prado application from the others
544
	 */
545
	public function getUniqueID()
546 55
	{
547
		return $this->_uniqueID;
548 55
	}
549 55
550
	/**
551
	 * @return string|TApplicationMode application mode. Defaults to TApplicationMode::Debug.
552
	 */
553
	public function getMode()
554
	{
555
		return $this->_mode;
556
	}
557
558
	/**
559
	 * @param TApplicationMode $value application mode
560
	 */
561
	public function setMode($value)
562
	{
563
		$this->_mode = TPropertyValue::ensureEnum($value, TApplicationMode::class);
564
	}
565
566
	/**
567
	 * @return string the directory containing the application configuration file (absolute path)
568
	 */
569
	public function getBasePath()
570 9
	{
571
		return $this->_basePath;
572 9
	}
573
574
	/**
575
	 * @param string $value the directory containing the application configuration file
576
	 */
577
	public function setBasePath($value)
578 55
	{
579
		$this->_basePath = $value;
580 55
	}
581 55
582
	/**
583
	 * @return string the application configuration file (absolute path)
584
	 */
585
	public function getConfigurationFile()
586
	{
587
		return $this->_configFile;
588
	}
589
590
	/**
591
	 * @param string $value the application configuration file (absolute path)
592
	 */
593
	public function setConfigurationFile($value)
594
	{
595
		$this->_configFile = $value;
596
	}
597
598
	/**
599
	 * @return string the application configuration file (absolute path)
600
	 */
601
	public function getConfigurationType()
602
	{
603 55
		return $this->_configType;
604
	}
605 55
606 55
	/**
607 2
	 * @param string $value the application configuration type. 'xml' and 'php' are valid values
608 2
	 */
609
	public function setConfigurationType($value)
610
	{
611
		$this->_configType = $value;
612 2
	}
613
614
	/**
615 55
	 * @return string the application configuration type. default is 'xml'
616
	 */
617
	public function getConfigurationFileExt()
618
	{
619
		if ($this->_configFileExt === null) {
620
			switch ($this->_configType) {
621 3
				case TApplication::CONFIG_TYPE_PHP:
622
					$this->_configFileExt = TApplication::CONFIG_FILE_EXT_PHP;
623 3
					break;
624
				default:
625
					$this->_configFileExt = TApplication::CONFIG_FILE_EXT_XML;
626
			}
627
		}
628
		return $this->_configFileExt;
629 55
	}
630
631 55
	/**
632 55
	 * @return string the default configuration file name
633
	 */
634
	public function getConfigurationFileName()
635
	{
636 55
		static $fileName;
637 55
		if ($fileName == null) {
638
			switch ($this->_configType) {
639
				case TApplication::CONFIG_TYPE_PHP:
640
					$fileName = TApplication::CONFIG_FILE_PHP;
641
					break;
642
				default:
643
					$fileName = TApplication::CONFIG_FILE_XML;
644
			}
645
		}
646
		return $fileName;
647
	}
648
649
	/**
650
	 * @return string the directory storing cache data and application-level persistent data. (absolute path)
651
	 */
652
	public function getRuntimePath()
653
	{
654
		return $this->_runtimePath;
655
	}
656
657
	/**
658
	 * @param string $value the directory storing cache data and application-level persistent data. (absolute path)
659
	 */
660
	public function setRuntimePath($value)
661 14
	{
662
		$this->_runtimePath = $value;
663 14
		if ($this->_cacheFile) {
664
			$this->_cacheFile = $this->_runtimePath . DIRECTORY_SEPARATOR . self::CONFIGCACHE_FILE;
665
		}
666 14
		// generates unique ID by hashing the runtime path
667
		$this->_uniqueID = md5($this->_runtimePath);
668 14
	}
669
670
	/**
671
	 * @return TService the currently requested service
672
	 */
673
	public function getService()
674 15
	{
675
		return $this->_service;
676 15
	}
677 2
678
	/**
679
	 * @param IService $value the currently requested service
680
	 */
681 15
	public function setService($value)
682
	{
683
		$this->_service = $value;
684
	}
685
686 15
	/**
687
	 * Adds a module to application.
688
	 * Note, this method does not do module initialization.
689
	 * @param string $id ID of the module
690
	 * @param null|IModule $module module object or null if the module has not been loaded yet
691
	 */
692
	public function setModule($id, IModule $module = null)
693
	{
694
		if (isset($this->_modules[$id])) {
695
			throw new TConfigurationException('application_moduleid_duplicated', $id);
696
		} else {
697
			$this->_modules[$id] = $module;
698
		}
699
	}
700
701
	/**
702
	 * @param mixed $id
703
	 * @return null|TModule the module with the specified ID, null if not found
704
	 */
705
	public function getModule($id)
706
	{
707
		if (!array_key_exists($id, $this->_modules)) {
708
			return null;
709
		}
710
711
		// force loading of a lazy module
712
		if ($this->_modules[$id] === null) {
713 18
			$module = $this->internalLoadModule($id, true);
714
			$module[0]->init($module[1]);
0 ignored issues
show
Bug introduced by
The method init() does not exist on null. ( Ignorable by Annotation )

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

714
			$module[0]->/** @scrutinizer ignore-call */ 
715
               init($module[1]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
715 18
		}
716 1
717 1
		return $this->_modules[$id];
718
	}
719 18
720
	/**
721
	 * Returns a list of application modules indexed by module IDs.
722
	 * Modules that have not been loaded yet are returned as null objects.
723
	 * @return array<TModule> list of loaded application modules, indexed by module IDs
724
	 */
725 42
	public function getModules()
726
	{
727 42
		return $this->_modules;
728 42
	}
729
730
	/**
731
	 * Returns a list of application modules of a specific class.
732
	 * Lazy Loading Modules are not loaded, and are null but have an ID Key.
733 1
	 * When null modules are found, load them with {@see getModule}. eg.
734
	 * ```php
735 1
	 *	foreach (Prado::getApplication()->getModulesByType(\Prado\Caching\ICache::class) as $id => $module) {
736
	 *		$module = (!$module) ? $app->getModule($id) : $module;
737
	 *		...
738
	 *	}
739 1
	 * ```
740
	 * @param string $type class name of the modules to look for.
741
	 * @param bool $strict should the module be the class or can the module be a subclass
742
	 * @return array keys are the ids of the module and values are module of a specific class
743
	 * @since 4.2.0
744
	 */
745 8
	public function getModulesByType($type, $strict = false)
746
	{
747 8
		$m = [];
748 8
		foreach ($this->_modules as $id => $module) {
749
			if ($module === null && isset($this->_lazyModules[$id])) {
750
				[$moduleClass, $initProperties, $configElement] = $this->_lazyModules[$id];
751
				if ($strict ? ($moduleClass === $type) : ($moduleClass instanceof $type)) {
752
					$m[$id] = null;
753
				}
754
			} elseif ($module !== null && ($strict ? ($module::class === $type) : $module->isa($type))) {
755
				$m[$id] = $module;
756
			}
757
		}
758
		return $m;
759
	}
760
761
	/**
762
	 * Returns the list of application parameters.
763
	 * Since the parameters are returned as a {@see \Prado\Collections\TMap} object, you may use
764
	 * the returned result to access, add or remove individual parameters.
765 10
	 * @return \Prado\Collections\TMap the list of application parameters
766
	 */
767 10
	public function getParameters()
768 10
	{
769
		return $this->_parameters;
770
	}
771
772
	/**
773
	 * @return THttpRequest the request module
774
	 */
775
	public function getRequest()
776
	{
777
		if (!$this->_request) {
778
			$this->_request = new \Prado\Web\THttpRequest();
779
			$this->_request->init(null);
780
		}
781
		return $this->_request;
782
	}
783
784
	/**
785
	 * @param THttpRequest $request the request module
786
	 */
787
	public function setRequest(THttpRequest $request)
788
	{
789
		$this->_request = $request;
790
	}
791
792
	/**
793 2
	 * @return THttpResponse the response module
794
	 */
795 2
	public function getResponse()
796 1
	{
797 1
		if (!$this->_response) {
798
			$this->_response = new THttpResponse();
799 2
			$this->_response->init(null);
800
		}
801
		return $this->_response;
802
	}
803
804
	/**
805 9
	 * @param THttpResponse $response the request module
806
	 */
807 9
	public function setResponse(THttpResponse $response)
808 9
	{
809
		$this->_response = $response;
810
	}
811
812
	/**
813 1
	 * @return THttpSession the session module, null if session module is not installed
814
	 */
815 1
	public function getSession()
816
	{
817
		if (!$this->_session) {
818
			$this->_session = new THttpSession();
819 1
			$this->_session->init(null);
820
		}
821
		return $this->_session;
822
	}
823
824
	/**
825 6
	 * @param THttpSession $session the session module
826
	 */
827 6
	public function setSession(THttpSession $session)
828 6
	{
829
		$this->_session = $session;
830
	}
831
832
	/**
833 3
	 * @return TErrorHandler the error handler module
834
	 */
835 3
	public function getErrorHandler()
836 2
	{
837 2
		if (!$this->_errorHandler) {
838
			$this->_errorHandler = new TErrorHandler();
839 3
			$this->_errorHandler->init(null);
840
		}
841
		return $this->_errorHandler;
842
	}
843
844
	/**
845 2
	 * @param TErrorHandler $handler the error handler module
846
	 */
847 2
	public function setErrorHandler(TErrorHandler $handler)
848 2
	{
849
		$this->_errorHandler = $handler;
850
	}
851
852
	/**
853 20
	 * @return TSecurityManager the security manager module
854
	 */
855 20
	public function getSecurityManager()
856
	{
857
		if (!$this->_security) {
858
			$this->_security = new TSecurityManager();
859
			$this->_security->init(null);
860
		}
861 17
		return $this->_security;
862
	}
863 17
864 17
	/**
865
	 * @param TSecurityManager $sm the security manager module
866
	 */
867
	public function setSecurityManager(TSecurityManager $sm)
868
	{
869
		$this->_security = $sm;
870
	}
871
872
	/**
873
	 * @return TAssetManager asset manager
874
	 */
875
	public function getAssetManager()
876
	{
877
		if (!$this->_assetManager) {
878
			$this->_assetManager = new TAssetManager();
879
			$this->_assetManager->init(null);
880
		}
881
		return $this->_assetManager;
882
	}
883
884
	/**
885
	 * @param TAssetManager $value asset manager
886
	 */
887
	public function setAssetManager(TAssetManager $value)
888
	{
889
		$this->_assetManager = $value;
890
	}
891
892
	/**
893
	 * @return TTemplateManager template manager
894
	 */
895
	public function getTemplateManager()
896
	{
897
		if (!$this->_templateManager) {
898
			$this->_templateManager = new TTemplateManager();
899
			$this->_templateManager->init(null);
900
		}
901
		return $this->_templateManager;
902
	}
903
904
	/**
905
	 * @param TTemplateManager $value template manager
906
	 */
907
	public function setTemplateManager(TTemplateManager $value)
908
	{
909
		$this->_templateManager = $value;
910
	}
911
912
	/**
913
	 * @return TThemeManager theme manager
914
	 */
915
	public function getThemeManager()
916
	{
917
		if (!$this->_themeManager) {
918
			$this->_themeManager = new TThemeManager();
919
			$this->_themeManager->init(null);
920
		}
921
		return $this->_themeManager;
922
	}
923
924
	/**
925
	 * @param TThemeManager $value theme manager
926
	 */
927
	public function setThemeManager(TThemeManager $value)
928
	{
929
		$this->_themeManager = $value;
930
	}
931
932
	/**
933
	 * @return IStatePersister application state persister
934
	 */
935
	public function getApplicationStatePersister()
936
	{
937
		if (!$this->_statePersister) {
938
			$this->_statePersister = new TApplicationStatePersister();
939
			$this->_statePersister->init(null);
940
		}
941
		return $this->_statePersister;
942
	}
943
944
	/**
945
	 * @param IStatePersister $persister application state persister
946
	 */
947
	public function setApplicationStatePersister(IStatePersister $persister)
948
	{
949
		$this->_statePersister = $persister;
950
	}
951
952
	/**
953
	 * @return null|\Prado\Caching\ICache the cache module, null if cache module is not installed
954
	 */
955
	public function getCache()
956
	{
957
		return $this->_cache;
958
	}
959
960
	/**
961
	 * @param \Prado\Caching\ICache $cache the cache module
962
	 */
963
	public function setCache(\Prado\Caching\ICache $cache)
964
	{
965
		$this->_cache = $cache;
966
	}
967
968
	/**
969
	 * @return \Prado\Security\IUser the application user
970
	 */
971
	public function getUser()
972
	{
973
		return $this->_user;
974
	}
975
976
	/**
977
	 * This sets the application user and raises the onSetUser event.
978
	 * @param \Prado\Security\IUser $user the application user
979
	 */
980
	public function setUser(\Prado\Security\IUser $user)
981
	{
982
		$this->_user = $user;
983
		$this->onSetUser($user);
984
	}
985
986
	/**
987
	 * Raises onSetUser event.
988
	 * Allows modules/components to run handlers when the Application User is set.
989
	 * e.g. A user module could set the $_SERVER['HTTP_ACCEPT_LANGUAGE'] and
990
	 * $_SERVER['HTTP_ACCEPT_CHARSET'] in a cli environment to the user's last
991
	 * web Language and Charset so Emails (and other templates) get language
992
	 * customized.
993
	 * @param \Prado\Security\IUser $user
994
	 * @since 4.2.2
995
	 */
996
	public function onSetUser(\Prado\Security\IUser $user)
997
	{
998
		$this->raiseEvent('onSetUser', $this, $user);
0 ignored issues
show
Bug introduced by
$user of type Prado\Security\IUser is incompatible with the type Prado\TEventParameter expected by parameter $param of Prado\TComponent::raiseEvent(). ( Ignorable by Annotation )

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

998
		$this->raiseEvent('onSetUser', $this, /** @scrutinizer ignore-type */ $user);
Loading history...
999
	}
1000
1001
	/**
1002
	 * @param bool $createIfNotExists whether to create globalization if it does not exist
1003
	 * @return null|TGlobalization globalization module
1004
	 */
1005
	public function getGlobalization($createIfNotExists = true)
1006
	{
1007
		if ($this->_globalization === null && $createIfNotExists) {
1008
			$this->_globalization = new TGlobalization();
1009
			$this->_globalization->init(null);
1010
		}
1011
		return $this->_globalization;
1012
	}
1013
1014
	/**
1015
	 * @param \Prado\I18N\TGlobalization $glob globalization module
1016
	 */
1017
	public function setGlobalization(\Prado\I18N\TGlobalization $glob)
1018
	{
1019
		$this->_globalization = $glob;
1020
	}
1021
1022
	/**
1023
	 * @return \Prado\Security\TAuthorizationRuleCollection list of authorization rules for the current request
1024
	 */
1025
	public function getAuthorizationRules()
1026
	{
1027
		if ($this->_authRules === null) {
1028
			$this->_authRules = new \Prado\Security\TAuthorizationRuleCollection();
1029
		}
1030
		return $this->_authRules;
1031
	}
1032
1033
	protected function getApplicationConfigurationClass()
1034
	{
1035
		return TApplicationConfiguration::class;
1036
	}
1037
1038
	protected function internalLoadModule($id, $force = false)
1039
	{
1040
		[$moduleClass, $initProperties, $configElement] = $this->_lazyModules[$id];
1041
		if (isset($initProperties['lazy']) && $initProperties['lazy'] && !$force) {
1042
			Prado::trace("Postponed loading of lazy module $id ({$moduleClass})", TApplication::class);
1043
			$this->setModule($id, null);
1044
			return null;
1045
		}
1046
1047
		Prado::trace("Loading module $id ({$moduleClass})", TApplication::class);
1048
		$module = Prado::createComponent($moduleClass);
1049
		foreach ($initProperties as $name => $value) {
1050
			if ($name === 'lazy') {
1051
				continue;
1052
			}
1053
			$module->setSubProperty($name, $value);
1054
		}
1055
		$this->setModule($id, $module);
1056
		// keep the key to avoid reuse of the old module id
1057
		$this->_lazyModules[$id] = null;
1058
		$module->dyPreInit($configElement);
0 ignored issues
show
Bug introduced by
The method dyPreInit() does not exist on Prado\IModule. It seems like you code against a sub-type of said class. However, the method does not exist in Prado\Util\IDbModule or Prado\Util\IPluginModule. Are you sure you never get one of those? ( Ignorable by Annotation )

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

1058
		$module->/** @scrutinizer ignore-call */ 
1059
           dyPreInit($configElement);
Loading history...
1059
1060
		return [$module, $configElement];
1061
	}
1062
	/**
1063
	 * Applies an application configuration.
1064
	 * @param TApplicationConfiguration $config the configuration
1065
	 * @param bool $withinService whether the configuration is specified within a service.
1066
	 */
1067
	public function applyConfiguration($config, $withinService = false)
1068
	{
1069
		if ($config->getIsEmpty()) {
1070
			return;
1071
		}
1072
1073
		// set path aliases and using namespaces
1074
		foreach ($config->getAliases() as $alias => $path) {
1075
			Prado::setPathOfAlias($alias, $path);
1076
		}
1077
		foreach ($config->getUsings() as $using) {
1078
			Prado::using($using);
1079
		}
1080
1081
		// set application properties
1082
		if (!$withinService) {
1083
			foreach ($config->getProperties() as $name => $value) {
1084
				$this->setSubProperty($name, $value);
1085
			}
1086
		}
1087
1088
		if (empty($this->_services)) {
1089
			$this->_services = [$this->getPageServiceID() => [\Prado\Web\Services\TPageService::class, [], null]];
1090
		}
1091
1092
		// load parameters
1093
		foreach ($config->getParameters() as $id => $parameter) {
1094
			if (is_array($parameter)) {
1095
				$component = Prado::createComponent($parameter[0]);
1096
				foreach ($parameter[1] as $name => $value) {
1097
					$component->setSubProperty($name, $value);
1098
				}
1099
				$component->dyInit($parameter[2]);
1100
				$this->_parameters->add($id, $component);
1101
			} else {
1102
				$this->_parameters->add($id, $parameter);
1103
			}
1104
		}
1105
1106
		// load and init modules specified in app config
1107
		$modules = [];
1108
		foreach ($config->getModules() as $id => $moduleConfig) {
1109
			if (!is_string($id)) {
1110
				$id = '_module' . count($this->_lazyModules);
1111
			}
1112
			$this->_lazyModules[$id] = $moduleConfig;
1113
			if ($module = $this->internalLoadModule($id)) {
1114
				$modules[] = $module;
1115
			}
1116
		}
1117
		foreach ($modules as $module) {
1118
			$module[0]->init($module[1]);
1119
		}
1120
1121
		// load service
1122
		foreach ($config->getServices() as $serviceID => $serviceConfig) {
1123
			$this->_services[$serviceID] = $serviceConfig;
1124
		}
1125
1126
		// external configurations
1127
		foreach ($config->getExternalConfigurations() as $filePath => $condition) {
1128
			if ($condition !== true) {
1129
				$condition = $this->evaluateExpression($condition);
1130
			}
1131
			if ($condition) {
1132
				if (($path = Prado::getPathOfNamespace($filePath, $this->getConfigurationFileExt())) === null || !is_file($path)) {
1133
					throw new TConfigurationException('application_includefile_invalid', $filePath);
1134
				}
1135
				$cn = $this->getApplicationConfigurationClass();
1136
				$c = new $cn();
1137
				$c->loadFromFile($path);
1138
				$this->applyConfiguration($c, $withinService);
1139
			}
1140
		}
1141
	}
1142
1143
	/**
1144
	 * Loads configuration and initializes application.
1145
	 * Configuration file will be read and parsed (if a valid cached version exists,
1146
	 * it will be used instead). Then, modules are created and initialized;
1147
	 * Afterwards, the requested service is created and initialized.
1148
	 * Lastly, the onInitComplete event is raised.
1149
	 * @throws TConfigurationException if module is redefined of invalid type, or service not defined or of invalid type
1150
	 */
1151
	protected function initApplication()
1152
	{
1153
		Prado::trace('Initializing application', TApplication::class);
1154
1155
		if ($this->_configFile !== null) {
1156
			if ($this->_cacheFile === null || @filemtime($this->_cacheFile) < filemtime($this->_configFile)) {
1157
				$config = new TApplicationConfiguration();
1158
				$config->loadFromFile($this->_configFile);
1159
				if ($this->_cacheFile !== null) {
1160
					file_put_contents($this->_cacheFile, serialize($config), LOCK_EX);
1161
				}
1162
			} else {
1163
				$config = unserialize(file_get_contents($this->_cacheFile));
1164
			}
1165
1166
			$this->applyConfiguration($config, false);
1167
		}
1168
1169
		if (($serviceID = $this->getRequest()->resolveRequest(array_keys($this->_services))) === null) {
0 ignored issues
show
introduced by
The condition $serviceID = $this->getR...s->_services)) === null is always false.
Loading history...
1170
			$serviceID = $this->getPageServiceID();
1171
		}
1172
1173
		$this->startService($serviceID);
1174
1175
		$this->onInitComplete();
1176
	}
1177
1178
	/**
1179
	 * Starts the specified service.
1180
	 * The service instance will be created. Its properties will be initialized
1181
	 * and the configurations will be applied, if any.
1182
	 * @param string $serviceID service ID
1183
	 */
1184
	public function startService($serviceID)
1185
	{
1186
		if (isset($this->_services[$serviceID])) {
1187
			[$serviceClass, $initProperties, $configElement] = $this->_services[$serviceID];
1188
			$service = Prado::createComponent($serviceClass);
1189
			if (!($service instanceof TService)) {
1190
				throw new THttpException(500, 'application_service_invalid', $serviceClass);
1191
			}
1192
			if (!$service->getEnabled()) {
1193
				throw new THttpException(500, 'application_service_unavailable', $serviceClass);
1194
			}
1195
			$service->setID($serviceID);
1196
			$this->setService($service);
1197
1198
			foreach ($initProperties as $name => $value) {
1199
				$service->setSubProperty($name, $value);
1200
			}
1201
1202
			if ($configElement !== null) {
1203
				$config = new TApplicationConfiguration();
1204
				if ($this->getConfigurationType() == self::CONFIG_TYPE_PHP) {
1205
					$config->loadFromPhp($configElement, $this->getBasePath());
1206
				} else {
1207
					$config->loadFromXml($configElement, $this->getBasePath());
1208
				}
1209
				$this->applyConfiguration($config, true);
1210
			}
1211
1212
			$service->init($configElement);
1213
		} else {
1214
			throw new THttpException(500, 'application_service_unknown', $serviceID);
1215
		}
1216
	}
1217
1218
	/**
1219
	 * Raises OnError event.
1220
	 * This method is invoked when an exception is raised during the lifecycles
1221
	 * of the application.
1222
	 * @param mixed $param event parameter
1223
	 */
1224
	public function onError($param)
1225
	{
1226
		Prado::log($param->getMessage(), TLogger::ERROR, TApplication::class);
1227
		$this->raiseEvent('OnError', $this, $param);
1228
		$this->getErrorHandler()->handleError($this, $param);
1229
	}
1230
1231
	/**
1232
	 * Raises onInitComplete event.
1233
	 * At the time when this method is invoked, application modules are loaded,
1234
	 * user request is resolved and the corresponding service is loaded and
1235
	 * initialized. The application is about to start processing the user
1236
	 * request.  This call is important for CLI/Shell applications that do not have
1237
	 * a web service lifecycle stack.  This is the first and last event for finalization
1238
	 * of any loaded modules in CLI/Shell mode.
1239
	 * @since 4.2.0
1240
	 */
1241
	public function onInitComplete()
1242
	{
1243
		$this->raiseEvent('onInitComplete', $this, null);
1244
	}
1245
1246
	/**
1247
	 * Raises OnBeginRequest event.
1248
	 * At the time when this method is invoked, application modules are loaded
1249
	 * and initialized, user request is resolved and the corresponding service
1250
	 * is loaded and initialized. The application is about to start processing
1251
	 * the user request.
1252
	 */
1253
	public function onBeginRequest()
1254
	{
1255
		$this->raiseEvent('OnBeginRequest', $this, null);
1256
	}
1257
1258
	/**
1259
	 * Raises OnAuthentication event.
1260
	 * This method is invoked when the user request needs to be authenticated.
1261
	 */
1262
	public function onAuthentication()
1263
	{
1264
		$this->raiseEvent('OnAuthentication', $this, null);
1265
	}
1266
1267
	/**
1268
	 * Raises OnAuthenticationComplete event.
1269
	 * This method is invoked right after the user request is authenticated.
1270
	 */
1271
	public function onAuthenticationComplete()
1272
	{
1273
		$this->raiseEvent('OnAuthenticationComplete', $this, null);
1274
	}
1275
1276
	/**
1277
	 * Raises OnAuthorization event.
1278
	 * This method is invoked when the user request needs to be authorized.
1279
	 */
1280
	public function onAuthorization()
1281
	{
1282
		$this->raiseEvent('OnAuthorization', $this, null);
1283
	}
1284
1285
	/**
1286
	 * Raises OnAuthorizationComplete event.
1287
	 * This method is invoked right after the user request is authorized.
1288
	 */
1289
	public function onAuthorizationComplete()
1290
	{
1291
		$this->raiseEvent('OnAuthorizationComplete', $this, null);
1292
	}
1293
1294
	/**
1295
	 * Raises OnLoadState event.
1296
	 * This method is invoked when the application needs to load state (probably stored in session).
1297
	 */
1298
	public function onLoadState()
1299
	{
1300
		$this->loadGlobals();
1301
		$this->raiseEvent('OnLoadState', $this, null);
1302
	}
1303
1304
	/**
1305
	 * Raises OnLoadStateComplete event.
1306
	 * This method is invoked right after the application state has been loaded.
1307
	 */
1308
	public function onLoadStateComplete()
1309
	{
1310
		$this->raiseEvent('OnLoadStateComplete', $this, null);
1311
	}
1312
1313
	/**
1314
	 * Raises OnPreRunService event.
1315
	 * This method is invoked right before the service is to be run.
1316
	 */
1317
	public function onPreRunService()
1318
	{
1319
		$this->raiseEvent('OnPreRunService', $this, null);
1320
	}
1321
1322
	/**
1323
	 * Runs the requested service.
1324
	 */
1325
	public function runService()
1326
	{
1327
		if ($this->_service) {
1328
			$this->_service->run();
1329
		}
1330
	}
1331
1332
	/**
1333
	 * Raises OnSaveState event.
1334
	 * This method is invoked when the application needs to save state (probably stored in session).
1335
	 */
1336
	public function onSaveState()
1337
	{
1338
		$this->raiseEvent('OnSaveState', $this, null);
1339
		$this->saveGlobals();
1340
	}
1341
1342
	/**
1343
	 * Raises OnSaveStateComplete event.
1344
	 * This method is invoked right after the application state has been saved.
1345
	 */
1346
	public function onSaveStateComplete()
1347
	{
1348
		$this->raiseEvent('OnSaveStateComplete', $this, null);
1349
	}
1350
1351
	/**
1352
	 * Raises OnPreFlushOutput event.
1353
	 * This method is invoked right before the application flushes output to client.
1354
	 */
1355
	public function onPreFlushOutput()
1356
	{
1357
		$this->raiseEvent('OnPreFlushOutput', $this, null);
1358
	}
1359
1360
	/**
1361
	 * Flushes output to client side.
1362
	 * @param bool $continueBuffering whether to continue buffering after flush if buffering was active
1363
	 */
1364
	public function flushOutput($continueBuffering = true)
1365
	{
1366
		$this->getResponse()->flush($continueBuffering);
1367
	}
1368
1369
	/**
1370
	 * Raises OnEndRequest event.
1371
	 * This method is invoked when the application completes the processing of the request.
1372
	 */
1373
	public function onEndRequest()
1374
	{
1375
		$this->flushOutput(false); // flush all remaining content in the buffer
1376
		$this->raiseEvent('OnEndRequest', $this, null);
1377
		$this->saveGlobals();  // save global state
1378
	}
1379
}
1380