TApplication::getModule()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

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

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

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

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

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