Application.php 11.8 KB
Newer Older
w  
Qiang Xue committed
1 2 3
<?php
/**
 * @link http://www.yiiframework.com/
Qiang Xue committed
4
 * @copyright Copyright (c) 2008 Yii Software LLC
w  
Qiang Xue committed
5 6 7
 * @license http://www.yiiframework.com/license/
 */

8 9
namespace yii\base;

Qiang Xue committed
10
use Yii;
Qiang Xue committed
11
use yii\helpers\FileHelper;
.  
Qiang Xue committed
12

w  
Qiang Xue committed
13 14 15
/**
 * Application is the base class for all application classes.
 *
w  
Qiang Xue committed
16 17
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
w  
Qiang Xue committed
18
 */
Qiang Xue committed
19
class Application extends Module
w  
Qiang Xue committed
20
{
Qiang Xue committed
21 22
	const EVENT_BEFORE_REQUEST = 'beforeRequest';
	const EVENT_AFTER_REQUEST = 'afterRequest';
w  
Qiang Xue committed
23
	/**
Qiang Xue committed
24
	 * @var string the application name.
w  
Qiang Xue committed
25 26
	 */
	public $name = 'My Application';
Qiang Xue committed
27
	/**
Qiang Xue committed
28
	 * @var string the version of this application.
Qiang Xue committed
29 30
	 */
	public $version = '1.0';
w  
Qiang Xue committed
31
	/**
Qiang Xue committed
32
	 * @var string the charset currently used for the application.
w  
Qiang Xue committed
33 34
	 */
	public $charset = 'UTF-8';
Qiang Xue committed
35 36 37 38 39
	/**
	 * @var string the language that is meant to be used for end users.
	 * @see sourceLanguage
	 */
	public $language = 'en_US';
w  
Qiang Xue committed
40 41
	/**
	 * @var string the language that the application is written in. This mainly refers to
Qiang Xue committed
42
	 * the language that the messages and view files are written in.
.  
Qiang Xue committed
43
	 * @see language
w  
Qiang Xue committed
44
	 */
Qiang Xue committed
45
	public $sourceLanguage = 'en_US';
Qiang Xue committed
46
	/**
Qiang Xue committed
47
	 * @var array IDs of the components that need to be loaded when the application starts.
Qiang Xue committed
48
	 */
Qiang Xue committed
49
	public $preload = array();
Qiang Xue committed
50
	/**
51
	 * @var \yii\web\Controller|\yii\console\Controller the currently active controller instance
Qiang Xue committed
52 53
	 */
	public $controller;
Qiang Xue committed
54 55 56 57 58
	/**
	 * @var mixed the layout that should be applied for views in this application. Defaults to 'main'.
	 * If this is false, layout will be disabled.
	 */
	public $layout = 'main';
w  
Qiang Xue committed
59 60 61

	private $_ended = false;

62
	/**
Alexander Makarov committed
63
	 * @var string Used to reserve memory for fatal error handler.
64 65 66
	 */
	private $_memoryReserve;

w  
Qiang Xue committed
67 68
	/**
	 * Constructor.
69 70 71
	 * @param array $config name-value pairs that will be used to initialize the object properties.
	 * Note that the configuration must contain both [[id]] and [[basePath]].
	 * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
w  
Qiang Xue committed
72
	 */
73
	public function __construct($config = array())
w  
Qiang Xue committed
74
	{
Qiang Xue committed
75
		Yii::$app = $this;
76 77 78 79 80 81 82

		if (!isset($config['id'])) {
			throw new InvalidConfigException('The "id" configuration is required.');
		}

		if (isset($config['basePath'])) {
			$this->setBasePath($config['basePath']);
Qiang Xue committed
83
			Yii::setAlias('@app', $this->getBasePath());
84 85 86 87
			unset($config['basePath']);
		} else {
			throw new InvalidConfigException('The "basePath" configuration is required.');
		}
Qiang Xue committed
88

Qiang Xue committed
89
		$this->registerErrorHandlers();
w  
Qiang Xue committed
90
		$this->registerCoreComponents();
Qiang Xue committed
91

Qiang Xue committed
92
		Component::__construct($config);
.  
Qiang Xue committed
93
	}
w  
Qiang Xue committed
94

.  
Qiang Xue committed
95
	/**
Qiang Xue committed
96
	 * Registers error handlers.
.  
Qiang Xue committed
97
	 */
Qiang Xue committed
98
	public function registerErrorHandlers()
.  
Qiang Xue committed
99
	{
Qiang Xue committed
100 101 102 103 104
		if (YII_ENABLE_ERROR_HANDLER) {
			ini_set('display_errors', 0);
			set_exception_handler(array($this, 'handleException'));
			set_error_handler(array($this, 'handleError'), error_reporting());
		}
w  
Qiang Xue committed
105 106 107 108
	}

	/**
	 * Terminates the application.
.  
Qiang Xue committed
109
	 * This method replaces PHP's exit() function by calling [[afterRequest()]] before exiting.
w  
Qiang Xue committed
110
	 * @param integer $status exit status (value 0 means normal exit while other values mean abnormal exit).
.  
Qiang Xue committed
111
	 * @param boolean $exit whether to exit the current request.
w  
Qiang Xue committed
112 113 114 115
	 * It defaults to true, meaning the PHP's exit() function will be called at the end of this method.
	 */
	public function end($status = 0, $exit = true)
	{
.  
Qiang Xue committed
116 117 118
		if (!$this->_ended) {
			$this->_ended = true;
			$this->afterRequest();
Qiang Xue committed
119
		}
120

121 122 123 124 125 126 127
		$this->handleFatalError();

		if ($exit) {
			exit($status);
		}
	}

Qiang Xue committed
128 129 130 131 132 133 134 135
	/**
	 * Runs the application.
	 * This is the main entrance of an application.
	 * @return integer the exit status (0 means normal, non-zero values mean abnormal)
	 */
	public function run()
	{
		$this->beforeRequest();
136 137
		// Allocating twice more than required to display memory exhausted error
		// in case of trying to allocate last 1 byte while all memory is taken.
Qiang Xue committed
138 139
		$this->_memoryReserve = str_repeat('x', 1024 * 256);
		register_shutdown_function(array($this, 'end'), 0, false);
Qiang Xue committed
140 141 142 143 144
		$status = $this->processRequest();
		$this->afterRequest();
		return $status;
	}

w  
Qiang Xue committed
145
	/**
Qiang Xue committed
146
	 * Raises the [[EVENT_BEFORE_REQUEST]] event right BEFORE the application processes the request.
w  
Qiang Xue committed
147
	 */
.  
Qiang Xue committed
148
	public function beforeRequest()
w  
Qiang Xue committed
149
	{
Qiang Xue committed
150
		$this->trigger(self::EVENT_BEFORE_REQUEST);
w  
Qiang Xue committed
151 152
	}

Qiang Xue committed
153
	/**
Qiang Xue committed
154
	 * Raises the [[EVENT_AFTER_REQUEST]] event right AFTER the application processes the request.
Qiang Xue committed
155
	 */
Qiang Xue committed
156
	public function afterRequest()
Qiang Xue committed
157
	{
Qiang Xue committed
158
		$this->trigger(self::EVENT_AFTER_REQUEST);
Qiang Xue committed
159 160
	}

w  
Qiang Xue committed
161
	/**
Qiang Xue committed
162
	 * Processes the request.
Qiang Xue committed
163
	 * Child classes should override this method with actual request processing logic.
Qiang Xue committed
164
	 * @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal)
Qiang Xue committed
165 166 167 168 169 170
	 */
	public function processRequest()
	{
		return 0;
	}

Qiang Xue committed
171 172
	private $_runtimePath;

w  
Qiang Xue committed
173 174 175 176 177 178
	/**
	 * Returns the directory that stores runtime files.
	 * @return string the directory that stores runtime files. Defaults to 'protected/runtime'.
	 */
	public function getRuntimePath()
	{
Qiang Xue committed
179
		if ($this->_runtimePath === null) {
w  
Qiang Xue committed
180 181
			$this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime');
		}
Qiang Xue committed
182
		return $this->_runtimePath;
w  
Qiang Xue committed
183 184 185 186 187
	}

	/**
	 * Sets the directory that stores runtime files.
	 * @param string $path the directory that stores runtime files.
Qiang Xue committed
188
	 * @throws InvalidConfigException if the directory does not exist or is not writable
w  
Qiang Xue committed
189 190 191
	 */
	public function setRuntimePath($path)
	{
Qiang Xue committed
192 193 194
		$path = Yii::getAlias($path);
		if (is_dir($path) && is_writable($path)) {
			$this->_runtimePath = $path;
Qiang Xue committed
195
		} else {
Qiang Xue committed
196
			throw new InvalidConfigException("Runtime path must be a directory writable by the Web server process: $path");
Qiang Xue committed
197
		}
w  
Qiang Xue committed
198 199
	}

Qiang Xue committed
200 201 202 203 204 205 206 207
	private $_vendorPath;

	/**
	 * Returns the directory that stores vendor files.
	 * @return string the directory that stores vendor files. Defaults to 'protected/vendor'.
	 */
	public function getVendorPath()
	{
Qiang Xue committed
208
		if ($this->_vendorPath === null) {
Qiang Xue committed
209 210 211 212 213 214 215 216 217 218 219
			$this->setVendorPath($this->getBasePath() . DIRECTORY_SEPARATOR . 'vendor');
		}
		return $this->_vendorPath;
	}

	/**
	 * Sets the directory that stores vendor files.
	 * @param string $path the directory that stores vendor files.
	 */
	public function setVendorPath($path)
	{
Qiang Xue committed
220
		$this->_vendorPath = Yii::getAlias($path);
Qiang Xue committed
221 222
	}

w  
Qiang Xue committed
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
	/**
	 * Returns the time zone used by this application.
	 * This is a simple wrapper of PHP function date_default_timezone_get().
	 * @return string the time zone used by this application.
	 * @see http://php.net/manual/en/function.date-default-timezone-get.php
	 */
	public function getTimeZone()
	{
		return date_default_timezone_get();
	}

	/**
	 * Sets the time zone used by this application.
	 * This is a simple wrapper of PHP function date_default_timezone_set().
	 * @param string $value the time zone used by this application.
	 * @see http://php.net/manual/en/function.date-default-timezone-set.php
	 */
	public function setTimeZone($value)
	{
		date_default_timezone_set($value);
	}

	/**
	 * Returns the database connection component.
Qiang Xue committed
247
	 * @return \yii\db\Connection the database connection
w  
Qiang Xue committed
248 249 250 251 252 253 254 255
	 */
	public function getDb()
	{
		return $this->getComponent('db');
	}

	/**
	 * Returns the error handler component.
.  
Qiang Xue committed
256
	 * @return ErrorHandler the error handler application component.
w  
Qiang Xue committed
257 258 259 260 261 262 263 264
	 */
	public function getErrorHandler()
	{
		return $this->getComponent('errorHandler');
	}

	/**
	 * Returns the cache component.
.  
Qiang Xue committed
265
	 * @return \yii\caching\Cache the cache application component. Null if the component is not enabled.
w  
Qiang Xue committed
266 267 268 269 270 271 272 273
	 */
	public function getCache()
	{
		return $this->getComponent('cache');
	}

	/**
	 * Returns the request component.
274
	 * @return \yii\web\Request|\yii\console\Request the request component
w  
Qiang Xue committed
275 276 277 278 279 280
	 */
	public function getRequest()
	{
		return $this->getComponent('request');
	}

Qiang Xue committed
281
	/**
Qiang Xue committed
282 283
	 * Returns the view object.
	 * @return View the view object that is used to render various view files.
Qiang Xue committed
284
	 */
Qiang Xue committed
285
	public function getView()
Qiang Xue committed
286
	{
Qiang Xue committed
287
		return $this->getComponent('view');
Qiang Xue committed
288 289
	}

Qiang Xue committed
290 291 292 293 294 295 296 297 298
	/**
	 * Returns the URL manager for this application.
	 * @return \yii\web\UrlManager the URL manager for this application.
	 */
	public function getUrlManager()
	{
		return $this->getComponent('urlManager');
	}

Qiang Xue committed
299 300 301 302 303 304 305 306 307
	/**
	 * Returns the internationalization (i18n) component
	 * @return \yii\i18n\I18N the internationalization component
	 */
	public function getI18N()
	{
		return $this->getComponent('i18n');
	}

w  
Qiang Xue committed
308 309 310 311
	/**
	 * Registers the core application components.
	 * @see setComponents
	 */
.  
Qiang Xue committed
312
	public function registerCoreComponents()
w  
Qiang Xue committed
313
	{
.  
Qiang Xue committed
314 315 316 317
		$this->setComponents(array(
			'errorHandler' => array(
				'class' => 'yii\base\ErrorHandler',
			),
Qiang Xue committed
318 319
			'i18n' => array(
				'class' => 'yii\i18n\I18N',
w  
Qiang Xue committed
320
			),
Qiang Xue committed
321 322
			'urlManager' => array(
				'class' => 'yii\web\UrlManager',
w  
Qiang Xue committed
323
			),
Qiang Xue committed
324 325 326
			'view' => array(
				'class' => 'yii\base\View',
			),
.  
Qiang Xue committed
327
		));
w  
Qiang Xue committed
328
	}
Qiang Xue committed
329

330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
	/**
	 * Handles uncaught PHP exceptions.
	 *
	 * This method is implemented as a PHP exception handler. It requires
	 * that constant YII_ENABLE_ERROR_HANDLER be defined true.
	 *
	 * @param \Exception $exception exception that is not caught
	 */
	public function handleException($exception)
	{
		// disable error capturing to avoid recursive errors while handling exceptions
		restore_error_handler();
		restore_exception_handler();

		try {
			$this->logException($exception);

			if (($handler = $this->getErrorHandler()) !== null) {
				$handler->handle($exception);
			} else {
				$this->renderException($exception);
			}

			$this->end(1);

		} catch (\Exception $e) {
			// exception could be thrown in end() or ErrorHandler::handle()
			$msg = (string)$e;
			$msg .= "\nPrevious exception:\n";
			$msg .= (string)$exception;
			if (YII_DEBUG) {
				echo $msg;
			}
			$msg .= "\n\$_SERVER = " . var_export($_SERVER, true);
			error_log($msg);
			exit(1);
		}
	}

Qiang Xue committed
369 370 371 372 373 374 375 376 377
	/**
	 * Handles PHP execution errors such as warnings, notices.
	 *
	 * This method is used as a PHP error handler. It will simply raise an `ErrorException`.
	 *
	 * @param integer $code the level of the error raised
	 * @param string $message the error message
	 * @param string $file the filename that the error was raised in
	 * @param integer $line the line number the error was raised at
378 379
	 *
	 * @throws ErrorException
Qiang Xue committed
380 381 382 383
	 */
	public function handleError($code, $message, $file, $line)
	{
		if (error_reporting() !== 0) {
384 385 386 387 388
			$exception = new ErrorException($message, $code, $code, $file, $line);

			// in case error appeared in __toString method we can't throw any exception
			$trace = debug_backtrace(false);
			array_shift($trace);
Qiang Xue committed
389 390
			foreach ($trace as $frame) {
				if ($frame['function'] == '__toString') {
391 392 393 394 395
					$this->handleException($exception);
				}
			}

			throw $exception;
Qiang Xue committed
396 397 398 399
		}
	}

	/**
400
	 * Handles fatal PHP errors
Qiang Xue committed
401
	 */
402
	public function handleFatalError()
Qiang Xue committed
403
	{
404 405
		if (YII_ENABLE_ERROR_HANDLER) {
			$error = error_get_last();
Qiang Xue committed
406

407 408 409 410 411
			if (ErrorException::isFatalError($error)) {
				unset($this->_memoryReserve);
				$exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
				// use error_log because it's too late to use Yii log
				error_log($exception);
Qiang Xue committed
412

413
				if (($handler = $this->getErrorHandler()) !== null) {
414
					$handler->handle($exception);
415 416 417
				} else {
					$this->renderException($exception);
				}
Qiang Xue committed
418

419
				exit(1);
Qiang Xue committed
420
			}
Qiang Xue committed
421 422 423
		}
	}

Qiang Xue committed
424 425 426 427 428 429
	/**
	 * Renders an exception without using rich format.
	 * @param \Exception $exception the exception to be rendered.
	 */
	public function renderException($exception)
	{
Qiang Xue committed
430
		if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) {
Qiang Xue committed
431 432 433 434 435 436 437 438 439 440 441
			$message = $exception->getName() . ': ' . $exception->getMessage();
		} else {
			$message = YII_DEBUG ? (string)$exception : 'Error: ' . $exception->getMessage();
		}
		if (PHP_SAPI) {
			echo $message . "\n";
		} else {
			echo '<pre>' . htmlspecialchars($message, ENT_QUOTES, $this->charset) . '</pre>';
		}
	}

Qiang Xue committed
442 443 444 445 446 447 448 449 450 451 452 453 454
	// todo: to be polished
	protected function logException($exception)
	{
		$category = get_class($exception);
		if ($exception instanceof HttpException) {
			/** @var $exception HttpException */
			$category .= '\\' . $exception->statusCode;
		} elseif ($exception instanceof \ErrorException) {
			/** @var $exception \ErrorException */
			$category .= '\\' . $exception->getSeverity();
		}
		Yii::error((string)$exception, $category);
	}
w  
Qiang Xue committed
455
}