diff options
author | emkael <emkael@tlen.pl> | 2016-02-24 23:18:07 +0100 |
---|---|---|
committer | emkael <emkael@tlen.pl> | 2016-02-24 23:18:07 +0100 |
commit | 6f7fdef0f500cd4bb540affd3bc1482243f337c1 (patch) | |
tree | 4853eecd0769a903e6130c1896e1d070848150dd /lib/prado/framework/TApplication.php | |
parent | 61f2ea48a4e11cb5fb941b3783e19c9e9ef38a45 (diff) |
* Prado 3.3.0
Diffstat (limited to 'lib/prado/framework/TApplication.php')
-rw-r--r-- | lib/prado/framework/TApplication.php | 1874 |
1 files changed, 1874 insertions, 0 deletions
diff --git a/lib/prado/framework/TApplication.php b/lib/prado/framework/TApplication.php new file mode 100644 index 0000000..1ae868d --- /dev/null +++ b/lib/prado/framework/TApplication.php @@ -0,0 +1,1874 @@ +<?php +/** + * TApplication class file + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link https://github.com/pradosoft/prado + * @copyright Copyright © 2005-2015 The PRADO Group + * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT + * @package System + */ + +/** + * Includes core interfaces essential for TApplication class + */ +require_once(PRADO_DIR.'/interfaces.php'); + +/** + * Includes core classes essential for TApplication class + */ +Prado::using('System.TApplicationComponent'); +Prado::using('System.TModule'); +Prado::using('System.TService'); +Prado::using('System.Exceptions.TErrorHandler'); +Prado::using('System.Caching.TCache'); +Prado::using('System.IO.TTextWriter'); +Prado::using('System.Collections.TPriorityList'); +Prado::using('System.Collections.TPriorityMap'); +Prado::using('System.Collections.TStack'); +Prado::using('System.Xml.TXmlDocument'); +Prado::using('System.Security.TAuthorizationRule'); +Prado::using('System.Security.TSecurityManager'); +Prado::using('System.Web.THttpUtility'); +Prado::using('System.Web.Javascripts.TJavaScript'); +Prado::using('System.Web.THttpRequest'); +Prado::using('System.Web.THttpResponse'); +Prado::using('System.Web.THttpSession'); +Prado::using('System.Web.Services.TPageService'); +Prado::using('System.Web.TAssetManager'); +Prado::using('System.I18N.TGlobalization'); + +/** + * TApplication class. + * + * TApplication coordinates modules and services, and serves as a configuration + * context for all Prado components. + * + * TApplication uses a configuration file to specify the settings of + * the application, the modules, the services, the parameters, and so on. + * + * TApplication adopts a modular structure. A TApplication instance is a composition + * of multiple modules. A module is an instance of class implementing + * {@link IModule} interface. Each module accomplishes certain functionalities + * that are shared by all Prado components in an application. + * There are default modules and user-defined modules. The latter offers extreme + * flexibility of extending TApplication in a plug-and-play fashion. + * Modules cooperate with each other to serve a user request by following + * a sequence of lifecycles predefined in TApplication. + * + * TApplication has four modes that can be changed by setting {@link setMode Mode} + * property (in the application configuration file). + * - <b>Off</b> mode will prevent the application from serving user requests. + * - <b>Debug</b> mode is mainly used during application development. It ensures + * the cache is always up-to-date if caching is enabled. It also allows + * exceptions are displayed with rich context information if they occur. + * - <b>Normal</b> mode is mainly used during production stage. Exception information + * will only be recorded in system error logs. The cache is ensured to be + * up-to-date if it is enabled. + * - <b>Performance</b> mode is similar to <b>Normal</b> mode except that it + * does not ensure the cache is up-to-date. + * + * TApplication dispatches each user request to a particular service which + * finishes the actual work for the request with the aid from the application + * modules. + * + * TApplication maintains a lifecycle with the following stages: + * - [construct] : construction of the application instance + * - [initApplication] : load application configuration and instantiate modules and the requested service + * - onBeginRequest : this event happens right after application initialization + * - onAuthentication : this event happens when authentication is needed for the current request + * - onAuthenticationComplete : this event happens right after the authentication is done for the current request + * - onAuthorization : this event happens when authorization is needed for the current request + * - onAuthorizationComplete : this event happens right after the authorization is done for the current request + * - onLoadState : this event happens when application state needs to be loaded + * - onLoadStateComplete : this event happens right after the application state is loaded + * - onPreRunService : this event happens right before the requested service is to run + * - runService : the requested service runs + * - onSaveState : this event happens when application needs to save its state + * - onSaveStateComplete : this event happens right after the application saves its state + * - onPreFlushOutput : this event happens right before the application flushes output to client side. + * - flushOutput : the application flushes output to client side. + * - onEndRequest : this is the last stage a request is being completed + * - [destruct] : destruction of the application instance + * Modules and services can attach their methods to one or several of the above + * events and do appropriate processing when the events are raised. By this way, + * the application is able to coordinate the activities of modules and services + * in the above order. To terminate an application before the whole lifecycle + * completes, call {@link completeRequest}. + * + * Examples: + * - Create and run a Prado application: + * <code> + * $application=new TApplication($configFile); + * $application->run(); + * </code> + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @package System + * @since 3.0 + */ +class TApplication extends TComponent +{ + /** + * possible application mode. + * @deprecated deprecated since version 3.0.4 (use TApplicationMode constants instead) + */ + const STATE_OFF='Off'; + const STATE_DEBUG='Debug'; + const STATE_NORMAL='Normal'; + const STATE_PERFORMANCE='Performance'; + + /** + * Page service ID + */ + const PAGE_SERVICE_ID='page'; + /** + * Application configuration file name + */ + const CONFIG_FILE_XML='application.xml'; + /** + * File extension for external config files + */ + const CONFIG_FILE_EXT_XML='.xml'; + /** + * Configuration file type, application.xml and config.xml + */ + const CONFIG_TYPE_XML = 'xml'; + /** + * Application configuration file name + */ + const CONFIG_FILE_PHP='application.php'; + /** + * File extension for external config files + */ + const CONFIG_FILE_EXT_PHP='.php'; + /** + * Configuration file type, application.php and config.php + */ + const CONFIG_TYPE_PHP = 'php'; + /** + * Runtime directory name + */ + const RUNTIME_PATH='runtime'; + /** + * Config cache file + */ + const CONFIGCACHE_FILE='config.cache'; + /** + * Global data file + */ + const GLOBAL_FILE='global.cache'; + + /** + * @var array list of events that define application lifecycles + */ + private static $_steps=array( + 'onBeginRequest', + 'onLoadState', + 'onLoadStateComplete', + 'onAuthentication', + 'onAuthenticationComplete', + 'onAuthorization', + 'onAuthorizationComplete', + 'onPreRunService', + 'runService', + 'onSaveState', + 'onSaveStateComplete', + 'onPreFlushOutput', + 'flushOutput' + ); + + /** + * @var string application ID + */ + private $_id; + /** + * @var string unique application ID + */ + private $_uniqueID; + /** + * @var boolean whether the request is completed + */ + private $_requestCompleted=false; + /** + * @var integer application state + */ + private $_step; + /** + * @var array available services and their configurations indexed by service IDs + */ + private $_services; + /** + * @var IService current service instance + */ + private $_service; + /** + * @var array list of loaded application modules + */ + private $_modules=array(); + /** + * @var array list of application modules yet to be loaded + */ + private $_lazyModules=array(); + /** + * @var TMap list of application parameters + */ + private $_parameters; + /** + * @var string configuration file + */ + private $_configFile; + /** + * @var string configuration file extension + */ + private $_configFileExt; + /** + * @var string configuration type + */ + private $_configType; + /** + * @var string application base path + */ + private $_basePath; + /** + * @var string directory storing application state + */ + private $_runtimePath; + /** + * @var boolean if any global state is changed during the current request + */ + private $_stateChanged=false; + /** + * @var array global variables (persistent across sessions, requests) + */ + private $_globals=array(); + /** + * @var string cache file + */ + private $_cacheFile; + /** + * @var TErrorHandler error handler module + */ + private $_errorHandler; + /** + * @var THttpRequest request module + */ + private $_request; + /** + * @var THttpResponse response module + */ + private $_response; + /** + * @var THttpSession session module, could be null + */ + private $_session; + /** + * @var ICache cache module, could be null + */ + private $_cache; + /** + * @var IStatePersister application state persister + */ + private $_statePersister; + /** + * @var IUser user instance, could be null + */ + private $_user; + /** + * @var TGlobalization module, could be null + */ + private $_globalization; + /** + * @var TSecurityManager security manager module + */ + private $_security; + /** + * @var TAssetManager asset manager module + */ + private $_assetManager; + /** + * @var TAuthorizationRuleCollection collection of authorization rules + */ + private $_authRules; + /** + * @var TApplicationMode application mode + */ + private $_mode=TApplicationMode::Debug; + + /** + * @var string Customizable page service ID + */ + private $_pageServiceID = self::PAGE_SERVICE_ID; + + /** + * Constructor. + * Sets application base path and initializes the application singleton. + * Application base path refers to the root directory storing application + * data and code not directly accessible by Web users. + * By default, the base path is assumed to be the <b>protected</b> + * directory under the directory containing the current running script. + * @param string application base path or configuration file path. + * If the parameter is a file, it is assumed to be the application + * configuration file, and the directory containing the file is treated + * as the application base path. + * If it is a directory, it is assumed to be the application base path, + * and within that directory, a file named <b>application.xml</b> + * will be looked for. If found, the file is considered as the application + * configuration file. + * @param boolean whether to cache application configuration. Defaults to true. + * @throws TConfigurationException if configuration file cannot be read or the runtime path is invalid. + */ + public function __construct($basePath='protected',$cacheConfig=true, $configType=self::CONFIG_TYPE_XML) + { + // register application as a singleton + Prado::setApplication($this); + $this->setConfigurationType($configType); + $this->resolvePaths($basePath); + + if($cacheConfig) + $this->_cacheFile=$this->_runtimePath.DIRECTORY_SEPARATOR.self::CONFIGCACHE_FILE; + + // generates unique ID by hashing the runtime path + $this->_uniqueID=md5($this->_runtimePath); + $this->_parameters=new TMap; + $this->_services=array($this->getPageServiceID()=>array('TPageService',array(),null)); + + Prado::setPathOfAlias('Application',$this->_basePath); + } + + /** + * Resolves application-relevant paths. + * This method is invoked by the application constructor + * to determine the application configuration file, + * application root path and the runtime path. + * @param string the application root path or the application configuration file + * @see setBasePath + * @see setRuntimePath + * @see setConfigurationFile + */ + protected function resolvePaths($basePath) + { + // determine configuration path and file + if(empty($basePath) || ($basePath=realpath($basePath))===false) + throw new TConfigurationException('application_basepath_invalid',$basePath); + if(is_dir($basePath) && is_file($basePath.DIRECTORY_SEPARATOR.$this->getConfigurationFileName())) + $configFile=$basePath.DIRECTORY_SEPARATOR.$this->getConfigurationFileName(); + else if(is_file($basePath)) + { + $configFile=$basePath; + $basePath=dirname($configFile); + } + else + $configFile=null; + + // determine runtime path + $runtimePath=$basePath.DIRECTORY_SEPARATOR.self::RUNTIME_PATH; + if(is_writable($runtimePath)) + { + if($configFile!==null) + { + $runtimePath.=DIRECTORY_SEPARATOR.basename($configFile).'-'.Prado::getVersion(); + if(!is_dir($runtimePath)) + { + if(@mkdir($runtimePath)===false) + throw new TConfigurationException('application_runtimepath_failed',$runtimePath); + @chmod($runtimePath, PRADO_CHMOD); //make it deletable + } + $this->setConfigurationFile($configFile); + } + $this->setBasePath($basePath); + $this->setRuntimePath($runtimePath); + } + else + throw new TConfigurationException('application_runtimepath_invalid',$runtimePath); + + } + + /** + * Executes the lifecycles of the application. + * This is the main entry function that leads to the running of the whole + * Prado application. + */ + public function run() + { + try + { + $this->initApplication(); + $n=count(self::$_steps); + $this->_step=0; + $this->_requestCompleted=false; + while($this->_step<$n) + { + if($this->_mode===self::STATE_OFF) + throw new THttpException(503,'application_unavailable'); + if($this->_requestCompleted) + break; + $method=self::$_steps[$this->_step]; + Prado::trace("Executing $method()",'System.TApplication'); + $this->$method(); + $this->_step++; + } + } + catch(Exception $e) + { + $this->onError($e); + } + $this->onEndRequest(); + } + + /** + * Completes current request processing. + * This method can be used to exit the application lifecycles after finishing + * the current cycle. + */ + public function completeRequest() + { + $this->_requestCompleted=true; + } + + /** + * @return boolean whether the current request is processed. + */ + public function getRequestCompleted() + { + return $this->_requestCompleted; + } + + /** + * Returns a global value. + * + * A global value is one that is persistent across users sessions and requests. + * @param string the name of the value to be returned + * @param mixed the default value. If $key is not found, $defaultValue will be returned + * @return mixed the global value corresponding to $key + */ + public function getGlobalState($key,$defaultValue=null) + { + return isset($this->_globals[$key])?$this->_globals[$key]:$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 the name of the value to be set + * @param mixed the global value to be set + * @param mixed the default value. If $key is not found, $defaultValue will be returned + * @param boolean wheter to force an immediate GlobalState save. defaults to false + */ + public function setGlobalState($key,$value,$defaultValue=null,$forceSave=false) + { + $this->_stateChanged=true; + if($value===$defaultValue) + unset($this->_globals[$key]); + else + $this->_globals[$key]=$value; + if($forceSave) + $this->saveGlobals(); + } + + /** + * Clears a global value. + * + * The value cleared will no longer be available in this request and the following requests. + * @param string the name of the value to be cleared + */ + public function clearGlobalState($key) + { + $this->_stateChanged=true; + unset($this->_globals[$key]); + } + + /** + * Loads global values from persistent storage. + * This method is invoked when {@link onLoadState OnLoadState} event is raised. + * After this method, values that are stored in previous requests become + * available to the current request via {@link getGlobalState}. + */ + protected function loadGlobals() + { + $this->_globals=$this->getApplicationStatePersister()->load(); + } + + /** + * Saves global values into persistent storage. + * This method is invoked when {@link onSaveState OnSaveState} event is raised. + */ + protected function saveGlobals() + { + if($this->_stateChanged) + { + $this->_stateChanged=false; + $this->getApplicationStatePersister()->save($this->_globals); + } + } + + /** + * @return string application ID + */ + public function getID() + { + return $this->_id; + } + + /** + * @param string application ID + */ + public function setID($value) + { + $this->_id=$value; + } + + /** + * @return string page service ID + */ + public function getPageServiceID() + { + return $this->_pageServiceID; + } + + /** + * @param string page service ID + */ + public function setPageServiceID($value) + { + $this->_pageServiceID=$value; + } + + /** + * @return string an ID that uniquely identifies this Prado application from the others + */ + public function getUniqueID() + { + return $this->_uniqueID; + } + + /** + * @return TApplicationMode application mode. Defaults to TApplicationMode::Debug. + */ + public function getMode() + { + return $this->_mode; + } + + /** + * @param TApplicationMode application mode + */ + public function setMode($value) + { + $this->_mode=TPropertyValue::ensureEnum($value,'TApplicationMode'); + } + + /** + * @return string the directory containing the application configuration file (absolute path) + */ + public function getBasePath() + { + return $this->_basePath; + } + + /** + * @param string the directory containing the application configuration file + */ + public function setBasePath($value) + { + $this->_basePath=$value; + } + + /** + * @return string the application configuration file (absolute path) + */ + public function getConfigurationFile() + { + return $this->_configFile; + } + + /** + * @param string the application configuration file (absolute path) + */ + public function setConfigurationFile($value) + { + $this->_configFile=$value; + } + + /** + * @return string the application configuration file (absolute path) + */ + public function getConfigurationType() + { + return $this->_configType; + } + + /** + * @param string the application configuration type. 'xml' and 'php' are valid values + */ + public function setConfigurationType($value) + { + $this->_configType = $value; + } + + /** + * @return string the application configuration type. default is 'xml' + */ + public function getConfigurationFileExt() + { + if($this->_configFileExt===null) + { + switch($this->_configType) + { + case TApplication::CONFIG_TYPE_PHP: + $this->_configFileExt = TApplication::CONFIG_FILE_EXT_PHP; + break; + default: + $this->_configFileExt = TApplication::CONFIG_FILE_EXT_XML; + } + } + return $this->_configFileExt; + } + + /** + * @return string the default configuration file name + */ + public function getConfigurationFileName() + { + static $fileName; + if($fileName == null) + { + switch($this->_configType) + { + case TApplication::CONFIG_TYPE_PHP: + $fileName = TApplication::CONFIG_FILE_PHP; + break; + default: + $fileName = TApplication::CONFIG_FILE_XML; + } + } + return $fileName; + } + + /** + * @return string the directory storing cache data and application-level persistent data. (absolute path) + */ + public function getRuntimePath() + { + return $this->_runtimePath; + } + + /** + * @param string the directory storing cache data and application-level persistent data. (absolute path) + */ + public function setRuntimePath($value) + { + $this->_runtimePath=$value; + if($this->_cacheFile) + $this->_cacheFile=$this->_runtimePath.DIRECTORY_SEPARATOR.self::CONFIGCACHE_FILE; + // generates unique ID by hashing the runtime path + $this->_uniqueID=md5($this->_runtimePath); + } + + /** + * @return IService the currently requested service + */ + public function getService() + { + return $this->_service; + } + + /** + * @param IService the currently requested service + */ + public function setService($value) + { + $this->_service=$value; + } + + /** + * Adds a module to application. + * Note, this method does not do module initialization. + * @param string ID of the module + * @param IModule module object or null if the module has not been loaded yet + */ + public function setModule($id,IModule $module=null) + { + if(isset($this->_modules[$id])) + throw new TConfigurationException('application_moduleid_duplicated',$id); + else + $this->_modules[$id]=$module; + } + + /** + * @return IModule the module with the specified ID, null if not found + */ + public function getModule($id) + { + if(!array_key_exists($id, $this->_modules)) + return null; + + // force loading of a lazy module + if($this->_modules[$id]===null) + { + $module = $this->internalLoadModule($id, true); + $module[0]->init($module[1]); + } + + return $this->_modules[$id]; + } + + /** + * Returns a list of application modules indexed by module IDs. + * Modules that have not been loaded yet are returned as null objects. + * @return array list of loaded application modules, indexed by module IDs + */ + public function getModules() + { + return $this->_modules; + } + + /** + * Returns the list of application parameters. + * Since the parameters are returned as a {@link TMap} object, you may use + * the returned result to access, add or remove individual parameters. + * @return TMap the list of application parameters + */ + public function getParameters() + { + return $this->_parameters; + } + + /** + * @return THttpRequest the request module + */ + public function getRequest() + { + if(!$this->_request) + { + $this->_request=new THttpRequest; + $this->_request->init(null); + } + return $this->_request; + } + + /** + * @param THttpRequest the request module + */ + public function setRequest(THttpRequest $request) + { + $this->_request=$request; + } + + /** + * @return THttpResponse the response module + */ + public function getResponse() + { + if(!$this->_response) + { + $this->_response=new THttpResponse; + $this->_response->init(null); + } + return $this->_response; + } + + /** + * @param THttpRequest the request module + */ + public function setResponse(THttpResponse $response) + { + $this->_response=$response; + } + + /** + * @return THttpSession the session module, null if session module is not installed + */ + public function getSession() + { + if(!$this->_session) + { + $this->_session=new THttpSession; + $this->_session->init(null); + } + return $this->_session; + } + + /** + * @param THttpSession the session module + */ + public function setSession(THttpSession $session) + { + $this->_session=$session; + } + + /** + * @return TErrorHandler the error handler module + */ + public function getErrorHandler() + { + if(!$this->_errorHandler) + { + $this->_errorHandler=new TErrorHandler; + $this->_errorHandler->init(null); + } + return $this->_errorHandler; + } + + /** + * @param TErrorHandler the error handler module + */ + public function setErrorHandler(TErrorHandler $handler) + { + $this->_errorHandler=$handler; + } + + /** + * @return TSecurityManager the security manager module + */ + public function getSecurityManager() + { + if(!$this->_security) + { + $this->_security=new TSecurityManager; + $this->_security->init(null); + } + return $this->_security; + } + + /** + * @param TSecurityManager the security manager module + */ + public function setSecurityManager(TSecurityManager $sm) + { + $this->_security=$sm; + } + + /** + * @return TAssetManager asset manager + */ + public function getAssetManager() + { + if(!$this->_assetManager) + { + $this->_assetManager=new TAssetManager; + $this->_assetManager->init(null); + } + return $this->_assetManager; + } + + /** + * @param TAssetManager asset manager + */ + public function setAssetManager(TAssetManager $value) + { + $this->_assetManager=$value; + } + + /** + * @return IStatePersister application state persister + */ + public function getApplicationStatePersister() + { + if(!$this->_statePersister) + { + $this->_statePersister=new TApplicationStatePersister; + $this->_statePersister->init(null); + } + return $this->_statePersister; + } + + /** + * @param IStatePersister application state persister + */ + public function setApplicationStatePersister(IStatePersister $persister) + { + $this->_statePersister=$persister; + } + + /** + * @return ICache the cache module, null if cache module is not installed + */ + public function getCache() + { + return $this->_cache; + } + + /** + * @param ICache the cache module + */ + public function setCache(ICache $cache) + { + $this->_cache=$cache; + } + + /** + * @return IUser the application user + */ + public function getUser() + { + return $this->_user; + } + + /** + * @param IUser the application user + */ + public function setUser(IUser $user) + { + $this->_user=$user; + } + + /** + * @param boolean whether to create globalization if it does not exist + * @return TGlobalization globalization module + */ + public function getGlobalization($createIfNotExists=true) + { + if($this->_globalization===null && $createIfNotExists) + { + $this->_globalization=new TGlobalization; + $this->_globalization->init(null); + } + return $this->_globalization; + } + + /** + * @param TGlobalization globalization module + */ + public function setGlobalization(TGlobalization $glob) + { + $this->_globalization=$glob; + } + + /** + * @return TAuthorizationRuleCollection list of authorization rules for the current request + */ + public function getAuthorizationRules() + { + if($this->_authRules===null) + $this->_authRules=new TAuthorizationRuleCollection; + return $this->_authRules; + } + + protected function getApplicationConfigurationClass() + { + return 'TApplicationConfiguration'; + } + + protected function internalLoadModule($id, $force=false) + { + list($moduleClass, $initProperties, $configElement)=$this->_lazyModules[$id]; + if(isset($initProperties['lazy']) && $initProperties['lazy'] && !$force) + { + Prado::trace("Postponed loading of lazy module $id ({$moduleClass})",'System.TApplication'); + $this->setModule($id, null); + return null; + } + + Prado::trace("Loading module $id ({$moduleClass})",'System.TApplication'); + $module=Prado::createComponent($moduleClass); + foreach($initProperties as $name=>$value) + { + if($name==='lazy') continue; + $module->setSubProperty($name,$value); + } + $this->setModule($id,$module); + // keep the key to avoid reuse of the old module id + $this->_lazyModules[$id]=null; + + return array($module,$configElement); + } + /** + * Applies an application configuration. + * @param TApplicationConfiguration the configuration + * @param boolean whether the configuration is specified within a service. + */ + public function applyConfiguration($config,$withinService=false) + { + if($config->getIsEmpty()) + return; + + // set path aliases and using namespaces + foreach($config->getAliases() as $alias=>$path) + Prado::setPathOfAlias($alias,$path); + foreach($config->getUsings() as $using) + Prado::using($using); + + // set application properties + if(!$withinService) + { + foreach($config->getProperties() as $name=>$value) + $this->setSubProperty($name,$value); + } + + if(empty($this->_services)) + $this->_services=array($this->getPageServiceID()=>array('TPageService',array(),null)); + + // load parameters + foreach($config->getParameters() as $id=>$parameter) + { + if(is_array($parameter)) + { + $component=Prado::createComponent($parameter[0]); + foreach($parameter[1] as $name=>$value) + $component->setSubProperty($name,$value); + $this->_parameters->add($id,$component); + } + else + $this->_parameters->add($id,$parameter); + } + + // load and init modules specified in app config + $modules=array(); + foreach($config->getModules() as $id=>$moduleConfig) + { + if(!is_string($id)) + $id='_module'.count($this->_lazyModules); + $this->_lazyModules[$id]=$moduleConfig; + if($module = $this->internalLoadModule($id)) + $modules[]=$module; + } + foreach($modules as $module) + $module[0]->init($module[1]); + + // load service + foreach($config->getServices() as $serviceID=>$serviceConfig) + $this->_services[$serviceID]=$serviceConfig; + + // external configurations + foreach($config->getExternalConfigurations() as $filePath=>$condition) + { + if($condition!==true) + $condition=$this->evaluateExpression($condition); + if($condition) + { + if(($path=Prado::getPathOfNamespace($filePath,$this->getConfigurationFileExt()))===null || !is_file($path)) + throw new TConfigurationException('application_includefile_invalid',$filePath); + $cn=$this->getApplicationConfigurationClass(); + $c=new $cn; + $c->loadFromFile($path); + $this->applyConfiguration($c,$withinService); + } + } + } + + /** + * Loads configuration and initializes application. + * Configuration file will be read and parsed (if a valid cached version exists, + * it will be used instead). Then, modules are created and initialized; + * Afterwards, the requested service is created and initialized. + * @param string configuration file path (absolute or relative to current executing script) + * @param string cache file path, empty if no present or needed + * @throws TConfigurationException if module is redefined of invalid type, or service not defined or of invalid type + */ + protected function initApplication() + { + Prado::trace('Initializing application','System.TApplication'); + + if($this->_configFile!==null) + { + if($this->_cacheFile===null || @filemtime($this->_cacheFile)<filemtime($this->_configFile)) + { + $config=new TApplicationConfiguration; + $config->loadFromFile($this->_configFile); + if($this->_cacheFile!==null) + file_put_contents($this->_cacheFile,serialize($config),LOCK_EX); + } + else + $config=unserialize(file_get_contents($this->_cacheFile)); + + $this->applyConfiguration($config,false); + } + + if(($serviceID=$this->getRequest()->resolveRequest(array_keys($this->_services)))===null) + $serviceID=$this->getPageServiceID(); + + $this->startService($serviceID); + } + + /** + * Starts the specified service. + * The service instance will be created. Its properties will be initialized + * and the configurations will be applied, if any. + * @param string service ID + */ + public function startService($serviceID) + { + if(isset($this->_services[$serviceID])) + { + list($serviceClass,$initProperties,$configElement)=$this->_services[$serviceID]; + $service=Prado::createComponent($serviceClass); + if(!($service instanceof IService)) + throw new THttpException(500,'application_service_invalid',$serviceClass); + if(!$service->getEnabled()) + throw new THttpException(500,'application_service_unavailable',$serviceClass); + $service->setID($serviceID); + $this->setService($service); + + foreach($initProperties as $name=>$value) + $service->setSubProperty($name,$value); + + if($configElement!==null) + { + $config=new TApplicationConfiguration; + if($this->getConfigurationType()==self::CONFIG_TYPE_PHP) + $config->loadFromPhp($configElement,$this->getBasePath()); + else + $config->loadFromXml($configElement,$this->getBasePath()); + $this->applyConfiguration($config,true); + } + + $service->init($configElement); + } + else + throw new THttpException(500,'application_service_unknown',$serviceID); + } + + /** + * Raises OnError event. + * This method is invoked when an exception is raised during the lifecycles + * of the application. + * @param mixed event parameter + */ + public function onError($param) + { + Prado::log($param->getMessage(),TLogger::ERROR,'System.TApplication'); + $this->raiseEvent('OnError',$this,$param); + $this->getErrorHandler()->handleError($this,$param); + } + + /** + * Raises OnBeginRequest event. + * At the time when this method is invoked, application modules are loaded + * and initialized, user request is resolved and the corresponding service + * is loaded and initialized. The application is about to start processing + * the user request. + */ + public function onBeginRequest() + { + $this->raiseEvent('OnBeginRequest',$this,null); + } + + /** + * Raises OnAuthentication event. + * This method is invoked when the user request needs to be authenticated. + */ + public function onAuthentication() + { + $this->raiseEvent('OnAuthentication',$this,null); + } + + /** + * Raises OnAuthenticationComplete event. + * This method is invoked right after the user request is authenticated. + */ + public function onAuthenticationComplete() + { + $this->raiseEvent('OnAuthenticationComplete',$this,null); + } + + /** + * Raises OnAuthorization event. + * This method is invoked when the user request needs to be authorized. + */ + public function onAuthorization() + { + $this->raiseEvent('OnAuthorization',$this,null); + } + + /** + * Raises OnAuthorizationComplete event. + * This method is invoked right after the user request is authorized. + */ + public function onAuthorizationComplete() + { + $this->raiseEvent('OnAuthorizationComplete',$this,null); + } + + /** + * Raises OnLoadState event. + * This method is invoked when the application needs to load state (probably stored in session). + */ + public function onLoadState() + { + $this->loadGlobals(); + $this->raiseEvent('OnLoadState',$this,null); + } + + /** + * Raises OnLoadStateComplete event. + * This method is invoked right after the application state has been loaded. + */ + public function onLoadStateComplete() + { + $this->raiseEvent('OnLoadStateComplete',$this,null); + } + + /** + * Raises OnPreRunService event. + * This method is invoked right before the service is to be run. + */ + public function onPreRunService() + { + $this->raiseEvent('OnPreRunService',$this,null); + } + + /** + * Runs the requested service. + */ + public function runService() + { + if($this->_service) + $this->_service->run(); + } + + /** + * Raises OnSaveState event. + * This method is invoked when the application needs to save state (probably stored in session). + */ + public function onSaveState() + { + $this->raiseEvent('OnSaveState',$this,null); + $this->saveGlobals(); + } + + /** + * Raises OnSaveStateComplete event. + * This method is invoked right after the application state has been saved. + */ + public function onSaveStateComplete() + { + $this->raiseEvent('OnSaveStateComplete',$this,null); + } + + /** + * Raises OnPreFlushOutput event. + * This method is invoked right before the application flushes output to client. + */ + public function onPreFlushOutput() + { + $this->raiseEvent('OnPreFlushOutput',$this,null); + } + + /** + * Flushes output to client side. + * @param boolean whether to continue buffering after flush if buffering was active + */ + public function flushOutput($continueBuffering = true) + { + $this->getResponse()->flush($continueBuffering); + } + + /** + * Raises OnEndRequest event. + * This method is invoked when the application completes the processing of the request. + */ + public function onEndRequest() + { + $this->flushOutput(false); // flush all remaining content in the buffer + $this->saveGlobals(); // save global state + $this->raiseEvent('OnEndRequest',$this,null); + } +} + +/** + * TApplicationMode class. + * TApplicationMode defines the possible mode that an application can be set at by + * setting {@link TApplication::setMode Mode}. + * In particular, the following modes are defined + * - Off: the application is not running. Any request to the application will obtain an error. + * - Debug: the application is running in debug mode. + * - Normal: the application is running in normal production mode. + * - Performance: the application is running in performance mode. + * @author Qiang Xue <qiang.xue@gmail.com> + * @package System + * @since 3.0.4 + */ +class TApplicationMode extends TEnumerable +{ + const Off='Off'; + const Debug='Debug'; + const Normal='Normal'; + const Performance='Performance'; +} + + +/** + * TApplicationConfiguration class. + * + * This class is used internally by TApplication to parse and represent application configuration. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @author Carl G. Mathisen <carlgmathisen@gmail.com> + * @package System + * @since 3.0 + */ +class TApplicationConfiguration extends TComponent +{ + /** + * @var array list of application initial property values, indexed by property names + */ + private $_properties=array(); + /** + * @var array list of namespaces to be used + */ + private $_usings=array(); + /** + * @var array list of path aliases, indexed by alias names + */ + private $_aliases=array(); + /** + * @var array list of module configurations + */ + private $_modules=array(); + /** + * @var array list of service configurations + */ + private $_services=array(); + /** + * @var array list of parameters + */ + private $_parameters=array(); + /** + * @var array list of included configurations + */ + private $_includes=array(); + /** + * @var boolean whether this configuration contains actual stuff + */ + private $_empty=true; + + /** + * Parses the application configuration file. + * @param string configuration file name + * @throws TConfigurationException if there is any parsing error + */ + public function loadFromFile($fname) + { + if(Prado::getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP) + { + $fcontent = include $fname; + $this->loadFromPhp($fcontent,dirname($fname)); + } + else + { + $dom=new TXmlDocument; + $dom->loadFromFile($fname); + $this->loadFromXml($dom,dirname($fname)); + } + } + + /** + * @return boolean whether this configuration contains actual stuff + */ + public function getIsEmpty() + { + return $this->_empty; + } + + /** + * Parses the application configuration given in terms of a PHP array. + * @param array the PHP array + * @param string the context path (for specifying relative paths) + */ + public function loadFromPhp($config, $configPath) + { + // application properties + if(isset($config['application'])) + { + foreach($config['application'] as $name=>$value) + { + $this->_properties[$name]=$value; + } + $this->_empty = false; + } + + if(isset($config['paths']) && is_array($config['paths'])) + $this->loadPathsPhp($config['paths'],$configPath); + + if(isset($config['modules']) && is_array($config['modules'])) + $this->loadModulesPhp($config['modules'],$configPath); + + if(isset($config['services']) && is_array($config['services'])) + $this->loadServicesPhp($config['services'],$configPath); + + if(isset($config['parameters']) && is_array($config['parameters'])) + $this->loadParametersPhp($config['parameters'], $configPath); + + if(isset($config['includes']) && is_array($config['includes'])) + $this->loadExternalXml($config['includes'],$configPath); + } + + /** + * Parses the application configuration given in terms of a TXmlElement. + * @param TXmlElement the XML element + * @param string the context path (for specifying relative paths) + */ + public function loadFromXml($dom,$configPath) + { + // application properties + foreach($dom->getAttributes() as $name=>$value) + { + $this->_properties[$name]=$value; + $this->_empty=false; + } + + foreach($dom->getElements() as $element) + { + switch($element->getTagName()) + { + case 'paths': + $this->loadPathsXml($element,$configPath); + break; + case 'modules': + $this->loadModulesXml($element,$configPath); + break; + case 'services': + $this->loadServicesXml($element,$configPath); + break; + case 'parameters': + $this->loadParametersXml($element,$configPath); + break; + case 'include': + $this->loadExternalXml($element,$configPath); + break; + default: + //throw new TConfigurationException('appconfig_tag_invalid',$element->getTagName()); + break; + } + } + } + + /** + * Loads the paths PHP array + * @param array the paths PHP array + * @param string the context path (for specifying relative paths) + */ + protected function loadPathsPhp($pathsNode, $configPath) + { + if(isset($pathsNode['aliases']) && is_array($pathsNode['aliases'])) + { + foreach($pathsNode['aliases'] as $id=>$path) + { + $path=str_replace('\\','/',$path); + if(preg_match('/^\\/|.:\\/|.:\\\\/',$path)) // if absolute path + $p=realpath($path); + else + $p=realpath($configPath.DIRECTORY_SEPARATOR.$path); + if($p===false || !is_dir($p)) + throw new TConfigurationException('appconfig_aliaspath_invalid',$id,$path); + if(isset($this->_aliases[$id])) + throw new TConfigurationException('appconfig_alias_redefined',$id); + $this->_aliases[$id]=$p; + } + } + + if(isset($pathsNode['using']) && is_array($pathsNode['using'])) + { + foreach($pathsNode['using'] as $namespace) + { + $this->_usings[] = $namespace; + } + } + } + + /** + * Loads the paths XML node. + * @param TXmlElement the paths XML node + * @param string the context path (for specifying relative paths) + */ + protected function loadPathsXml($pathsNode,$configPath) + { + foreach($pathsNode->getElements() as $element) + { + switch($element->getTagName()) + { + case 'alias': + { + if(($id=$element->getAttribute('id'))!==null && ($path=$element->getAttribute('path'))!==null) + { + $path=str_replace('\\','/',$path); + if(preg_match('/^\\/|.:\\/|.:\\\\/',$path)) // if absolute path + $p=realpath($path); + else + $p=realpath($configPath.DIRECTORY_SEPARATOR.$path); + if($p===false || !is_dir($p)) + throw new TConfigurationException('appconfig_aliaspath_invalid',$id,$path); + if(isset($this->_aliases[$id])) + throw new TConfigurationException('appconfig_alias_redefined',$id); + $this->_aliases[$id]=$p; + } + else + throw new TConfigurationException('appconfig_alias_invalid'); + $this->_empty=false; + break; + } + case 'using': + { + if(($namespace=$element->getAttribute('namespace'))!==null) + $this->_usings[]=$namespace; + else + throw new TConfigurationException('appconfig_using_invalid'); + $this->_empty=false; + break; + } + default: + throw new TConfigurationException('appconfig_paths_invalid',$element->getTagName()); + } + } + } + + /** + * Loads the modules PHP array. + * @param array the modules PHP array + * @param string the context path (for specifying relative paths) + */ + protected function loadModulesPhp($modulesNode, $configPath) + { + foreach($modulesNode as $id=>$module) + { + if(!isset($module['class'])) + throw new TConfigurationException('appconfig_moduletype_required',$id); + $type = $module['class']; + unset($module['class']); + $properties = array(); + if(isset($module['properties'])) + { + $properties = $module['properties']; + unset($module['properties']); + } + $properties['id'] = $id; + $this->_modules[$id]=array($type,$properties,$module); + $this->_empty=false; + } + } + + /** + * Loads the modules XML node. + * @param TXmlElement the modules XML node + * @param string the context path (for specifying relative paths) + */ + protected function loadModulesXml($modulesNode,$configPath) + { + foreach($modulesNode->getElements() as $element) + { + if($element->getTagName()==='module') + { + $properties=$element->getAttributes(); + $id=$properties->itemAt('id'); + $type=$properties->remove('class'); + if($type===null) + throw new TConfigurationException('appconfig_moduletype_required',$id); + $element->setParent(null); + if($id===null) + $this->_modules[]=array($type,$properties->toArray(),$element); + else + $this->_modules[$id]=array($type,$properties->toArray(),$element); + $this->_empty=false; + } + else + throw new TConfigurationException('appconfig_modules_invalid',$element->getTagName()); + } + } + + /** + * Loads the services PHP array. + * @param array the services PHP array + * @param string the context path (for specifying relative paths) + */ + protected function loadServicesPhp($servicesNode,$configPath) + { + foreach($servicesNode as $id => $service) + { + if(!isset($service['class'])) + throw new TConfigurationException('appconfig_servicetype_required'); + $type = $service['class']; + $properties = isset($service['properties']) ? $service['properties'] : array(); + unset($service['properties']); + $properties['id'] = $id; + $this->_services[$id] = array($type,$properties,$service); + $this->_empty = false; + } + } + + /** + * Loads the services XML node. + * @param TXmlElement the services XML node + * @param string the context path (for specifying relative paths) + */ + protected function loadServicesXml($servicesNode,$configPath) + { + foreach($servicesNode->getElements() as $element) + { + if($element->getTagName()==='service') + { + $properties=$element->getAttributes(); + if(($id=$properties->itemAt('id'))===null) + throw new TConfigurationException('appconfig_serviceid_required'); + if(($type=$properties->remove('class'))===null) + throw new TConfigurationException('appconfig_servicetype_required',$id); + $element->setParent(null); + $this->_services[$id]=array($type,$properties->toArray(),$element); + $this->_empty=false; + } + else + throw new TConfigurationException('appconfig_services_invalid',$element->getTagName()); + } + } + + /** + * Loads the parameters PHP array. + * @param array the parameters PHP array + * @param string the context path (for specifying relative paths) + */ + protected function loadParametersPhp($parametersNode,$configPath) + { + foreach($parametersNode as $id => $parameter) + { + if(is_array($parameter)) + { + if(isset($parameter['class'])) + { + $type = $parameter['class']; + unset($parameter['class']); + $properties = isset($service['properties']) ? $service['properties'] : array(); + $properties['id'] = $id; + $this->_parameters[$id] = array($type,$properties); + } + } + else + { + $this->_parameters[$id] = $parameter; + } + } + } + + /** + * Loads the parameters XML node. + * @param TXmlElement the parameters XML node + * @param string the context path (for specifying relative paths) + */ + protected function loadParametersXml($parametersNode,$configPath) + { + foreach($parametersNode->getElements() as $element) + { + if($element->getTagName()==='parameter') + { + $properties=$element->getAttributes(); + if(($id=$properties->remove('id'))===null) + throw new TConfigurationException('appconfig_parameterid_required'); + if(($type=$properties->remove('class'))===null) + { + if(($value=$properties->remove('value'))===null) + $this->_parameters[$id]=$element; + else + $this->_parameters[$id]=$value; + } + else + $this->_parameters[$id]=array($type,$properties->toArray()); + $this->_empty=false; + } + else + throw new TConfigurationException('appconfig_parameters_invalid',$element->getTagName()); + } + } + + /** + * Loads the external PHP array. + * @param array the application PHP array + * @param string the context path (for specifying relative paths) + */ + protected function loadExternalPhp($includeNode,$configPath) + { + foreach($includeNode as $include) + { + $when = isset($include['when'])?true:false; + if(!isset($include['file'])) + throw new TConfigurationException('appconfig_includefile_required'); + $filePath = $include['file']; + if(isset($this->_includes[$filePath])) + $this->_includes[$filePath]='('.$this->_includes[$filePath].') || ('.$when.')'; + else + $$this->_includes[$filePath]=$when; + $this->_empty=false; + } + } + + /** + * Loads the external XML configurations. + * @param TXmlElement the application DOM element + * @param string the context path (for specifying relative paths) + */ + protected function loadExternalXml($includeNode,$configPath) + { + if(($when=$includeNode->getAttribute('when'))===null) + $when=true; + if(($filePath=$includeNode->getAttribute('file'))===null) + throw new TConfigurationException('appconfig_includefile_required'); + if(isset($this->_includes[$filePath])) + $this->_includes[$filePath]='('.$this->_includes[$filePath].') || ('.$when.')'; + else + $this->_includes[$filePath]=$when; + $this->_empty=false; + } + + /** + * Returns list of page initial property values. + * Each array element represents a single property with the key + * being the property name and the value the initial property value. + * @return array list of page initial property values + */ + public function getProperties() + { + return $this->_properties; + } + + /** + * Returns list of path alias definitions. + * The definitions are aggregated (top-down) from configuration files along the path + * to the specified page. Each array element represents a single alias definition, + * with the key being the alias name and the value the absolute path. + * @return array list of path alias definitions + */ + public function getAliases() + { + return $this->_aliases; + } + + /** + * Returns list of namespaces to be used. + * The namespaces are aggregated (top-down) from configuration files along the path + * to the specified page. Each array element represents a single namespace usage, + * with the value being the namespace to be used. + * @return array list of namespaces to be used + */ + public function getUsings() + { + return $this->_usings; + } + + /** + * Returns list of module configurations. + * The module configurations are aggregated (top-down) from configuration files + * along the path to the specified page. Each array element represents + * a single module configuration, with the key being the module ID and + * the value the module configuration. Each module configuration is + * stored in terms of an array with the following content + * ([0]=>module type, [1]=>module properties, [2]=>complete module configuration) + * The module properties are an array of property values indexed by property names. + * The complete module configuration is a TXmlElement object representing + * the raw module configuration which may contain contents enclosed within + * module tags. + * @return array list of module configurations to be used + */ + public function getModules() + { + return $this->_modules; + } + + /** + * @return array list of service configurations + */ + public function getServices() + { + return $this->_services; + } + + /** + * Returns list of parameter definitions. + * The parameter definitions are aggregated (top-down) from configuration files + * along the path to the specified page. Each array element represents + * a single parameter definition, with the key being the parameter ID and + * the value the parameter definition. A parameter definition can be either + * a string representing a string-typed parameter, or an array. + * The latter defines a component-typed parameter whose format is as follows, + * ([0]=>component type, [1]=>component properties) + * The component properties are an array of property values indexed by property names. + * @return array list of parameter definitions to be used + */ + public function getParameters() + { + return $this->_parameters; + } + + /** + * @return array list of external configuration files. Each element is like $filePath=>$condition + */ + public function getExternalConfigurations() + { + return $this->_includes; + } +} + +/** + * TApplicationStatePersister class. + * TApplicationStatePersister provides a file-based persistent storage + * for application state. Application state, when serialized, is stored + * in a file named 'global.cache' under the 'runtime' directory of the application. + * Cache will be exploited if it is enabled. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @package System + * @since 3.0 + */ +class TApplicationStatePersister extends TModule implements IStatePersister +{ + /** + * Name of the value stored in cache + */ + const CACHE_NAME='prado:appstate'; + + /** + * Initializes module. + * @param TXmlElement module configuration (may be null) + */ + public function init($config) + { + $this->getApplication()->setApplicationStatePersister($this); + } + + /** + * @return string the file path storing the application state + */ + protected function getStateFilePath() + { + return $this->getApplication()->getRuntimePath().'/global.cache'; + } + + /** + * Loads application state from persistent storage. + * @return mixed application state + */ + public function load() + { + if(($cache=$this->getApplication()->getCache())!==null && ($value=$cache->get(self::CACHE_NAME))!==false) + return unserialize($value); + else + { + if(($content=@file_get_contents($this->getStateFilePath()))!==false) + return unserialize($content); + else + return null; + } + } + + /** + * Saves application state in persistent storage. + * @param mixed application state + */ + public function save($state) + { + $content=serialize($state); + $saveFile=true; + if(($cache=$this->getApplication()->getCache())!==null) + { + if($cache->get(self::CACHE_NAME)===$content) + $saveFile=false; + else + $cache->set(self::CACHE_NAME,$content); + } + if($saveFile) + { + $fileName=$this->getStateFilePath(); + file_put_contents($fileName,$content,LOCK_EX); + } + } + +} |