* @link http://www.pradosoft.com/
* @copyright Copyright © 2005 PradoSoft
* @license http://www.pradosoft.com/license/
* @version $Revision: $ $Date: $
* @package System
*/
/**
* 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 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 dispatches each user request to a particular service which
* finishes the actual work for the request with the aid from the application
* modules.
*
* TApplication uses a configuration file to specify the settings of
* the application, the modules, the services, the parameters, and so on.
*
* Examples:
*
* $application=new TApplication($configFile);
* $application->run();
*
*
* @author Qiang Xue
* @version $Revision: $ $Date: $
* @package System
* @since 3.0
*/
class TApplication extends TComponent implements IApplication
{
/**
* 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 array list of types that the named modules must be of
*/
private static $_moduleTypes=array(
'request'=>'THttpRequest',
'response'=>'THttpResponse',
'session'=>'THttpSession',
'cache'=>'ICache',
'error'=>'IErrorHandler'
);
/**
* @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 string user type
*/
private $_userType='System.Security.TUser';
/**
* @var IUser user instance
*/
private $_user=null;
/**
* Constructor.
* Loads application configuration and initializes application.
* If a cache is specified and present, it will be used instead of the configuration file.
* If the cache file is specified but is not present, the configuration file
* will be parsed and the result is saved in the cache file.
* @param string configuration file path (absolute or relative to current running script)
* @param string cache file path
*/
public function __construct($configFile,$cacheFile='')
{
parent::__construct();
Prado::setApplication($this);
$this->_configFile=$configFile;
$this->_cacheFile=$cacheFile;
}
/**
* Executes the lifecycles of the 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)
{
$method='on'.self::$_steps[$this->_step];
$this->$method(null);
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 IService the currently requested service
*/
public function getService()
{
return $this->_service;
}
/**
* Adds a module to application.
* Note, this method does not do module initialization.
* Also, if there is already a module with the same ID, an exception will be raised.
* @param string ID of the module
* @param IModule module object
* @throws TInvalidOperationException if a module with the same ID already exists
*/
public function setModule($id,IModule $module)
{
if(isset($this->_modules[$id]))
throw new TInvalidOperationException('application_module_existing',$id);
else
$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;
}
/**
* @return TMap the list of application parameters
*/
public function getParameters()
{
return $this->_parameters;
}
/**
* @return THttpRequest the request object
*/
public function getRequest()
{
return isset($this->_modules['request'])?$this->_modules['request']:null;
}
/**
* @return THttpResponse the response object
*/
public function getResponse()
{
return isset($this->_modules['response'])?$this->_modules['response']:null;
}
/**
* @return THttpSession the session object
*/
public function getSession()
{
return isset($this->_modules['session'])?$this->_modules['session']:null;
}
/**
* @return ICache the cache object, null if not exists
*/
public function getCache()
{
return isset($this->_modules['cache'])?$this->_modules['cache']:null;
}
/**
* @return IErrorHandler the error hanlder module
*/
public function getErrorHandler()
{
return isset($this->_modules['error'])?$this->_modules['error']:null;
}
/**
* @return IRoleProvider provider for user auth management
*/
public function getAuthManager()
{
return isset($this->_modules['auth'])?$this->_modules['auth']:null;
}
/**
* @return IUser the application user
*/
public function getUser()
{
return $this->_user;
}
/**
* @param IUser the application user
*/
public function setUser(IUser $user)
{
$this->_user=$user;
}
/**
* Loads configuration and initializes application.
* Configuration file will be read and parsed (if a valid cache version exists,
* it will be used instead). Then, modules are created and initialized;
* 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 config file is not given, or module is redefined of invalid type, or service not defined or of invalid type
*/
protected function initApplication($configFile,$cacheFile)
{
if($cacheFile==='' || @filemtime($cacheFile)loadFromFile($configFile);
if($cacheFile!=='')
{
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));
}
// generates unique ID by hashing the configuration file path
$this->_uniqueID=md5(realpath($configFile));
// 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)
{
if(isset($this->_modules[$id]))
throw new TConfigurationException('application_module_redefined',$id);
$module=Prado::createComponent($moduleConfig[0]);
if(isset(self::$_moduleTypes[$id]) && !($module instanceof self::$_moduleTypes[$id]))
throw new TConfigurationException('application_module_invalid',$id,self::$_moduleTypes[$id]);
$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
echo $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
* @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(
'request'=>array('THttpRequest',array(),null),
'response'=>array('THttpResponse',array(),null),
'error'=>array('TErrorHandler',array(),null)
);
/**
* @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 config file cannot be read or any parsing error is found
*/
public function loadFromFile($fname)
{
if(!is_file($fname))
throw new TConfigurationException('application_configuration_inexistent',$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('application_alias_path_invalid',$id,$path);
$this->_aliases[$id]=$p;
}
else
throw new TConfigurationException('application_alias_element_invalid');
}
foreach($pathsNode->getElementsByTagName('using') as $usingNode)
{
if(($namespace=$usingNode->getAttribute('namespace'))!==null)
$this->_usings[]=$namespace;
else
throw new TConfigurationException('application_using_element_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('application_module_element_invalid');
if(($type=$properties->remove('type'))===null && isset($this->_modules[$id]) && $this->_modules[$id][2]===null)
{
$type=$this->_modules[$id][0];
unset($this->_modules[$id]);
}
if($type===null)
throw new TConfigurationException('application_module_element_invalid');
if(isset($this->_modules[$id]))
throw new TConfigurationException('application_module_redefined',$id);
else
{
$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('application_service_element_invalid');
if(($type=$properties->remove('type'))===null && isset($this->_services[$id]) && $this->_services[$id][2]===null)
{
$type=$this->_services[$id][0];
unset($this->_services[$id]);
}
if($type===null)
throw new TConfigurationException('application_service_element_invalid');
if(isset($this->_services[$id]))
throw new TConfigurationException('application_service_redefined',$id);
else
{
$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('application_parameter_element_invalid');
if(($type=$properties->remove('type'))===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;
}
}
?>