summaryrefslogtreecommitdiff
path: root/framework/TApplication.php
diff options
context:
space:
mode:
Diffstat (limited to 'framework/TApplication.php')
-rw-r--r--framework/TApplication.php1816
1 files changed, 915 insertions, 901 deletions
diff --git a/framework/TApplication.php b/framework/TApplication.php
index 3f9457ea..9051ea23 100644
--- a/framework/TApplication.php
+++ b/framework/TApplication.php
@@ -1,902 +1,916 @@
-<?php
-/**
- * TApplication class file
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @link http://www.pradosoft.com/
- * @copyright Copyright &copy; 2005 PradoSoft
- * @license http://www.pradosoft.com/license/
- * @version $Revision: $ $Date: $
- * @package System
- */
-
-/**
- * Includes TErrorHandler class
- */
-require_once(PRADO_DIR.'/Exceptions/TErrorHandler.php');
-/**
- * Includes THttpRequest class
- */
-require_once(PRADO_DIR.'/Web/THttpRequest.php');
-/**
- * Includes THttpResponse class
- */
-require_once(PRADO_DIR.'/Web/THttpResponse.php');
-/**
- * Includes THttpSession class
- */
-require_once(PRADO_DIR.'/Web/THttpSession.php');
-/**
- * Includes TAuthorizationRule class
- */
-require_once(PRADO_DIR.'/Security/TAuthorizationRule.php');
-/**
- * Includes TPageService class (default service)
- */
-require_once(PRADO_DIR.'/Web/Services/TPageService.php');
-
-/**
- * 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
- * - BeginRequest : this event happens right after application initialization
- * - Authentication : this event happens when authentication is needed for the current request
- * - PostAuthentication : this event happens right after the authentication is done for the current request
- * - Authorization : this event happens when authorization is needed for the current request
- * - PostAuthorization : this event happens right after the authorization is done for the current request
- * - LoadState : this event happens when application state needs to be loaded
- * - PostLoadState : this event happens right after the application state is loaded
- * - PreRunService : this event happens right before the requested service is to run
- * - RunService : this event happens when the requested service runs
- * - PostRunService : this event happens right after the requested service finishes running
- * - SaveState : this event happens when application needs to save its state
- * - PostSaveState : this event happens right after the application saves its state
- * - EndRequest : this is the last stage an application runs
- * - [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>
- * - The parsed application configuration file is cached.
- * <code>
- * $application=new TApplication($configFile,$cacheFile);
- * $application->run();
- * </code>
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @version $Revision: $ $Date: $
- * @package System
- * @since 3.0
- */
-class TApplication extends TComponent
-{
- /**
- * Default service ID
- */
- const DEFAULT_SERVICE='page';
- /**
- * @var array list of events that define application lifecycles
- */
- private static $_steps=array(
- 'BeginRequest',
- 'Authentication',
- 'PostAuthentication',
- 'Authorization',
- 'PostAuthorization',
- 'LoadState',
- 'PostLoadState',
- 'PreRunService',
- 'RunService',
- 'PostRunService',
- 'SaveState',
- 'PostSaveState',
- 'EndRequest'
- );
-
- /**
- * @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 IService current service instance
- */
- private $_service;
- /**
- * @var array list of application modules
- */
- private $_modules;
- /**
- * @var TMap list of application parameters
- */
- private $_parameters;
- /**
- * @var string configuration file
- */
- private $_configFile;
- /**
- * @var string cache file
- */
- private $_cacheFile;
- /**
- * @var TErrorHandler error handler module
- */
- private $_errorHandler=null;
- /**
- * @var THttpRequest request module
- */
- private $_request=null;
- /**
- * @var THttpResponse response module
- */
- private $_response=null;
- /**
- * @var THttpSession session module, could be null
- */
- private $_session=null;
- /**
- * @var ICache cache module, could be null
- */
- private $_cache=null;
- /**
- * @var IUser user instance, could be null
- */
- private $_user=null;
- /**
- * @var TAuthorizationRuleCollection collection of authorization rules
- */
- private $_authRules=null;
- /**
- * @var string application mode
- */
- private $_mode='Debug';
-
- /**
- * Constructor.
- * Initializes the application singleton. This method ensures that users can
- * only create one application instance.
- * @param string configuration file path (absolute or relative to current running script)
- * @param string cache file path. This is optional. If it is present, it will
- * be used to store and load parsed application configuration (to improve performance).
- */
- public function __construct($configFile,$cacheFile=null)
- {
- parent::__construct();
- Prado::setApplication($this);
- if(($this->_configFile=realpath($configFile))===false || !is_file($this->_configFile))
- throw new TIOException('application_configfile_inexistent',$configFile);
- $this->_cacheFile=$cacheFile;
- // generates unique ID by hashing the configuration file path
- $this->_uniqueID=md5($this->_configFile);
- }
-
- /**
- * 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($this->_configFile,$this->_cacheFile);
- $n=count(self::$_steps);
- $this->_step=0;
- $this->_requestCompleted=false;
- while($this->_step<$n)
- {
- if($this->_mode==='Off')
- throw new THttpException(503,'application_service_unavailable');
- $method='on'.self::$_steps[$this->_step];
- $this->$method($this);
- if($this->_requestCompleted && $this->_step<$n-1)
- $this->_step=$n-1;
- else
- $this->_step++;
- }
- }
- catch(Exception $e)
- {
- $this->onError($e);
- }
- }
-
- /**
- * 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 string application ID
- */
- public function getID()
- {
- return $this->_id;
- }
-
- /**
- * @param string application ID
- */
- public function setID($value)
- {
- $this->_id=$value;
- }
-
- /**
- * @return string an ID that unique identifies this Prado application from the others
- */
- public function getUniqueID()
- {
- return $this->_uniqueID;
- }
-
- /**
- * @return string application mode (Off|Debug|Normal|Peformance), defaults to Debug.
- */
- public function getMode()
- {
- return $this->_mode;
- }
-
- /**
- * @param string application mode. Valid values include Off, Debug, Normal, or Peformance
- */
- public function setMode($value)
- {
- $this->_mode=TPropertyValue::ensureEnum($value,array('Off','Debug','Normal','Performance'));
- }
-
- /**
- * @return string configuration file path
- */
- public function getConfigurationFile()
- {
- return $this->_configFile;
- }
-
- /**
- * @return IService the currently requested service
- */
- public function getService()
- {
- return $this->_service;
- }
-
- /**
- * Adds a module to application.
- * Note, this method does not do module initialization.
- * @param string ID of the module
- * @param IModule module object
- */
- public function setModule($id,IModule $module)
- {
- $this->_modules[$id]=$module;
- }
-
- /**
- * @return IModule the module with the specified ID, null if not found
- */
- public function getModule($id)
- {
- return isset($this->_modules[$id])?$this->_modules[$id]:null;
- }
-
- /**
- * @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($this,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($this,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($this,null);
- }
- return $this->_session;
- }
-
- /**
- * @param THttpSession the session module
- */
- public function setSession(THttpSession $session)
- {
- $this->_session=$session;
- }
-
- /**
- * @return TErrorHandler the error hanlder module
- */
- public function getErrorHandler()
- {
- if(!$this->_errorHandler)
- {
- $this->_errorHandler=new TErrorHandler;
- $this->_errorHandler->init($this,null);
- }
- return $this->_errorHandler;
- }
-
- /**
- * @param TErrorHandler the error hanlder module
- */
- public function setErrorHandler(TErrorHandler $handler)
- {
- $this->_errorHandler=$handler;
- }
-
- /**
- * @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;
- }
-
- /**
- * @return TAuthorizationRuleCollection list of authorization rules for the current request
- */
- public function getAuthorizationRules()
- {
- if($this->_authRules===null)
- $this->_authRules=new TAuthorizationRuleCollection;
- return $this->_authRules;
- }
-
- /**
- * 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($configFile,$cacheFile)
- {
- if($cacheFile===null || @filemtime($cacheFile)<filemtime($configFile))
- {
- $config=new TApplicationConfiguration;
- $config->loadFromFile($configFile);
- if($cacheFile!==null)
- {
- if(($fp=fopen($cacheFile,'wb'))!==false)
- {
- fputs($fp,Prado::serialize($config));
- fclose($fp);
- }
- else
- syslog(LOG_WARNING,'Prado application config cache file "'.$cacheFile.'" cannot be created.');
- }
- }
- else
- {
- $config=Prado::unserialize(file_get_contents($cacheFile));
- }
-
- // 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
- foreach($config->getProperties() as $name=>$value)
- $this->setSubProperty($name,$value);
-
- // load parameters
- $this->_parameters=new TMap;
- foreach($config->getParameters() as $id=>$parameter)
- {
- if(is_string($parameter))
- $this->_parameters->add($id,$parameter);
- else
- {
- $component=Prado::createComponent($parameter[0]);
- foreach($parameter[1] as $name=>$value)
- $component->setSubProperty($name,$value);
- $this->_parameters->add($id,$component);
- }
- }
-
- // load and init modules specified in app config
- $this->_modules=array();
- foreach($config->getModules() as $id=>$moduleConfig)
- {
- $module=Prado::createComponent($moduleConfig[0]);
- $this->_modules[$id]=$module;
- foreach($moduleConfig[1] as $name=>$value)
- $module->setSubProperty($name,$value);
- $module->init($this,$moduleConfig[2]);
- }
-
- if(($serviceID=$this->getRequest()->getServiceID())===null)
- $serviceID=self::DEFAULT_SERVICE;
- if(($serviceConfig=$config->getService($serviceID))!==null)
- {
- $service=Prado::createComponent($serviceConfig[0]);
- if(!($service instanceof IService))
- throw new TConfigurationException('application_service_invalid',$serviceID);
- $this->_service=$service;
- foreach($serviceConfig[1] as $name=>$value)
- $service->setSubProperty($name,$value);
- $service->init($this,$serviceConfig[2]);
- $this->attachEventHandler('RunService',array($service,'run'));
- }
- else
- throw new TConfigurationException('application_service_unknown',$serviceID);
- }
-
- /**
- * Raises Error event.
- * This method is invoked when an exception is raised during the lifecycles
- * of the application.
- * @param mixed event parameter
- */
- public function onError($param)
- {
- if($this->hasEventHandler('Error'))
- $this->raiseEvent('Error',$this,$param);
- else
- $this->getErrorHandler()->handleError($this,$param);
- }
-
- /**
- * Raises BeginRequest 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.
- * @param mixed event parameter
- */
- public function onBeginRequest($param)
- {
- $this->raiseEvent('BeginRequest',$this,$param);
- }
-
- /**
- * Raises Authentication event.
- * This method is invoked when the user request needs to be authenticated.
- * @param mixed event parameter
- */
- public function onAuthentication($param)
- {
- $this->raiseEvent('Authentication',$this,$param);
- }
-
- /**
- * Raises PostAuthentication event.
- * This method is invoked right after the user request is authenticated.
- * @param mixed event parameter
- */
- public function onPostAuthentication($param)
- {
- $this->raiseEvent('PostAuthentication',$this,$param);
- }
-
- /**
- * Raises Authorization event.
- * This method is invoked when the user request needs to be authorized.
- * @param mixed event parameter
- */
- public function onAuthorization($param)
- {
- $this->raiseEvent('Authorization',$this,$param);
- }
-
- /**
- * Raises PostAuthorization event.
- * This method is invoked right after the user request is authorized.
- * @param mixed event parameter
- */
- public function onPostAuthorization($param)
- {
- $this->raiseEvent('PostAuthorization',$this,$param);
- }
-
- /**
- * Raises LoadState event.
- * This method is invoked when the application needs to load state (probably stored in session).
- * @param mixed event parameter
- */
- public function onLoadState($param)
- {
- $this->raiseEvent('LoadState',$this,$param);
- }
-
- /**
- * Raises PostLoadState event.
- * This method is invoked right after the application state has been loaded.
- * @param mixed event parameter
- */
- public function onPostLoadState($param)
- {
- $this->raiseEvent('PostLoadState',$this,$param);
- }
-
- /**
- * Raises PreRunService event.
- * This method is invoked right before the service is to be run.
- * @param mixed event parameter
- */
- public function onPreRunService($param)
- {
- $this->raiseEvent('PreRunService',$this,$param);
- }
-
- /**
- * Raises RunService event.
- * This method is invoked when the service runs.
- * @param mixed event parameter
- */
- public function onRunService($param)
- {
- $this->raiseEvent('RunService',$this,$param);
- }
-
- /**
- * Raises PostRunService event.
- * This method is invoked right after the servie is run.
- * @param mixed event parameter
- */
- public function onPostRunService($param)
- {
- $this->raiseEvent('PostRunService',$this,$param);
- }
-
- /**
- * Raises SaveState event.
- * This method is invoked when the application needs to save state (probably stored in session).
- * @param mixed event parameter
- */
- public function onSaveState($param)
- {
- $this->raiseEvent('SaveState',$this,$param);
- }
-
- /**
- * Raises PostSaveState event.
- * This method is invoked right after the application state has been saved.
- * @param mixed event parameter
- */
- public function onPostSaveState($param)
- {
- $this->raiseEvent('PostSaveState',$this,$param);
- }
-
- /**
- * Raises EndRequest event.
- * This method is invoked when the application completes the processing of the request.
- * @param mixed event parameter
- */
- public function onEndRequest($param)
- {
- $this->raiseEvent('EndRequest',$this,$param);
- }
-}
-
-
-/**
- * TApplicationConfiguration class.
- *
- * This class is used internally by TApplication to parse and represent application configuration.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @version $Revision: $ $Date: $
- * @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(
- 'page'=>array('TPageService',array(),null)
- );
- /**
- * @var array list of parameters
- */
- private $_parameters=array();
-
- /**
- * Parses the application configuration file.
- * @param string configuration file name
- * @throws TConfigurationException if there is any parsing error
- */
- public function loadFromFile($fname)
- {
- $configPath=dirname($fname);
- $dom=new TXmlDocument;
- $dom->loadFromFile($fname);
-
- // application properties
- foreach($dom->getAttributes() as $name=>$value)
- $this->_properties[$name]=$value;
-
- // paths
- if(($pathsNode=$dom->getElementByTagName('paths'))!==null)
- {
- foreach($pathsNode->getElementsByTagName('alias') as $aliasNode)
- {
- if(($id=$aliasNode->getAttribute('id'))!==null && ($path=$aliasNode->getAttribute('path'))!==null)
- {
- $path=str_replace('\\','/',$path);
- if(preg_match('/^\\/|.:\\/|.:\\\\/',$path)) // if absolute path
- $p=realpath($path);
- else
- $p=realpath($configPath.'/'.$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');
- }
- foreach($pathsNode->getElementsByTagName('using') as $usingNode)
- {
- if(($namespace=$usingNode->getAttribute('namespace'))!==null)
- $this->_usings[]=$namespace;
- else
- throw new TConfigurationException('appconfig_using_invalid');
- }
- }
-
- // application modules
- if(($modulesNode=$dom->getElementByTagName('modules'))!==null)
- {
- foreach($modulesNode->getElementsByTagName('module') as $node)
- {
- $properties=$node->getAttributes();
- if(($id=$properties->itemAt('id'))===null)
- throw new TConfigurationException('appconfig_moduleid_required');
- if(($type=$properties->remove('class'))===null && isset($this->_modules[$id]) && $this->_modules[$id][2]===null)
- $type=$this->_modules[$id][0];
- if($type===null)
- throw new TConfigurationException('appconfig_moduletype_required',$id);
- $node->setParent(null);
- $this->_modules[$id]=array($type,$properties->toArray(),$node);
- }
- }
-
- // services
- if(($servicesNode=$dom->getElementByTagName('services'))!==null)
- {
- foreach($servicesNode->getElementsByTagName('service') as $node)
- {
- $properties=$node->getAttributes();
- if(($id=$properties->itemAt('id'))===null)
- throw new TConfigurationException('appconfig_serviceid_required');
- if(($type=$properties->remove('class'))===null && isset($this->_services[$id]) && $this->_services[$id][2]===null)
- $type=$this->_services[$id][0];
- if($type===null)
- throw new TConfigurationException('appconfig_servicetype_required',$id);
- $node->setParent(null);
- $this->_services[$id]=array($type,$properties->toArray(),$node);
- }
- }
-
- // parameters
- if(($parametersNode=$dom->getElementByTagName('parameters'))!==null)
- {
- foreach($parametersNode->getElementsByTagName('parameter') as $node)
- {
- $properties=$node->getAttributes();
- if(($id=$properties->remove('id'))===null)
- throw new TConfigurationException('appconfig_parameterid_required');
- if(($type=$properties->remove('class'))===null)
- $this->_parameters[$id]=$node->getValue();
- else
- $this->_parameters[$id]=array($type,$properties->toArray());
- }
- }
- }
-
- /**
- * @return array list of application initial property values, indexed by property names
- */
- public function getProperties()
- {
- return $this->_properties;
- }
-
- /**
- * @return array list of path aliases, indexed by alias names
- */
- public function getAliases()
- {
- return $this->_aliases;
- }
-
- /**
- * @return array list of namespaces to be used
- */
- public function getUsings()
- {
- return $this->_usings;
- }
-
- /**
- * @return array list of module configurations
- */
- public function getModules()
- {
- return $this->_modules;
- }
-
- /**
- * @return array list of service configurations
- */
- public function getService($id)
- {
- return isset($this->_services[$id])?$this->_services[$id]:null;
- }
-
- /**
- * @return array list of parameters
- */
- public function getParameters()
- {
- return $this->_parameters;
- }
-}
-
+<?php
+/**
+ * TApplication class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $Date: $
+ * @package System
+ */
+
+/**
+ * Includes TErrorHandler class
+ */
+require_once(PRADO_DIR.'/Exceptions/TErrorHandler.php');
+/**
+ * Includes THttpRequest class
+ */
+require_once(PRADO_DIR.'/Web/THttpRequest.php');
+/**
+ * Includes THttpResponse class
+ */
+require_once(PRADO_DIR.'/Web/THttpResponse.php');
+/**
+ * Includes THttpSession class
+ */
+require_once(PRADO_DIR.'/Web/THttpSession.php');
+/**
+ * Includes TAuthorizationRule class
+ */
+require_once(PRADO_DIR.'/Security/TAuthorizationRule.php');
+/**
+ * Includes TPageService class (default service)
+ */
+require_once(PRADO_DIR.'/Web/Services/TPageService.php');
+
+/**
+ * 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
+ * - BeginRequest : this event happens right after application initialization
+ * - Authentication : this event happens when authentication is needed for the current request
+ * - PostAuthentication : this event happens right after the authentication is done for the current request
+ * - Authorization : this event happens when authorization is needed for the current request
+ * - PostAuthorization : this event happens right after the authorization is done for the current request
+ * - LoadState : this event happens when application state needs to be loaded
+ * - PostLoadState : this event happens right after the application state is loaded
+ * - PreRunService : this event happens right before the requested service is to run
+ * - RunService : this event happens when the requested service runs
+ * - PostRunService : this event happens right after the requested service finishes running
+ * - SaveState : this event happens when application needs to save its state
+ * - PostSaveState : this event happens right after the application saves its state
+ * - EndRequest : this is the last stage an application runs
+ * - [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>
+ * - The parsed application configuration file is cached.
+ * <code>
+ * $application=new TApplication($configFile,$cacheFile);
+ * $application->run();
+ * </code>
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System
+ * @since 3.0
+ */
+class TApplication extends TComponent
+{
+ /**
+ * Default service ID
+ */
+ const DEFAULT_SERVICE='page';
+ /**
+ * @var array list of events that define application lifecycles
+ */
+ private static $_steps=array(
+ 'BeginRequest',
+ 'Authentication',
+ 'PostAuthentication',
+ 'Authorization',
+ 'PostAuthorization',
+ 'LoadState',
+ 'PostLoadState',
+ 'PreRunService',
+ 'RunService',
+ 'PostRunService',
+ 'SaveState',
+ 'PostSaveState',
+ 'EndRequest'
+ );
+
+ /**
+ * @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 IService current service instance
+ */
+ private $_service;
+ /**
+ * @var array list of application modules
+ */
+ private $_modules;
+ /**
+ * @var TMap list of application parameters
+ */
+ private $_parameters;
+ /**
+ * @var string configuration file
+ */
+ private $_configFile;
+ /**
+ * @var string cache file
+ */
+ private $_cacheFile;
+ /**
+ * @var TErrorHandler error handler module
+ */
+ private $_errorHandler=null;
+ /**
+ * @var THttpRequest request module
+ */
+ private $_request=null;
+ /**
+ * @var THttpResponse response module
+ */
+ private $_response=null;
+ /**
+ * @var THttpSession session module, could be null
+ */
+ private $_session=null;
+ /**
+ * @var ICache cache module, could be null
+ */
+ private $_cache=null;
+ /**
+ * @var IUser user instance, could be null
+ */
+ private $_user=null;
+ /**
+ * @var TAuthorizationRuleCollection collection of authorization rules
+ */
+ private $_authRules=null;
+ /**
+ * @var string application mode
+ */
+ private $_mode='Debug';
+
+ /**
+ * Constructor.
+ * Initializes the application singleton. This method ensures that users can
+ * only create one application instance.
+ * @param string configuration file path (absolute or relative to current running script)
+ * @param string cache file path. This is optional. If it is present, it will
+ * be used to store and load parsed application configuration (to improve performance).
+ */
+ public function __construct($configFile,$cacheFile=null)
+ {
+ parent::__construct();
+ Prado::setApplication($this);
+ if(($this->_configFile=realpath($configFile))===false || !is_file($this->_configFile))
+ throw new TIOException('application_configfile_inexistent',$configFile);
+ $this->_cacheFile=$cacheFile;
+ // generates unique ID by hashing the configuration file path
+ $this->_uniqueID=md5($this->_configFile);
+ }
+
+
+ /**
+ * 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()
+ {
+ //init the error handlers
+ $this->applyDefaultExceptionHandlers();
+ try
+ {
+ $this->initApplication($this->_configFile,$this->_cacheFile);
+ $n=count(self::$_steps);
+ $this->_step=0;
+ $this->_requestCompleted=false;
+ while($this->_step<$n)
+ {
+ if($this->_mode==='Off')
+ throw new THttpException(503,'application_service_unavailable');
+ $method='on'.self::$_steps[$this->_step];
+ $this->$method($this);
+ if($this->_requestCompleted && $this->_step<$n-1)
+ $this->_step=$n-1;
+ else
+ $this->_step++;
+ }
+ }
+ catch(Exception $e)
+ {
+ $this->onError($e);
+ }
+ }
+
+ /**
+ * Sets up error handler to convert PHP errors into exceptions that can be caught.
+ */
+ protected function applyDefaultExceptionHandlers()
+ {
+ set_error_handler(array('Prado','phpErrorHandler'),error_reporting());
+ set_exception_handler(array('Prado','exceptionHandler'));
+ }
+
+ /**
+ * 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 string application ID
+ */
+ public function getID()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * @param string application ID
+ */
+ public function setID($value)
+ {
+ $this->_id=$value;
+ }
+
+ /**
+ * @return string an ID that unique identifies this Prado application from the others
+ */
+ public function getUniqueID()
+ {
+ return $this->_uniqueID;
+ }
+
+ /**
+ * @return string application mode (Off|Debug|Normal|Peformance), defaults to Debug.
+ */
+ public function getMode()
+ {
+ return $this->_mode;
+ }
+
+ /**
+ * @param string application mode. Valid values include Off, Debug, Normal, or Peformance
+ */
+ public function setMode($value)
+ {
+ $this->_mode=TPropertyValue::ensureEnum($value,array('Off','Debug','Normal','Performance'));
+ }
+
+ /**
+ * @return string configuration file path
+ */
+ public function getConfigurationFile()
+ {
+ return $this->_configFile;
+ }
+
+ /**
+ * @return IService the currently requested service
+ */
+ public function getService()
+ {
+ return $this->_service;
+ }
+
+ /**
+ * Adds a module to application.
+ * Note, this method does not do module initialization.
+ * @param string ID of the module
+ * @param IModule module object
+ */
+ public function setModule($id,IModule $module)
+ {
+ $this->_modules[$id]=$module;
+ }
+
+ /**
+ * @return IModule the module with the specified ID, null if not found
+ */
+ public function getModule($id)
+ {
+ return isset($this->_modules[$id])?$this->_modules[$id]:null;
+ }
+
+ /**
+ * @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($this,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($this,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($this,null);
+ }
+ return $this->_session;
+ }
+
+ /**
+ * @param THttpSession the session module
+ */
+ public function setSession(THttpSession $session)
+ {
+ $this->_session=$session;
+ }
+
+ /**
+ * @return TErrorHandler the error hanlder module
+ */
+ public function getErrorHandler()
+ {
+ if(!$this->_errorHandler)
+ {
+ $this->_errorHandler=new TErrorHandler;
+ $this->_errorHandler->init($this,null);
+ }
+ return $this->_errorHandler;
+ }
+
+ /**
+ * @param TErrorHandler the error hanlder module
+ */
+ public function setErrorHandler(TErrorHandler $handler)
+ {
+ $this->_errorHandler=$handler;
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * @return TAuthorizationRuleCollection list of authorization rules for the current request
+ */
+ public function getAuthorizationRules()
+ {
+ if($this->_authRules===null)
+ $this->_authRules=new TAuthorizationRuleCollection;
+ return $this->_authRules;
+ }
+
+ /**
+ * 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($configFile,$cacheFile)
+ {
+ if($cacheFile===null || @filemtime($cacheFile)<filemtime($configFile))
+ {
+ $config=new TApplicationConfiguration;
+ $config->loadFromFile($configFile);
+ if($cacheFile!==null)
+ {
+ if(($fp=fopen($cacheFile,'wb'))!==false)
+ {
+ fputs($fp,Prado::serialize($config));
+ fclose($fp);
+ }
+ else
+ syslog(LOG_WARNING,'Prado application config cache file "'.$cacheFile.'" cannot be created.');
+ }
+ }
+ else
+ {
+ $config=Prado::unserialize(file_get_contents($cacheFile));
+ }
+
+
+ // 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
+ foreach($config->getProperties() as $name=>$value)
+ $this->setSubProperty($name,$value);
+
+ // load parameters
+ $this->_parameters=new TMap;
+ foreach($config->getParameters() as $id=>$parameter)
+ {
+ if(is_string($parameter))
+ $this->_parameters->add($id,$parameter);
+ else
+ {
+ $component=Prado::createComponent($parameter[0]);
+ foreach($parameter[1] as $name=>$value)
+ $component->setSubProperty($name,$value);
+ $this->_parameters->add($id,$component);
+ }
+ }
+
+ // load and init modules specified in app config
+ $this->_modules=array();
+ foreach($config->getModules() as $id=>$moduleConfig)
+ {
+ $module=Prado::createComponent($moduleConfig[0]);
+ $this->_modules[$id]=$module;
+ foreach($moduleConfig[1] as $name=>$value)
+ $module->setSubProperty($name,$value);
+ $module->init($this,$moduleConfig[2]);
+ }
+
+ if(($serviceID=$this->getRequest()->getServiceID())===null)
+ $serviceID=self::DEFAULT_SERVICE;
+
+ if(($serviceConfig=$config->getService($serviceID))!==null)
+ {
+ $service=Prado::createComponent($serviceConfig[0]);
+ if(!($service instanceof IService))
+ throw new TConfigurationException('application_service_invalid',$serviceID);
+ $this->_service=$service;
+ foreach($serviceConfig[1] as $name=>$value)
+ $service->setSubProperty($name,$value);
+ $service->init($this,$serviceConfig[2]);
+ $this->attachEventHandler('RunService',array($service,'run'));
+ }
+ else
+ throw new TConfigurationException('application_service_unknown',$serviceID);
+ }
+
+ /**
+ * Raises Error event.
+ * This method is invoked when an exception is raised during the lifecycles
+ * of the application.
+ * @param mixed event parameter
+ */
+ public function onError($param)
+ {
+ if($this->hasEventHandler('Error'))
+ $this->raiseEvent('Error',$this,$param);
+ else
+ $this->getErrorHandler()->handleError($this,$param);
+ }
+
+ /**
+ * Raises BeginRequest 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.
+ * @param mixed event parameter
+ */
+ public function onBeginRequest($param)
+ {
+ $this->raiseEvent('BeginRequest',$this,$param);
+ }
+
+ /**
+ * Raises Authentication event.
+ * This method is invoked when the user request needs to be authenticated.
+ * @param mixed event parameter
+ */
+ public function onAuthentication($param)
+ {
+ $this->raiseEvent('Authentication',$this,$param);
+ }
+
+ /**
+ * Raises PostAuthentication event.
+ * This method is invoked right after the user request is authenticated.
+ * @param mixed event parameter
+ */
+ public function onPostAuthentication($param)
+ {
+ $this->raiseEvent('PostAuthentication',$this,$param);
+ }
+
+ /**
+ * Raises Authorization event.
+ * This method is invoked when the user request needs to be authorized.
+ * @param mixed event parameter
+ */
+ public function onAuthorization($param)
+ {
+ $this->raiseEvent('Authorization',$this,$param);
+ }
+
+ /**
+ * Raises PostAuthorization event.
+ * This method is invoked right after the user request is authorized.
+ * @param mixed event parameter
+ */
+ public function onPostAuthorization($param)
+ {
+ $this->raiseEvent('PostAuthorization',$this,$param);
+ }
+
+ /**
+ * Raises LoadState event.
+ * This method is invoked when the application needs to load state (probably stored in session).
+ * @param mixed event parameter
+ */
+ public function onLoadState($param)
+ {
+ $this->raiseEvent('LoadState',$this,$param);
+ }
+
+ /**
+ * Raises PostLoadState event.
+ * This method is invoked right after the application state has been loaded.
+ * @param mixed event parameter
+ */
+ public function onPostLoadState($param)
+ {
+ $this->raiseEvent('PostLoadState',$this,$param);
+ }
+
+ /**
+ * Raises PreRunService event.
+ * This method is invoked right before the service is to be run.
+ * @param mixed event parameter
+ */
+ public function onPreRunService($param)
+ {
+ $this->raiseEvent('PreRunService',$this,$param);
+ }
+
+ /**
+ * Raises RunService event.
+ * This method is invoked when the service runs.
+ * @param mixed event parameter
+ */
+ public function onRunService($param)
+ {
+ $this->raiseEvent('RunService',$this,$param);
+ }
+
+ /**
+ * Raises PostRunService event.
+ * This method is invoked right after the servie is run.
+ * @param mixed event parameter
+ */
+ public function onPostRunService($param)
+ {
+ $this->raiseEvent('PostRunService',$this,$param);
+ }
+
+ /**
+ * Raises SaveState event.
+ * This method is invoked when the application needs to save state (probably stored in session).
+ * @param mixed event parameter
+ */
+ public function onSaveState($param)
+ {
+ $this->raiseEvent('SaveState',$this,$param);
+ }
+
+ /**
+ * Raises PostSaveState event.
+ * This method is invoked right after the application state has been saved.
+ * @param mixed event parameter
+ */
+ public function onPostSaveState($param)
+ {
+ $this->raiseEvent('PostSaveState',$this,$param);
+ }
+
+ /**
+ * Raises EndRequest event.
+ * This method is invoked when the application completes the processing of the request.
+ * @param mixed event parameter
+ */
+ public function onEndRequest($param)
+ {
+ $this->raiseEvent('EndRequest',$this,$param);
+ }
+}
+
+
+/**
+ * TApplicationConfiguration class.
+ *
+ * This class is used internally by TApplication to parse and represent application configuration.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @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(
+ 'page'=>array('TPageService',array(),null)
+ );
+ /**
+ * @var array list of parameters
+ */
+ private $_parameters=array();
+
+ /**
+ * Parses the application configuration file.
+ * @param string configuration file name
+ * @throws TConfigurationException if there is any parsing error
+ */
+ public function loadFromFile($fname)
+ {
+ $configPath=dirname($fname);
+ $dom=new TXmlDocument;
+ $dom->loadFromFile($fname);
+
+ // application properties
+ foreach($dom->getAttributes() as $name=>$value)
+ $this->_properties[$name]=$value;
+
+ // paths
+ if(($pathsNode=$dom->getElementByTagName('paths'))!==null)
+ {
+ foreach($pathsNode->getElementsByTagName('alias') as $aliasNode)
+ {
+ if(($id=$aliasNode->getAttribute('id'))!==null && ($path=$aliasNode->getAttribute('path'))!==null)
+ {
+ $path=str_replace('\\','/',$path);
+ if(preg_match('/^\\/|.:\\/|.:\\\\/',$path)) // if absolute path
+ $p=realpath($path);
+ else
+ $p=realpath($configPath.'/'.$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');
+ }
+ foreach($pathsNode->getElementsByTagName('using') as $usingNode)
+ {
+ if(($namespace=$usingNode->getAttribute('namespace'))!==null)
+ $this->_usings[]=$namespace;
+ else
+ throw new TConfigurationException('appconfig_using_invalid');
+ }
+ }
+
+ // application modules
+ if(($modulesNode=$dom->getElementByTagName('modules'))!==null)
+ {
+ foreach($modulesNode->getElementsByTagName('module') as $node)
+ {
+ $properties=$node->getAttributes();
+ if(($id=$properties->itemAt('id'))===null)
+ throw new TConfigurationException('appconfig_moduleid_required');
+ if(($type=$properties->remove('class'))===null && isset($this->_modules[$id]) && $this->_modules[$id][2]===null)
+ $type=$this->_modules[$id][0];
+ if($type===null)
+ throw new TConfigurationException('appconfig_moduletype_required',$id);
+ $node->setParent(null);
+ $this->_modules[$id]=array($type,$properties->toArray(),$node);
+ }
+ }
+
+ // services
+ if(($servicesNode=$dom->getElementByTagName('services'))!==null)
+ {
+ foreach($servicesNode->getElementsByTagName('service') as $node)
+ {
+ $properties=$node->getAttributes();
+ if(($id=$properties->itemAt('id'))===null)
+ throw new TConfigurationException('appconfig_serviceid_required');
+ if(($type=$properties->remove('class'))===null && isset($this->_services[$id]) && $this->_services[$id][2]===null)
+ $type=$this->_services[$id][0];
+ if($type===null)
+ throw new TConfigurationException('appconfig_servicetype_required',$id);
+ $node->setParent(null);
+ $this->_services[$id]=array($type,$properties->toArray(),$node);
+ }
+ }
+
+ // parameters
+ if(($parametersNode=$dom->getElementByTagName('parameters'))!==null)
+ {
+ foreach($parametersNode->getElementsByTagName('parameter') as $node)
+ {
+ $properties=$node->getAttributes();
+ if(($id=$properties->remove('id'))===null)
+ throw new TConfigurationException('appconfig_parameterid_required');
+ if(($type=$properties->remove('class'))===null)
+ $this->_parameters[$id]=$node->getValue();
+ else
+ $this->_parameters[$id]=array($type,$properties->toArray());
+ }
+ }
+ }
+
+ /**
+ * @return array list of application initial property values, indexed by property names
+ */
+ public function getProperties()
+ {
+ return $this->_properties;
+ }
+
+ /**
+ * @return array list of path aliases, indexed by alias names
+ */
+ public function getAliases()
+ {
+ return $this->_aliases;
+ }
+
+ /**
+ * @return array list of namespaces to be used
+ */
+ public function getUsings()
+ {
+ return $this->_usings;
+ }
+
+ /**
+ * @return array list of module configurations
+ */
+ public function getModules()
+ {
+ return $this->_modules;
+ }
+
+ /**
+ * @return array list of service configurations
+ */
+ public function getService($id)
+ {
+ return isset($this->_services[$id])?$this->_services[$id]:null;
+ }
+
+ /**
+ * @return array list of parameters
+ */
+ public function getParameters()
+ {
+ return $this->_parameters;
+ }
+}
+
?> \ No newline at end of file