* @link https://github.com/pradosoft/prado * @copyright Copyright © 2005-2016 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). * - Off mode will prevent the application from serving user requests. * - Debug 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. * - Normal 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. * - Performance mode is similar to Normal 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: * * $application=new TApplication($configFile); * $application->run(); * * * @author Qiang Xue * @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 protected * 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 application.xml * 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)_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 * @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 * @author Carl G. Mathisen * @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 * @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); } } }