Passed
Push — master ( 6900bc...893516 )
by Fabio
04:51
created

TApplication::getTemplateManager()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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

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

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

982
		$this->raiseEvent('onSetUser', $this, /** @scrutinizer ignore-type */ $user);
Loading history...
983
	}
984
985
	/**
986
	 * @param bool $createIfNotExists whether to create globalization if it does not exist
987
	 * @return null|TGlobalization globalization module
988
	 */
989
	public function getGlobalization($createIfNotExists = true)
990
	{
991
		if ($this->_globalization === null && $createIfNotExists) {
992
			$this->_globalization = new TGlobalization();
993
			$this->_globalization->init(null);
994
		}
995
		return $this->_globalization;
996
	}
997
998
	/**
999
	 * @param \Prado\I18N\TGlobalization $glob globalization module
1000
	 */
1001
	public function setGlobalization(\Prado\I18N\TGlobalization $glob)
1002
	{
1003
		$this->_globalization = $glob;
1004
	}
1005
1006
	/**
1007
	 * @return \Prado\Security\TAuthorizationRuleCollection list of authorization rules for the current request
1008
	 */
1009
	public function getAuthorizationRules()
1010
	{
1011
		if ($this->_authRules === null) {
1012
			$this->_authRules = new \Prado\Security\TAuthorizationRuleCollection();
1013
		}
1014
		return $this->_authRules;
1015
	}
1016
1017
	protected function getApplicationConfigurationClass()
1018
	{
1019
		return TApplicationConfiguration::class;
1020
	}
1021
1022
	protected function internalLoadModule($id, $force = false)
1023
	{
1024
		[$moduleClass, $initProperties, $configElement] = $this->_lazyModules[$id];
1025
		if (isset($initProperties['lazy']) && $initProperties['lazy'] && !$force) {
1026
			Prado::trace("Postponed loading of lazy module $id ({$moduleClass})", TApplication::class);
1027
			$this->setModule($id, null);
1028
			return null;
1029
		}
1030
1031
		Prado::trace("Loading module $id ({$moduleClass})", TApplication::class);
1032
		$module = Prado::createComponent($moduleClass);
1033
		foreach ($initProperties as $name => $value) {
1034
			if ($name === 'lazy') {
1035
				continue;
1036
			}
1037
			$module->setSubProperty($name, $value);
1038
		}
1039
		$this->setModule($id, $module);
1040
		// keep the key to avoid reuse of the old module id
1041
		$this->_lazyModules[$id] = null;
1042
		$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

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