Passed
Push — master ( d5adc1...45b033 )
by Fabio
06:12
created

TApplication::resolvePaths()   B

Complexity

Conditions 10
Paths 16

Size

Total Lines 32
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 24.3713

Importance

Changes 0
Metric Value
cc 10
eloc 22
c 0
b 0
f 0
nc 16
nop 1
dl 0
loc 32
ccs 10
cts 21
cp 0.4762
crap 24.3713
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

685
			$module[0]->/** @scrutinizer ignore-call */ 
686
               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...
686 15
		}
687
688
		return $this->_modules[$id];
689
	}
690
691
	/**
692
	 * Returns a list of application modules indexed by module IDs.
693
	 * Modules that have not been loaded yet are returned as null objects.
694
	 * @return array list of loaded application modules, indexed by module IDs
695
	 */
696
	public function getModules()
697
	{
698
		return $this->_modules;
699
	}
700
701
	/**
702
	 * Returns a list of application modules of a specific class.
703
	 * Lazy Loading Modules are not loaded, and are null but have an ID Key.
704
	 * When null modules are found, load them with {@link getModule}.
705
	 * @param string $type class name of the modules to look for.
706
	 * @param bool $strict should the module be the class or can the module be a subclass
707
	 * @return array keys are the ids of the module and values are module of a specific class
708
	 * @since 4.2.0
709
	 */
710
	public function getModulesByType($type, $strict = false)
711
	{
712
		$m = [];
713 18
		foreach ($this->_modules as $id => $module) {
714
			if ($module === null) {
715 18
				[$moduleClass, $initProperties, $configElement] = $this->_lazyModules[$id];
716 1
				if ($strict ? ($moduleClass === $type) : ($moduleClass instanceof $type)) {
717 1
					$m[$id] = null;
718
				}
719 18
			} elseif ($strict ? (get_class($module) === $type) : $module->isa($type)) {
720
				$m[$id] = $module;
721
			}
722
		}
723
		return $m;
724
	}
725 42
726
	/**
727 42
	 * Returns the list of application parameters.
728 42
	 * Since the parameters are returned as a {@link \Prado\Collections\TMap} object, you may use
729
	 * the returned result to access, add or remove individual parameters.
730
	 * @return \Prado\Collections\TMap the list of application parameters
731
	 */
732
	public function getParameters()
733 1
	{
734
		return $this->_parameters;
735 1
	}
736
737
	/**
738
	 * @return THttpRequest the request module
739 1
	 */
740
	public function getRequest()
741
	{
742
		if (!$this->_request) {
743
			$this->_request = new \Prado\Web\THttpRequest;
744
			$this->_request->init(null);
745 8
		}
746
		return $this->_request;
747 8
	}
748 8
749
	/**
750
	 * @param THttpRequest $request the request module
751
	 */
752
	public function setRequest(THttpRequest $request)
753
	{
754
		$this->_request = $request;
755
	}
756
757
	/**
758
	 * @return THttpResponse the response module
759
	 */
760
	public function getResponse()
761
	{
762
		if (!$this->_response) {
763
			$this->_response = new THttpResponse;
764
			$this->_response->init(null);
765 10
		}
766
		return $this->_response;
767 10
	}
768 10
769
	/**
770
	 * @param THttpResponse $response the request module
771
	 */
772
	public function setResponse(THttpResponse $response)
773
	{
774
		$this->_response = $response;
775
	}
776
777
	/**
778
	 * @return THttpSession the session module, null if session module is not installed
779
	 */
780
	public function getSession()
781
	{
782
		if (!$this->_session) {
783
			$this->_session = new THttpSession;
784
			$this->_session->init(null);
785
		}
786
		return $this->_session;
787
	}
788
789
	/**
790
	 * @param THttpSession $session the session module
791
	 */
792
	public function setSession(THttpSession $session)
793 2
	{
794
		$this->_session = $session;
795 2
	}
796 1
797 1
	/**
798
	 * @return TErrorHandler the error handler module
799 2
	 */
800
	public function getErrorHandler()
801
	{
802
		if (!$this->_errorHandler) {
803
			$this->_errorHandler = new TErrorHandler;
804
			$this->_errorHandler->init(null);
805 9
		}
806
		return $this->_errorHandler;
807 9
	}
808 9
809
	/**
810
	 * @param TErrorHandler $handler the error handler module
811
	 */
812
	public function setErrorHandler(TErrorHandler $handler)
813 1
	{
814
		$this->_errorHandler = $handler;
815 1
	}
816
817
	/**
818
	 * @return TSecurityManager the security manager module
819 1
	 */
820
	public function getSecurityManager()
821
	{
822
		if (!$this->_security) {
823
			$this->_security = new TSecurityManager;
824
			$this->_security->init(null);
825 6
		}
826
		return $this->_security;
827 6
	}
828 6
829
	/**
830
	 * @param TSecurityManager $sm the security manager module
831
	 */
832
	public function setSecurityManager(TSecurityManager $sm)
833 3
	{
834
		$this->_security = $sm;
835 3
	}
836 2
837 2
	/**
838
	 * @return TAssetManager asset manager
839 3
	 */
840
	public function getAssetManager()
841
	{
842
		if (!$this->_assetManager) {
843
			$this->_assetManager = new TAssetManager;
844
			$this->_assetManager->init(null);
845 2
		}
846
		return $this->_assetManager;
847 2
	}
848 2
849
	/**
850
	 * @param TAssetManager $value asset manager
851
	 */
852
	public function setAssetManager(TAssetManager $value)
853 20
	{
854
		$this->_assetManager = $value;
855 20
	}
856
857
	/**
858
	 * @return IStatePersister application state persister
859
	 */
860
	public function getApplicationStatePersister()
861 17
	{
862
		if (!$this->_statePersister) {
863 17
			$this->_statePersister = new TApplicationStatePersister;
864 17
			$this->_statePersister->init(null);
865
		}
866
		return $this->_statePersister;
867
	}
868
869
	/**
870
	 * @param IStatePersister $persister application state persister
871
	 */
872
	public function setApplicationStatePersister(IStatePersister $persister)
873
	{
874
		$this->_statePersister = $persister;
875
	}
876
877
	/**
878
	 * @return null|\Prado\Caching\ICache the cache module, null if cache module is not installed
879
	 */
880
	public function getCache()
881
	{
882
		return $this->_cache;
883
	}
884
885
	/**
886
	 * @param \Prado\Caching\ICache $cache the cache module
887
	 */
888
	public function setCache(\Prado\Caching\ICache $cache)
889
	{
890
		$this->_cache = $cache;
891
	}
892
893
	/**
894
	 * @return \Prado\Security\IUser the application user
895
	 */
896
	public function getUser()
897
	{
898
		return $this->_user;
899
	}
900
901
	/**
902
	 * @param \Prado\Security\IUser $user the application user
903
	 */
904
	public function setUser(\Prado\Security\IUser $user)
905
	{
906
		$this->_user = $user;
907
	}
908
909
	/**
910
	 * @param bool $createIfNotExists whether to create globalization if it does not exist
911
	 * @return null|TGlobalization globalization module
912
	 */
913
	public function getGlobalization($createIfNotExists = true)
914
	{
915
		if ($this->_globalization === null && $createIfNotExists) {
916
			$this->_globalization = new TGlobalization;
917
			$this->_globalization->init(null);
918
		}
919
		return $this->_globalization;
920
	}
921
922
	/**
923
	 * @param \Prado\I18N\TGlobalization $glob globalization module
924
	 */
925
	public function setGlobalization(\Prado\I18N\TGlobalization $glob)
926
	{
927
		$this->_globalization = $glob;
928
	}
929
930
	/**
931
	 * @return \Prado\Security\TAuthorizationRuleCollection list of authorization rules for the current request
932
	 */
933
	public function getAuthorizationRules()
934
	{
935
		if ($this->_authRules === null) {
936
			$this->_authRules = new \Prado\Security\TAuthorizationRuleCollection;
937
		}
938
		return $this->_authRules;
939
	}
940
941
	protected function getApplicationConfigurationClass()
942
	{
943
		return '\Prado\TApplicationConfiguration';
944
	}
945
946
	protected function internalLoadModule($id, $force = false)
947
	{
948
		[$moduleClass, $initProperties, $configElement] = $this->_lazyModules[$id];
949
		if (isset($initProperties['lazy']) && $initProperties['lazy'] && !$force) {
950
			Prado::trace("Postponed loading of lazy module $id ({$moduleClass})", '\Prado\TApplication');
951
			$this->setModule($id, null);
952
			return null;
953
		}
954
955
		Prado::trace("Loading module $id ({$moduleClass})", '\Prado\TApplication');
956
		$module = Prado::createComponent($moduleClass);
957
		foreach ($initProperties as $name => $value) {
958
			if ($name === 'lazy') {
959
				continue;
960
			}
961
			$module->setSubProperty($name, $value);
962
		}
963
		$this->setModule($id, $module);
964
		// keep the key to avoid reuse of the old module id
965
		$this->_lazyModules[$id] = null;
966
		$module->dyPreInit($configElement);
0 ignored issues
show
Bug introduced by
The method dyPreInit() does not exist on Prado\IModule. Since it exists in all sub-types, consider adding an abstract or default implementation to Prado\IModule. ( Ignorable by Annotation )

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

966
		$module->/** @scrutinizer ignore-call */ 
967
           dyPreInit($configElement);
Loading history...
967
968
		return [$module, $configElement];
969
	}
970
	/**
971
	 * Applies an application configuration.
972
	 * @param TApplicationConfiguration $config the configuration
973
	 * @param bool $withinService whether the configuration is specified within a service.
974
	 */
975
	public function applyConfiguration($config, $withinService = false)
976
	{
977
		if ($config->getIsEmpty()) {
978
			return;
979
		}
980
981
		// set path aliases and using namespaces
982
		foreach ($config->getAliases() as $alias => $path) {
983
			Prado::setPathOfAlias($alias, $path);
984
		}
985
		foreach ($config->getUsings() as $using) {
986
			Prado::using($using);
987
		}
988
989
		// set application properties
990
		if (!$withinService) {
991
			foreach ($config->getProperties() as $name => $value) {
992
				$this->setSubProperty($name, $value);
993
			}
994
		}
995
996
		if (empty($this->_services)) {
997
			$this->_services = [$this->getPageServiceID() => ['Prado\Web\Services\TPageService', [], null]];
998
		}
999
1000
		// load parameters
1001
		foreach ($config->getParameters() as $id => $parameter) {
1002
			if (is_array($parameter)) {
1003
				$component = Prado::createComponent($parameter[0]);
1004
				foreach ($parameter[1] as $name => $value) {
1005
					$component->setSubProperty($name, $value);
1006
				}
1007
				$component->dyInit(null);
1008
				$this->_parameters->add($id, $component);
1009
			} else {
1010
				$this->_parameters->add($id, $parameter);
1011
			}
1012
		}
1013
1014
		// load and init modules specified in app config
1015
		$modules = [];
1016
		foreach ($config->getModules() as $id => $moduleConfig) {
1017
			if (!is_string($id)) {
1018
				$id = '_module' . count($this->_lazyModules);
1019
			}
1020
			$this->_lazyModules[$id] = $moduleConfig;
1021
			if ($module = $this->internalLoadModule($id)) {
1022
				$modules[] = $module;
1023
			}
1024
		}
1025
		foreach ($modules as $module) {
1026
			$module[0]->init($module[1]);
1027
		}
1028
1029
		// load service
1030
		foreach ($config->getServices() as $serviceID => $serviceConfig) {
1031
			$this->_services[$serviceID] = $serviceConfig;
1032
		}
1033
1034
		// external configurations
1035
		foreach ($config->getExternalConfigurations() as $filePath => $condition) {
1036
			if ($condition !== true) {
1037
				$condition = $this->evaluateExpression($condition);
1038
			}
1039
			if ($condition) {
1040
				if (($path = Prado::getPathOfNamespace($filePath, $this->getConfigurationFileExt())) === null || !is_file($path)) {
1041
					throw new TConfigurationException('application_includefile_invalid', $filePath);
1042
				}
1043
				$cn = $this->getApplicationConfigurationClass();
1044
				$c = new $cn;
1045
				$c->loadFromFile($path);
1046
				$this->applyConfiguration($c, $withinService);
1047
			}
1048
		}
1049
	}
1050
1051
	/**
1052
	 * Loads configuration and initializes application.
1053
	 * Configuration file will be read and parsed (if a valid cached version exists,
1054
	 * it will be used instead). Then, modules are created and initialized;
1055
	 * Afterwards, the requested service is created and initialized.
1056
	 * Lastly, the onInitComplete event is raised.
1057
	 * @throws TConfigurationException if module is redefined of invalid type, or service not defined or of invalid type
1058
	 */
1059
	protected function initApplication()
1060
	{
1061
		Prado::trace('Initializing application', 'Prado\TApplication');
1062
1063
		if ($this->_configFile !== null) {
1064
			if ($this->_cacheFile === null || @filemtime($this->_cacheFile) < filemtime($this->_configFile)) {
1065
				$config = new TApplicationConfiguration;
1066
				$config->loadFromFile($this->_configFile);
1067
				if ($this->_cacheFile !== null) {
1068
					file_put_contents($this->_cacheFile, serialize($config), LOCK_EX);
1069
				}
1070
			} else {
1071
				$config = unserialize(file_get_contents($this->_cacheFile));
1072
			}
1073
1074
			$this->applyConfiguration($config, false);
1075
		}
1076
1077
		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...
1078
			$serviceID = $this->getPageServiceID();
1079
		}
1080
1081
		$this->startService($serviceID);
1082
1083
		$this->onInitComplete();
1084
	}
1085
1086
	/**
1087
	 * Starts the specified service.
1088
	 * The service instance will be created. Its properties will be initialized
1089
	 * and the configurations will be applied, if any.
1090
	 * @param string $serviceID service ID
1091
	 */
1092
	public function startService($serviceID)
1093
	{
1094
		if (isset($this->_services[$serviceID])) {
1095
			[$serviceClass, $initProperties, $configElement] = $this->_services[$serviceID];
1096
			$service = Prado::createComponent($serviceClass);
1097
			if (!($service instanceof IService)) {
1098
				throw new THttpException(500, 'application_service_invalid', $serviceClass);
1099
			}
1100
			if (!$service->getEnabled()) {
1101
				throw new THttpException(500, 'application_service_unavailable', $serviceClass);
1102
			}
1103
			$service->setID($serviceID);
1104
			$this->setService($service);
1105
1106
			foreach ($initProperties as $name => $value) {
1107
				$service->setSubProperty($name, $value);
0 ignored issues
show
Bug introduced by
The method setSubProperty() does not exist on Prado\IService. Since it exists in all sub-types, consider adding an abstract or default implementation to Prado\IService. ( Ignorable by Annotation )

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

1107
				$service->/** @scrutinizer ignore-call */ 
1108
              setSubProperty($name, $value);
Loading history...
1108
			}
1109
1110
			if ($configElement !== null) {
1111
				$config = new TApplicationConfiguration;
1112
				if ($this->getConfigurationType() == self::CONFIG_TYPE_PHP) {
1113
					$config->loadFromPhp($configElement, $this->getBasePath());
1114
				} else {
1115
					$config->loadFromXml($configElement, $this->getBasePath());
1116
				}
1117
				$this->applyConfiguration($config, true);
1118
			}
1119
1120
			$service->init($configElement);
1121
		} else {
1122
			throw new THttpException(500, 'application_service_unknown', $serviceID);
1123
		}
1124
	}
1125
1126
	/**
1127
	 * Raises OnError event.
1128
	 * This method is invoked when an exception is raised during the lifecycles
1129
	 * of the application.
1130
	 * @param mixed $param event parameter
1131
	 */
1132
	public function onError($param)
1133
	{
1134
		Prado::log($param->getMessage(), TLogger::ERROR, 'Prado\TApplication');
1135
		$this->raiseEvent('OnError', $this, $param);
1136
		$this->getErrorHandler()->handleError($this, $param);
1137
	}
1138
1139
	/**
1140
	 * Raises onInitComplete event.
1141
	 * At the time when this method is invoked, application modules are loaded,
1142
	 * user request is resolved and the corresponding service is loaded and
1143
	 * initialized. The application is about to start processing the user
1144
	 * request.  This call is important for CLI/Shell applications that do not have
1145
	 * a web service lifecycle stack.  This is the first and last event for finalization
1146
	 * of any loaded modules in CLI/Shell mode.
1147
	 * @since 4.2.0
1148
	 */
1149
	public function onInitComplete()
1150
	{
1151
		$this->raiseEvent('onInitComplete', $this, null);
1152
	}
1153
1154
	/**
1155
	 * Raises OnBeginRequest event.
1156
	 * At the time when this method is invoked, application modules are loaded
1157
	 * and initialized, user request is resolved and the corresponding service
1158
	 * is loaded and initialized. The application is about to start processing
1159
	 * the user request.
1160
	 */
1161
	public function onBeginRequest()
1162
	{
1163
		$this->raiseEvent('OnBeginRequest', $this, null);
1164
	}
1165
1166
	/**
1167
	 * Raises OnAuthentication event.
1168
	 * This method is invoked when the user request needs to be authenticated.
1169
	 */
1170
	public function onAuthentication()
1171
	{
1172
		$this->raiseEvent('OnAuthentication', $this, null);
1173
	}
1174
1175
	/**
1176
	 * Raises OnAuthenticationComplete event.
1177
	 * This method is invoked right after the user request is authenticated.
1178
	 */
1179
	public function onAuthenticationComplete()
1180
	{
1181
		$this->raiseEvent('OnAuthenticationComplete', $this, null);
1182
	}
1183
1184
	/**
1185
	 * Raises OnAuthorization event.
1186
	 * This method is invoked when the user request needs to be authorized.
1187
	 */
1188
	public function onAuthorization()
1189
	{
1190
		$this->raiseEvent('OnAuthorization', $this, null);
1191
	}
1192
1193
	/**
1194
	 * Raises OnAuthorizationComplete event.
1195
	 * This method is invoked right after the user request is authorized.
1196
	 */
1197
	public function onAuthorizationComplete()
1198
	{
1199
		$this->raiseEvent('OnAuthorizationComplete', $this, null);
1200
	}
1201
1202
	/**
1203
	 * Raises OnLoadState event.
1204
	 * This method is invoked when the application needs to load state (probably stored in session).
1205
	 */
1206
	public function onLoadState()
1207
	{
1208
		$this->loadGlobals();
1209
		$this->raiseEvent('OnLoadState', $this, null);
1210
	}
1211
1212
	/**
1213
	 * Raises OnLoadStateComplete event.
1214
	 * This method is invoked right after the application state has been loaded.
1215
	 */
1216
	public function onLoadStateComplete()
1217
	{
1218
		$this->raiseEvent('OnLoadStateComplete', $this, null);
1219
	}
1220
1221
	/**
1222
	 * Raises OnPreRunService event.
1223
	 * This method is invoked right before the service is to be run.
1224
	 */
1225
	public function onPreRunService()
1226
	{
1227
		$this->raiseEvent('OnPreRunService', $this, null);
1228
	}
1229
1230
	/**
1231
	 * Runs the requested service.
1232
	 */
1233
	public function runService()
1234
	{
1235
		if ($this->_service) {
1236
			$this->_service->run();
1237
		}
1238
	}
1239
1240
	/**
1241
	 * Raises OnSaveState event.
1242
	 * This method is invoked when the application needs to save state (probably stored in session).
1243
	 */
1244
	public function onSaveState()
1245
	{
1246
		$this->raiseEvent('OnSaveState', $this, null);
1247
		$this->saveGlobals();
1248
	}
1249
1250
	/**
1251
	 * Raises OnSaveStateComplete event.
1252
	 * This method is invoked right after the application state has been saved.
1253
	 */
1254
	public function onSaveStateComplete()
1255
	{
1256
		$this->raiseEvent('OnSaveStateComplete', $this, null);
1257
	}
1258
1259
	/**
1260
	 * Raises OnPreFlushOutput event.
1261
	 * This method is invoked right before the application flushes output to client.
1262
	 */
1263
	public function onPreFlushOutput()
1264
	{
1265
		$this->raiseEvent('OnPreFlushOutput', $this, null);
1266
	}
1267
1268
	/**
1269
	 * Flushes output to client side.
1270
	 * @param bool $continueBuffering whether to continue buffering after flush if buffering was active
1271
	 */
1272
	public function flushOutput($continueBuffering = true)
1273
	{
1274
		$this->getResponse()->flush($continueBuffering);
1275
	}
1276
1277
	/**
1278
	 * Raises OnEndRequest event.
1279
	 * This method is invoked when the application completes the processing of the request.
1280
	 */
1281
	public function onEndRequest()
1282
	{
1283
		$this->flushOutput(false); // flush all remaining content in the buffer
1284
		$this->raiseEvent('OnEndRequest', $this, null);
1285
		$this->saveGlobals();  // save global state
1286
	}
1287
}
1288