From 07c86e58f18d9debe123b09143808422858c89cb Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 23 Jul 2011 14:10:07 -0400 Subject: [PATCH] w --- framework/base/Application.php | 965 ++++++++++++++++++++++++++++++++ framework/base/ApplicationComponent.php | 55 ++ framework/base/Module.php | 501 +++++++++++++++++ 3 files changed, 1521 insertions(+) create mode 100644 framework/base/Application.php create mode 100644 framework/base/ApplicationComponent.php create mode 100644 framework/base/Module.php diff --git a/framework/base/Application.php b/framework/base/Application.php new file mode 100644 index 0000000..ccc283c --- /dev/null +++ b/framework/base/Application.php @@ -0,0 +1,965 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2012 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * Application is the base class for all application classes. + * + * An application serves as the global context that the user request + * is being processed. It manages a set of application components that + * provide specific functionalities to the whole application. + * + * The core application components provided by Application are the following: + * + * + * Application will undergo the following lifecycles when processing a user request: + *
    + *
  1. load application configuration;
  2. + *
  3. set up class autoloader and error handling;
  4. + *
  5. load static application components;
  6. + *
  7. {@link onBeginRequest}: preprocess the user request;
  8. + *
  9. {@link processRequest}: process the user request;
  10. + *
  11. {@link onEndRequest}: postprocess the user request;
  12. + *
+ * + * Starting from lifecycle 3, if a PHP error or an uncaught exception occurs, + * the application will switch to its error handling logic and jump to step 6 afterwards. + * + * @author Qiang Xue + * @since 2.0 + * + * @property string $basePath Returns the root path of the application. + * @property CCache $cache Returns the cache component. + * @property CPhpMessageSource $coreMessages Returns the core message translations. + * @property CDateFormatter $dateFormatter Returns the locale-dependent date formatter. + * @property CDbConnection $db Returns the database connection component. + * @property CErrorHandler $errorHandler Returns the error handler component. + * @property string $extensionPath Returns the root directory that holds all third-party extensions. + * @property string $id Returns the unique identifier for the application. + * @property string $language Returns the language that the user is using and the application should be targeted to. + * @property CLocale $locale Returns the locale instance. + * @property string $localeDataPath Returns the directory that contains the locale data. + * @property CMessageSource $messages Returns the application message translations component. + * @property CNumberFormatter $numberFormatter The locale-dependent number formatter. + * @property CHttpRequest $request Returns the request component. + * @property string $runtimePath Returns the directory that stores runtime files. + * @property CSecurityManager $securityManager Returns the security manager component. + * @property CStatePersister $statePersister Returns the state persister component. + * @property string $timeZone Returns the time zone used by this application. + * @property CUrlManager $urlManager Returns the URL manager component. + * @property string $baseUrl Returns the relative URL for the application + * @property string $homeUrl the homepage URL + */ +abstract class Application extends Module +{ + /** + * @var string the application name. Defaults to 'My Application'. + */ + public $name = 'My Application'; + /** + * @var string the charset currently used for the application. Defaults to 'UTF-8'. + */ + public $charset = 'UTF-8'; + /** + * @var string the language that the application is written in. This mainly refers to + * the language that the messages and view files are in. Defaults to 'en_us' (US English). + */ + public $sourceLanguage = 'en_us'; + + private $_id; + private $_basePath; + private $_runtimePath; + private $_extensionPath; + private $_globalState; + private $_stateChanged; + private $_ended = false; + private $_language; + private $_homeUrl; + + /** + * Processes the request. + * This is the place where the actual request processing work is done. + * Derived classes should override this method. + */ + abstract public function processRequest(); + + /** + * Constructor. + * @param mixed $config application configuration. + * If a string, it is treated as the path of the file that contains the configuration; + * If an array, it is the actual configuration information. + * Please make sure you specify the {@link getBasePath basePath} property in the configuration, + * which should point to the directory containing all application logic, template and data. + * If not, the directory will be defaulted to 'protected'. + */ + public function __construct($config = null) + { + Yii::setApplication($this); + + // set basePath at early as possible to avoid trouble + if (is_string($config)) + $config = require($config); + if (isset($config['basePath'])) + { + $this->setBasePath($config['basePath']); + unset($config['basePath']); + } + else + $this->setBasePath('protected'); + Yii::setPathOfAlias('application', $this->getBasePath()); + Yii::setPathOfAlias('webroot', dirname($_SERVER['SCRIPT_FILENAME'])); + Yii::setPathOfAlias('ext', $this->getBasePath() . DIRECTORY_SEPARATOR . 'extensions'); + + $this->preinit(); + + $this->initSystemHandlers(); + $this->registerCoreComponents(); + + $this->configure($config); + $this->attachBehaviors($this->behaviors); + $this->preloadComponents(); + + $this->init(); + } + + + /** + * Runs the application. + * This method loads static application components. Derived classes usually overrides this + * method to do more application-specific tasks. + * Remember to call the parent implementation so that static application components are loaded. + */ + public function run() + { + if ($this->hasEventHandler('onBeginRequest')) + $this->onBeginRequest(new CEvent($this)); + $this->processRequest(); + if ($this->hasEventHandler('onEndRequest')) + $this->onEndRequest(new CEvent($this)); + } + + /** + * Terminates the application. + * This method replaces PHP's exit() function by calling + * {@link onEndRequest} before exiting. + * @param integer $status exit status (value 0 means normal exit while other values mean abnormal exit). + * @param boolean $exit whether to exit the current request. This parameter has been available since version 1.1.5. + * 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) + { + if ($this->hasEventHandler('onEndRequest')) + $this->onEndRequest(new CEvent($this)); + if ($exit) + exit($status); + } + + /** + * Raised right BEFORE the application processes the request. + * @param CEvent $event the event parameter + */ + public function onBeginRequest($event) + { + $this->raiseEvent('onBeginRequest', $event); + } + + /** + * Raised right AFTER the application processes the request. + * @param CEvent $event the event parameter + */ + public function onEndRequest($event) + { + if (!$this->_ended) + { + $this->_ended = true; + $this->raiseEvent('onEndRequest', $event); + } + } + + /** + * Returns the unique identifier for the application. + * @return string the unique identifier for the application. + */ + public function getId() + { + if ($this->_id !== null) + return $this->_id; + else + return $this->_id = sprintf('%x', crc32($this->getBasePath() . $this->name)); + } + + /** + * Sets the unique identifier for the application. + * @param string $id the unique identifier for the application. + */ + public function setId($id) + { + $this->_id = $id; + } + + /** + * Returns the root path of the application. + * @return string the root directory of the application. Defaults to 'protected'. + */ + public function getBasePath() + { + return $this->_basePath; + } + + /** + * Sets the root directory of the application. + * This method can only be invoked at the begin of the constructor. + * @param string $path the root directory of the application. + * @throws CException if the directory does not exist. + */ + public function setBasePath($path) + { + if (($this->_basePath = realpath($path)) === false || !is_dir($this->_basePath)) + throw new CException(Yii::t('yii', 'Application base path "{path}" is not a valid directory.', + array('{path}' => $path))); + } + + /** + * Returns the directory that stores runtime files. + * @return string the directory that stores runtime files. Defaults to 'protected/runtime'. + */ + public function getRuntimePath() + { + if ($this->_runtimePath !== null) + return $this->_runtimePath; + else + { + $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime'); + return $this->_runtimePath; + } + } + + /** + * Sets the directory that stores runtime files. + * @param string $path the directory that stores runtime files. + * @throws CException if the directory does not exist or is not writable + */ + public function setRuntimePath($path) + { + if (($runtimePath = realpath($path)) === false || !is_dir($runtimePath) || !is_writable($runtimePath)) + throw new CException(Yii::t('yii', 'Application runtime path "{path}" is not valid. Please make sure it is a directory writable by the Web server process.', + array('{path}' => $path))); + $this->_runtimePath = $runtimePath; + } + + /** + * Returns the root directory that holds all third-party extensions. + * @return string the directory that contains all extensions. Defaults to the 'extensions' directory under 'protected'. + */ + public function getExtensionPath() + { + return Yii::getPathOfAlias('ext'); + } + + /** + * Sets the root directory that holds all third-party extensions. + * @param string $path the directory that contains all third-party extensions. + */ + public function setExtensionPath($path) + { + if (($extensionPath = realpath($path)) === false || !is_dir($extensionPath)) + throw new CException(Yii::t('yii', 'Extension path "{path}" does not exist.', + array('{path}' => $path))); + Yii::setPathOfAlias('ext', $extensionPath); + } + + /** + * Returns the language that the user is using and the application should be targeted to. + * @return string the language that the user is using and the application should be targeted to. + * Defaults to the {@link sourceLanguage source language}. + */ + public function getLanguage() + { + return $this->_language === null ? $this->sourceLanguage : $this->_language; + } + + /** + * Specifies which language the application is targeted to. + * + * This is the language that the application displays to end users. + * If set null, it uses the {@link sourceLanguage source language}. + * + * Unless your application needs to support multiple languages, you should always + * set this language to null to maximize the application's performance. + * @param string $language the user language (e.g. 'en_US', 'zh_CN'). + * If it is null, the {@link sourceLanguage} will be used. + */ + public function setLanguage($language) + { + $this->_language = $language; + } + + /** + * 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 + * @since 1.0.9 + */ + 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 + * @since 1.0.9 + */ + public function setTimeZone($value) + { + date_default_timezone_set($value); + } + + /** + * Returns the localized version of a specified file. + * + * The searching is based on the specified language code. In particular, + * a file with the same name will be looked for under the subdirectory + * named as the locale ID. For example, given the file "path/to/view.php" + * and locale ID "zh_cn", the localized file will be looked for as + * "path/to/zh_cn/view.php". If the file is not found, the original file + * will be returned. + * + * For consistency, it is recommended that the locale ID is given + * in lower case and in the format of LanguageID_RegionID (e.g. "en_us"). + * + * @param string $srcFile the original file + * @param string $srcLanguage the language that the original file is in. If null, the application {@link sourceLanguage source language} is used. + * @param string $language the desired language that the file should be localized to. If null, the {@link getLanguage application language} will be used. + * @return string the matching localized file. The original file is returned if no localized version is found + * or if source language is the same as the desired language. + */ + public function findLocalizedFile($srcFile, $srcLanguage = null, $language = null) + { + if ($srcLanguage === null) + $srcLanguage = $this->sourceLanguage; + if ($language === null) + $language = $this->getLanguage(); + if ($language === $srcLanguage) + return $srcFile; + $desiredFile = dirname($srcFile) . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . basename($srcFile); + return is_file($desiredFile) ? $desiredFile : $srcFile; + } + + /** + * Returns the locale instance. + * @param string $localeID the locale ID (e.g. en_US). If null, the {@link getLanguage application language ID} will be used. + * @return CLocale the locale instance + */ + public function getLocale($localeID = null) + { + return CLocale::getInstance($localeID === null ? $this->getLanguage() : $localeID); + } + + /** + * Returns the directory that contains the locale data. + * @return string the directory that contains the locale data. It defaults to 'framework/i18n/data'. + * @since 1.1.0 + */ + public function getLocaleDataPath() + { + return CLocale::$dataPath === null ? Yii::getPathOfAlias('system.i18n.data') : CLocale::$dataPath; + } + + /** + * Sets the directory that contains the locale data. + * @param string $value the directory that contains the locale data. + * @since 1.1.0 + */ + public function setLocaleDataPath($value) + { + CLocale::$dataPath = $value; + } + + /** + * @return CNumberFormatter the locale-dependent number formatter. + * The current {@link getLocale application locale} will be used. + */ + public function getNumberFormatter() + { + return $this->getLocale()->getNumberFormatter(); + } + + /** + * Returns the locale-dependent date formatter. + * @return CDateFormatter the locale-dependent date formatter. + * The current {@link getLocale application locale} will be used. + */ + public function getDateFormatter() + { + return $this->getLocale()->getDateFormatter(); + } + + /** + * Returns the database connection component. + * @return CDbConnection the database connection + */ + public function getDb() + { + return $this->getComponent('db'); + } + + /** + * Returns the error handler component. + * @return CErrorHandler the error handler application component. + */ + public function getErrorHandler() + { + return $this->getComponent('errorHandler'); + } + + /** + * Returns the security manager component. + * @return CSecurityManager the security manager application component. + */ + public function getSecurityManager() + { + return $this->getComponent('securityManager'); + } + + /** + * Returns the state persister component. + * @return CStatePersister the state persister application component. + */ + public function getStatePersister() + { + return $this->getComponent('statePersister'); + } + + /** + * Returns the cache component. + * @return CCache the cache application component. Null if the component is not enabled. + */ + public function getCache() + { + return $this->getComponent('cache'); + } + + /** + * Returns the core message translations component. + * @return CPhpMessageSource the core message translations + */ + public function getCoreMessages() + { + return $this->getComponent('coreMessages'); + } + + /** + * Returns the application message translations component. + * @return CMessageSource the application message translations + */ + public function getMessages() + { + return $this->getComponent('messages'); + } + + /** + * Returns the request component. + * @return CHttpRequest the request component + */ + public function getRequest() + { + return $this->getComponent('request'); + } + + /** + * Returns the URL manager component. + * @return CUrlManager the URL manager component + */ + public function getUrlManager() + { + return $this->getComponent('urlManager'); + } + + /** + * @return CController the currently active controller. Null is returned in this base class. + * @since 1.1.8 + */ + public function getController() + { + return null; + } + + /** + * Creates a relative URL based on the given controller and action information. + * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'. + * @param array $params additional GET parameters (name=>value). Both the name and value will be URL-encoded. + * @param string $ampersand the token separating name-value pairs in the URL. + * @return string the constructed URL + */ + public function createUrl($route, $params = array(), $ampersand = '&') + { + return $this->getUrlManager()->createUrl($route, $params, $ampersand); + } + + /** + * Creates an absolute URL based on the given controller and action information. + * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'. + * @param array $params additional GET parameters (name=>value). Both the name and value will be URL-encoded. + * @param string $schema schema to use (e.g. http, https). If empty, the schema used for the current request will be used. + * @param string $ampersand the token separating name-value pairs in the URL. + * @return string the constructed URL + */ + public function createAbsoluteUrl($route, $params = array(), $schema = '', $ampersand = '&') + { + $url = $this->createUrl($route, $params, $ampersand); + if (strpos($url, 'http') === 0) + return $url; + else + return $this->getRequest()->getHostInfo($schema) . $url; + } + + /** + * Returns the relative URL for the application. + * This is a shortcut method to {@link CHttpRequest::getBaseUrl()}. + * @param boolean $absolute whether to return an absolute URL. Defaults to false, meaning returning a relative one. + * This parameter has been available since 1.0.2. + * @return string the relative URL for the application + * @see CHttpRequest::getBaseUrl() + */ + public function getBaseUrl($absolute = false) + { + return $this->getRequest()->getBaseUrl($absolute); + } + + /** + * @return string the homepage URL + */ + public function getHomeUrl() + { + if ($this->_homeUrl === null) + { + if ($this->getUrlManager()->showScriptName) + return $this->getRequest()->getScriptUrl(); + else + return $this->getRequest()->getBaseUrl() . '/'; + } + else + return $this->_homeUrl; + } + + /** + * @param string $value the homepage URL + */ + public function setHomeUrl($value) + { + $this->_homeUrl = $value; + } + + /** + * Returns a global value. + * + * A global value is one that is persistent across users sessions and requests. + * @param string $key the name of the value to be returned + * @param mixed $defaultValue the default value. If the named global value is not found, this will be returned instead. + * @return mixed the named global value + * @see setGlobalState + */ + public function getGlobalState($key, $defaultValue = null) + { + if ($this->_globalState === null) + $this->loadGlobalState(); + if (isset($this->_globalState[$key])) + return $this->_globalState[$key]; + else + return $defaultValue; + } + + /** + * Sets a global value. + * + * A global value is one that is persistent across users sessions and requests. + * Make sure that the value is serializable and unserializable. + * @param string $key the name of the value to be saved + * @param mixed $value the global value to be saved. It must be serializable. + * @param mixed $defaultValue the default value. If the named global value is the same as this value, it will be cleared from the current storage. + * @see getGlobalState + */ + public function setGlobalState($key, $value, $defaultValue = null) + { + if ($this->_globalState === null) + $this->loadGlobalState(); + + $changed = $this->_stateChanged; + if ($value === $defaultValue) + { + if (isset($this->_globalState[$key])) + { + unset($this->_globalState[$key]); + $this->_stateChanged = true; + } + } + elseif (!isset($this->_globalState[$key]) || $this->_globalState[$key] !== $value) + { + $this->_globalState[$key] = $value; + $this->_stateChanged = true; + } + + if ($this->_stateChanged !== $changed) + $this->attachEventHandler('onEndRequest', array($this, 'saveGlobalState')); + } + + /** + * Clears a global value. + * + * The value cleared will no longer be available in this request and the following requests. + * @param string $key the name of the value to be cleared + */ + public function clearGlobalState($key) + { + $this->setGlobalState($key, true, true); + } + + /** + * Loads the global state data from persistent storage. + * @see getStatePersister + * @throws CException if the state persister is not available + */ + public function loadGlobalState() + { + $persister = $this->getStatePersister(); + if (($this->_globalState = $persister->load()) === null) + $this->_globalState = array(); + $this->_stateChanged = false; + $this->detachEventHandler('onEndRequest', array($this, 'saveGlobalState')); + } + + /** + * Saves the global state data into persistent storage. + * @see getStatePersister + * @throws CException if the state persister is not available + */ + public function saveGlobalState() + { + if ($this->_stateChanged) + { + $this->_stateChanged = false; + $this->detachEventHandler('onEndRequest', array($this, 'saveGlobalState')); + $this->getStatePersister()->save($this->_globalState); + } + } + + /** + * Handles uncaught PHP exceptions. + * + * This method is implemented as a PHP exception handler. It requires + * that constant YII_ENABLE_EXCEPTION_HANDLER be defined true. + * + * This method will first raise an {@link onException} event. + * If the exception is not handled by any event handler, it will call + * {@link getErrorHandler errorHandler} to process the exception. + * + * The application will be terminated by this method. + * + * @param Exception $exception exception that is not caught + */ + public function handleException($exception) + { + // disable error capturing to avoid recursive errors + restore_error_handler(); + restore_exception_handler(); + + $category = 'exception.' . get_class($exception); + if ($exception instanceof CHttpException) + $category .= '.' . $exception->statusCode; + // php <5.2 doesn't support string conversion auto-magically + $message = $exception->__toString(); + if (isset($_SERVER['REQUEST_URI'])) + $message .= ' REQUEST_URI=' . $_SERVER['REQUEST_URI']; + Yii::log($message, CLogger::LEVEL_ERROR, $category); + + try + { + $event = new CExceptionEvent($this, $exception); + $this->onException($event); + if (!$event->handled) + { + // try an error handler + if (($handler = $this->getErrorHandler()) !== null) + $handler->handle($event); + else + $this->displayException($exception); + } + } + catch(Exception $e) + { + $this->displayException($e); + } + + try + { + $this->end(1); + } + catch(Exception $e) + { + // use the most primitive way to log error + $msg = get_class($e) . ': ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n"; + $msg .= $e->getTraceAsString() . "\n"; + $msg .= "Previous exception:\n"; + $msg .= get_class($exception) . ': ' . $exception->getMessage() . ' (' . $exception->getFile() . ':' . $exception->getLine() . ")\n"; + $msg .= $exception->getTraceAsString() . "\n"; + $msg .= '$_SERVER=' . var_export($_SERVER, true); + error_log($msg); + exit(1); + } + } + + /** + * Handles PHP execution errors such as warnings, notices. + * + * This method is implemented as a PHP error handler. It requires + * that constant YII_ENABLE_ERROR_HANDLER be defined true. + * + * This method will first raise an {@link onError} event. + * If the error is not handled by any event handler, it will call + * {@link getErrorHandler errorHandler} to process the error. + * + * The application will be terminated by this method. + * + * @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 + */ + public function handleError($code, $message, $file, $line) + { + if ($code & error_reporting()) + { + // disable error capturing to avoid recursive errors + restore_error_handler(); + restore_exception_handler(); + + $log = "$message ($file:$line)\nStack trace:\n"; + $trace = debug_backtrace(); + // skip the first 3 stacks as they do not tell the error position + if (count($trace) > 3) + $trace = array_slice($trace, 3); + foreach ($trace as $i => $t) + { + if (!isset($t['file'])) + $t['file'] = 'unknown'; + if (!isset($t['line'])) + $t['line'] = 0; + if (!isset($t['function'])) + $t['function'] = 'unknown'; + $log .= "#$i {$t['file']}( {$t['line']}): "; + if (isset($t['object']) && is_object($t['object'])) + $log .= get_class($t['object']) . '->'; + $log .= " {$t['function']}()\n"; + } + if (isset($_SERVER['REQUEST_URI'])) + $log .= 'REQUEST_URI=' . $_SERVER['REQUEST_URI']; + Yii::log($log, CLogger::LEVEL_ERROR, 'php'); + + try + { + Yii::import('CErrorEvent', true); + $event = new CErrorEvent($this, $code, $message, $file, $line); + $this->onError($event); + if (!$event->handled) + { + // try an error handler + if (($handler = $this->getErrorHandler()) !== null) + $handler->handle($event); + else + $this->displayError($code, $message, $file, $line); + } + } + catch(Exception $e) + { + $this->displayException($e); + } + + try + { + $this->end(1); + } + catch(Exception $e) + { + // use the most primitive way to log error + $msg = get_class($e) . ': ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n"; + $msg .= $e->getTraceAsString() . "\n"; + $msg .= "Previous error:\n"; + $msg .= $log . "\n"; + $msg .= '$_SERVER=' . var_export($_SERVER, true); + error_log($msg); + exit(1); + } + } + } + + /** + * Raised when an uncaught PHP exception occurs. + * + * An event handler can set the {@link CExceptionEvent::handled handled} + * property of the event parameter to be true to indicate no further error + * handling is needed. Otherwise, the {@link getErrorHandler errorHandler} + * application component will continue processing the error. + * + * @param CExceptionEvent $event event parameter + */ + public function onException($event) + { + $this->raiseEvent('onException', $event); + } + + /** + * Raised when a PHP execution error occurs. + * + * An event handler can set the {@link CErrorEvent::handled handled} + * property of the event parameter to be true to indicate no further error + * handling is needed. Otherwise, the {@link getErrorHandler errorHandler} + * application component will continue processing the error. + * + * @param CErrorEvent $event event parameter + */ + public function onError($event) + { + $this->raiseEvent('onError', $event); + } + + /** + * Displays the captured PHP error. + * This method displays the error in HTML when there is + * no active error handler. + * @param integer $code error code + * @param string $message error message + * @param string $file error file + * @param string $line error line + */ + public function displayError($code, $message, $file, $line) + { + if (YII_DEBUG) + { + echo "

PHP Error [$code]

\n"; + echo "

$message ($file:$line)

\n"; + echo '
';
+
+			$trace = debug_backtrace();
+			// skip the first 3 stacks as they do not tell the error position
+			if (count($trace) > 3)
+				$trace = array_slice($trace, 3);
+			foreach ($trace as $i => $t)
+			{
+				if (!isset($t['file']))
+					$t['file'] = 'unknown';
+				if (!isset($t['line']))
+					$t['line'] = 0;
+				if (!isset($t['function']))
+					$t['function'] = 'unknown';
+				echo "#$i  {$t['file']}( {$t['line']}): ";
+				if (isset($t['object']) && is_object($t['object']))
+					echo get_class($t['object']) . '->';
+				echo " {$t['function']}()\n";
+			}
+
+			echo '
'; + } + else + { + echo "

PHP Error [$code]

\n"; + echo "

$message

\n"; + } + } + + /** + * Displays the uncaught PHP exception. + * This method displays the exception in HTML when there is + * no active error handler. + * @param Exception $exception the uncaught exception + */ + public function displayException($exception) + { + if (YII_DEBUG) + { + echo '

' . get_class($exception) . "

\n"; + echo '

' . $exception->getMessage() . ' (' . $exception->getFile() . ':' . $exception->getLine() . ')

'; + echo '
' . $exception->getTraceAsString() . '
'; + } + else + { + echo '

' . get_class($exception) . "

\n"; + echo '

' . $exception->getMessage() . '

'; + } + } + + /** + * Initializes the class autoloader and error handlers. + */ + protected function initSystemHandlers() + { + if (YII_ENABLE_EXCEPTION_HANDLER) + set_exception_handler(array($this, 'handleException')); + if (YII_ENABLE_ERROR_HANDLER) + set_error_handler(array($this, 'handleError'), error_reporting()); + } + + /** + * Registers the core application components. + * @see setComponents + */ + protected function registerCoreComponents() + { + $components = array( + 'coreMessages' => array( + 'class' => 'CPhpMessageSource', + 'language' => 'en_us', + 'basePath' => YII_PATH . DIRECTORY_SEPARATOR . 'messages', + ), + 'db' => array( + 'class' => 'CDbConnection', + ), + 'messages' => array( + 'class' => 'CPhpMessageSource', + ), + 'errorHandler' => array( + 'class' => 'CErrorHandler', + ), + 'securityManager' => array( + 'class' => 'CSecurityManager', + ), + 'statePersister' => array( + 'class' => 'CStatePersister', + ), + 'urlManager' => array( + 'class' => 'CUrlManager', + ), + 'request' => array( + 'class' => 'CHttpRequest', + ), + 'format' => array( + 'class' => 'CFormatter', + ), + ); + + $this->setComponents($components); + } +} diff --git a/framework/base/ApplicationComponent.php b/framework/base/ApplicationComponent.php new file mode 100644 index 0000000..bf8ee0e --- /dev/null +++ b/framework/base/ApplicationComponent.php @@ -0,0 +1,55 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2012 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * ApplicationComponent is the base class for application component classes. + * + * ApplicationComponent implements the basic methods required by {@link IApplicationComponent}. + * + * When developing an application component, try to put application component initialization code in + * the {@link init()} method instead of the constructor. This has the advantage that + * the application component can be customized through application configuration. + * + * @author Qiang Xue + * @since 2.0 + */ +abstract class ApplicationComponent extends Component +{ + /** + * @var array the behaviors that should be attached to this component. + * The behaviors will be attached to the component when {@link init} is called. + * Please refer to {@link CModel::behaviors} on how to specify the value of this property. + * @since 1.0.2 + */ + public $behaviors = array(); + + private $_initialized = false; + + /** + * Initializes the application component. + * This method is required by {@link IApplicationComponent} and is invoked by application. + * If you override this method, make sure to call the parent implementation + * so that the application component can be marked as initialized. + */ + public function init() + { + $this->attachBehaviors($this->behaviors); + $this->_initialized = true; + } + + /** + * Checks if this application component bas been initialized. + * @return boolean whether this application component has been initialized (ie, {@link init()} is invoked). + */ + public function getIsInitialized() + { + return $this->_initialized; + } +} diff --git a/framework/base/Module.php b/framework/base/Module.php new file mode 100644 index 0000000..f6a408f --- /dev/null +++ b/framework/base/Module.php @@ -0,0 +1,501 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2012 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\base; + +/** + * Module is the base class for module and application classes. + * + * Module mainly manages application components and sub-modules. + * + * @author Qiang Xue + * @since 2.0 + */ +abstract class Module extends Component +{ + /** + * @var array the IDs of the application components that should be preloaded. + */ + public $preload = array(); + /** + * @var array the behaviors that should be attached to the module. + * The behaviors will be attached to the module when {@link init} is called. + * Please refer to {@link CModel::behaviors} on how to specify the value of this property. + */ + public $behaviors = array(); + + private $_id; + private $_parentModule; + private $_basePath; + private $_modulePath; + private $_params; + private $_modules = array(); + private $_moduleConfig = array(); + private $_components = array(); + private $_componentConfig = array(); + + + /** + * Constructor. + * @param string $id the ID of this module + * @param CModule $parent the parent module (if any) + * @param mixed $config the module configuration. It can be either an array or + * the path of a PHP file returning the configuration array. + */ + public function __construct($id, $parent, $config = null) + { + $this->_id = $id; + $this->_parentModule = $parent; + + // set basePath at early as possible to avoid trouble + if (is_string($config)) + $config = require($config); + if (isset($config['basePath'])) + { + $this->setBasePath($config['basePath']); + unset($config['basePath']); + } + Yii::setPathOfAlias($id, $this->getBasePath()); + + $this->preinit(); + + $this->configure($config); + $this->attachBehaviors($this->behaviors); + $this->preloadComponents(); + + $this->init(); + } + + /** + * Getter magic method. + * This method is overridden to support accessing application components + * like reading module properties. + * @param string $name application component or property name + * @return mixed the named property value + */ + public function __get($name) + { + if ($this->hasComponent($name)) + return $this->getComponent($name); + else + return parent::__get($name); + } + + /** + * Checks if a property value is null. + * This method overrides the parent implementation by checking + * if the named application component is loaded. + * @param string $name the property name or the event name + * @return boolean whether the property value is null + */ + public function __isset($name) + { + if ($this->hasComponent($name)) + return $this->getComponent($name) !== null; + else + return parent::__isset($name); + } + + /** + * Returns the module ID. + * @return string the module ID. + */ + public function getId() + { + return $this->_id; + } + + /** + * Sets the module ID. + * @param string $id the module ID + */ + public function setId($id) + { + $this->_id = $id; + } + + /** + * Returns the root directory of the module. + * @return string the root directory of the module. Defaults to the directory containing the module class. + */ + public function getBasePath() + { + if ($this->_basePath === null) + { + $class = new ReflectionClass(get_class($this)); + $this->_basePath = dirname($class->getFileName()); + } + return $this->_basePath; + } + + /** + * Sets the root directory of the module. + * This method can only be invoked at the beginning of the constructor. + * @param string $path the root directory of the module. + * @throws CException if the directory does not exist. + */ + public function setBasePath($path) + { + if (($this->_basePath = realpath($path)) === false || !is_dir($this->_basePath)) + throw new CException(Yii::t('yii', 'Base path "{path}" is not a valid directory.', + array('{path}' => $path))); + } + + /** + * Returns user-defined parameters. + * @return CAttributeCollection the list of user-defined parameters + */ + public function getParams() + { + if ($this->_params !== null) + return $this->_params; + else + { + $this->_params = new CAttributeCollection; + $this->_params->caseSensitive = true; + return $this->_params; + } + } + + /** + * Sets user-defined parameters. + * @param array $value user-defined parameters. This should be in name-value pairs. + */ + public function setParams($value) + { + $params = $this->getParams(); + foreach ($value as $k => $v) + $params->add($k, $v); + } + + /** + * Returns the directory that contains the application modules. + * @return string the directory that contains the application modules. Defaults to the 'modules' subdirectory of {@link basePath}. + */ + public function getModulePath() + { + if ($this->_modulePath !== null) + return $this->_modulePath; + else + return $this->_modulePath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'modules'; + } + + /** + * Sets the directory that contains the application modules. + * @param string $value the directory that contains the application modules. + * @throws CException if the directory is invalid + */ + public function setModulePath($value) + { + if (($this->_modulePath = realpath($value)) === false || !is_dir($this->_modulePath)) + throw new CException(Yii::t('yii', 'The module path "{path}" is not a valid directory.', + array('{path}' => $value))); + } + + /** + * Sets the aliases that are used in the module. + * @param array $aliases list of aliases to be imported + */ + public function setImport($aliases) + { + foreach ($aliases as $alias) + Yii::import($alias); + } + + /** + * Defines the root aliases. + * @param array $mappings list of aliases to be defined. The array keys are root aliases, + * while the array values are paths or aliases corresponding to the root aliases. + * For example, + *
+	 * array(
+	 *    'models'=>'application.models',              // an existing alias
+	 *    'extensions'=>'application.extensions',      // an existing alias
+	 *    'backend'=>dirname(__FILE__).'/../backend',  // a directory
+	 * )
+	 * 
+ * @since 1.0.5 + */ + public function setAliases($mappings) + { + foreach ($mappings as $name => $alias) + { + if (($path = Yii::getPathOfAlias($alias)) !== false) + Yii::setPathOfAlias($name, $path); + else + Yii::setPathOfAlias($name, $alias); + } + } + + /** + * Returns the parent module. + * @return CModule the parent module. Null if this module does not have a parent. + */ + public function getParentModule() + { + return $this->_parentModule; + } + + /** + * Retrieves the named application module. + * The module has to be declared in {@link modules}. A new instance will be created + * when calling this method with the given ID for the first time. + * @param string $id application module ID (case-sensitive) + * @return CModule the module instance, null if the module is disabled or does not exist. + */ + public function getModule($id) + { + if (isset($this->_modules[$id]) || array_key_exists($id, $this->_modules)) + return $this->_modules[$id]; + elseif (isset($this->_moduleConfig[$id])) + { + $config = $this->_moduleConfig[$id]; + if (!isset($config['enabled']) || $config['enabled']) + { + Yii::trace("Loading \"$id\" module", 'system.base.CModule'); + $class = $config['class']; + unset($config['class'], $config['enabled']); + if ($this === Yii::app()) + $module = Yii::createComponent($class, $id, null, $config); + else + $module = Yii::createComponent($class, $this->getId() . '/' . $id, $this, $config); + return $this->_modules[$id] = $module; + } + } + } + + /** + * Returns a value indicating whether the specified module is installed. + * @param string $id the module ID + * @return boolean whether the specified module is installed. + * @since 1.1.2 + */ + public function hasModule($id) + { + return isset($this->_moduleConfig[$id]) || isset($this->_modules[$id]); + } + + /** + * Returns the configuration of the currently installed modules. + * @return array the configuration of the currently installed modules (module ID => configuration) + */ + public function getModules() + { + return $this->_moduleConfig; + } + + /** + * Configures the sub-modules of this module. + * + * Call this method to declare sub-modules and configure them with their initial property values. + * The parameter should be an array of module configurations. Each array element represents a single module, + * which can be either a string representing the module ID or an ID-configuration pair representing + * a module with the specified ID and the initial property values. + * + * For example, the following array declares two modules: + *
+	 * array(
+	 *     'admin',                // a single module ID
+	 *     'payment'=>array(       // ID-configuration pair
+	 *         'server'=>'paymentserver.com',
+	 *     ),
+	 * )
+	 * 
+ * + * By default, the module class is determined using the expression ucfirst($moduleID).'Module'. + * And the class file is located under modules/$moduleID. + * You may override this default by explicitly specifying the 'class' option in the configuration. + * + * You may also enable or disable a module by specifying the 'enabled' option in the configuration. + * + * @param array $modules module configurations. + */ + public function setModules($modules) + { + foreach ($modules as $id => $module) + { + if (is_int($id)) + { + $id = $module; + $module = array(); + } + if (!isset($module['class'])) + { + Yii::setPathOfAlias($id, $this->getModulePath() . DIRECTORY_SEPARATOR . $id); + $module['class'] = $id . '.' . ucfirst($id) . 'Module'; + } + + if (isset($this->_moduleConfig[$id])) + $this->_moduleConfig[$id] = CMap::mergeArray($this->_moduleConfig[$id], $module); + else + $this->_moduleConfig[$id] = $module; + } + } + + /** + * Checks whether the named component exists. + * @param string $id application component ID + * @return boolean whether the named application component exists (including both loaded and disabled.) + */ + public function hasComponent($id) + { + return isset($this->_components[$id]) || isset($this->_componentConfig[$id]); + } + + /** + * Retrieves the named application component. + * @param string $id application component ID (case-sensitive) + * @param boolean $createIfNull whether to create the component if it doesn't exist yet. This parameter + * has been available since version 1.0.6. + * @return IApplicationComponent the application component instance, null if the application component is disabled or does not exist. + * @see hasComponent + */ + public function getComponent($id, $createIfNull = true) + { + if (isset($this->_components[$id])) + return $this->_components[$id]; + elseif (isset($this->_componentConfig[$id]) && $createIfNull) + { + $config = $this->_componentConfig[$id]; + if (!isset($config['enabled']) || $config['enabled']) + { + Yii::trace("Loading \"$id\" application component", 'system.CModule'); + unset($config['enabled']); + $component = Yii::createComponent($config); + $component->init(); + return $this->_components[$id] = $component; + } + } + } + + /** + * Puts a component under the management of the module. + * The component will be initialized by calling its {@link CApplicationComponent::init() init()} + * method if it has not done so. + * @param string $id component ID + * @param IApplicationComponent $component the component to be added to the module. + * If this parameter is null, it will unload the component from the module. + */ + public function setComponent($id, $component) + { + if ($component === null) + unset($this->_components[$id]); + else + { + $this->_components[$id] = $component; + if (!$component->getIsInitialized()) + $component->init(); + } + } + + /** + * Returns the application components. + * @param boolean $loadedOnly whether to return the loaded components only. If this is set false, + * then all components specified in the configuration will be returned, whether they are loaded or not. + * Loaded components will be returned as objects, while unloaded components as configuration arrays. + * This parameter has been available since version 1.1.3. + * @return array the application components (indexed by their IDs) + */ + public function getComponents($loadedOnly = true) + { + if ($loadedOnly) + return $this->_components; + else + return array_merge($this->_componentConfig, $this->_components); + } + + /** + * Sets the application components. + * + * When a configuration is used to specify a component, it should consist of + * the component's initial property values (name-value pairs). Additionally, + * a component can be enabled (default) or disabled by specifying the 'enabled' value + * in the configuration. + * + * If a configuration is specified with an ID that is the same as an existing + * component or configuration, the existing one will be replaced silently. + * + * The following is the configuration for two components: + *
+	 * array(
+	 *     'db'=>array(
+	 *         'class'=>'CDbConnection',
+	 *         'connectionString'=>'sqlite:path/to/file.db',
+	 *     ),
+	 *     'cache'=>array(
+	 *         'class'=>'CDbCache',
+	 *         'connectionID'=>'db',
+	 *         'enabled'=>!YII_DEBUG,  // enable caching in non-debug mode
+	 *     ),
+	 * )
+	 * 
+ * + * @param array $components application components(id=>component configuration or instances) + * @param boolean $merge whether to merge the new component configuration with the existing one. + * Defaults to true, meaning the previously registered component configuration of the same ID + * will be merged with the new configuration. If false, the existing configuration will be replaced completely. + */ + public function setComponents($components, $merge = true) + { + foreach ($components as $id => $component) + { + if ($component instanceof IApplicationComponent) + $this->setComponent($id, $component); + elseif (isset($this->_componentConfig[$id]) && $merge) + $this->_componentConfig[$id] = CMap::mergeArray($this->_componentConfig[$id], $component); + else + $this->_componentConfig[$id] = $component; + } + } + + /** + * Configures the module with the specified configuration. + * @param array $config the configuration array + */ + public function configure($config) + { + if (is_array($config)) + { + foreach ($config as $key => $value) + $this->$key = $value; + } + } + + /** + * Loads static application components. + */ + protected function preloadComponents() + { + foreach ($this->preload as $id) + $this->getComponent($id); + } + + /** + * Preinitializes the module. + * This method is called at the beginning of the module constructor. + * You may override this method to do some customized preinitialization work. + * Note that at this moment, the module is not configured yet. + * @see init + */ + protected function preinit() + { + } + + /** + * Initializes the module. + * This method is called at the end of the module constructor. + * Note that at this moment, the module has been configured, the behaviors + * have been attached and the application components have been registered. + * @see preinit + */ + protected function init() + { + } +}