Completed
Push — main ( 169704 )
by
unknown
24s queued 19s
created

TApplication::onSetUser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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

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

932
		$this->raiseEvent('onSetUser', $this, /** @scrutinizer ignore-type */ $user);
Loading history...
933
	}
934
935
	/**
936
	 * @param bool $createIfNotExists whether to create globalization if it does not exist
937
	 * @return null|TGlobalization globalization module
938
	 */
939
	public function getGlobalization($createIfNotExists = true)
940
	{
941
		if ($this->_globalization === null && $createIfNotExists) {
942
			$this->_globalization = new TGlobalization();
943
			$this->_globalization->init(null);
944
		}
945
		return $this->_globalization;
946
	}
947
948
	/**
949
	 * @param \Prado\I18N\TGlobalization $glob globalization module
950
	 */
951
	public function setGlobalization(\Prado\I18N\TGlobalization $glob)
952
	{
953
		$this->_globalization = $glob;
954
	}
955
956
	/**
957
	 * @return \Prado\Security\TAuthorizationRuleCollection list of authorization rules for the current request
958
	 */
959
	public function getAuthorizationRules()
960
	{
961
		if ($this->_authRules === null) {
962
			$this->_authRules = new \Prado\Security\TAuthorizationRuleCollection();
963
		}
964
		return $this->_authRules;
965
	}
966
967
	protected function getApplicationConfigurationClass()
968
	{
969
		return '\Prado\TApplicationConfiguration';
970
	}
971
972
	protected function internalLoadModule($id, $force = false)
973
	{
974
		[$moduleClass, $initProperties, $configElement] = $this->_lazyModules[$id];
975
		if (isset($initProperties['lazy']) && $initProperties['lazy'] && !$force) {
976
			Prado::trace("Postponed loading of lazy module $id ({$moduleClass})", '\Prado\TApplication');
977
			$this->setModule($id, null);
978
			return null;
979
		}
980
981
		Prado::trace("Loading module $id ({$moduleClass})", '\Prado\TApplication');
982
		$module = Prado::createComponent($moduleClass);
983
		foreach ($initProperties as $name => $value) {
984
			if ($name === 'lazy') {
985
				continue;
986
			}
987
			$module->setSubProperty($name, $value);
988
		}
989
		$this->setModule($id, $module);
990
		// keep the key to avoid reuse of the old module id
991
		$this->_lazyModules[$id] = null;
992
		$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

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