root/trunk/framework/TApplication.php

Revision 2482, 41.9 kB (checked in by knut, 4 months ago)

updated copyright

  • Property svn:keywords set to Id
Line 
1<?php
2/**
3 * TApplication class file
4 *
5 * @author Qiang Xue <qiang.xue@gmail.com>
6 * @link http://www.pradosoft.com/
7 * @copyright Copyright &copy; 2005-2008 PradoSoft
8 * @license http://www.pradosoft.com/license/
9 * @version $Id$
10 * @package System
11 */
12
13/**
14 * Includes core interfaces essential for TApplication class
15 */
16require_once(PRADO_DIR.'/interfaces.php');
17
18/**
19 * Includes core classes essential for TApplication class
20 */
21Prado::using('System.TApplicationComponent');
22Prado::using('System.TModule');
23Prado::using('System.TService');
24Prado::using('System.Exceptions.TErrorHandler');
25Prado::using('System.Caching.TCache');
26Prado::using('System.IO.TTextWriter');
27Prado::using('System.Collections.TList');
28Prado::using('System.Collections.TMap');
29Prado::using('System.Collections.TStack');
30Prado::using('System.Xml.TXmlDocument');
31Prado::using('System.Security.TAuthorizationRule');
32Prado::using('System.Security.TSecurityManager');
33Prado::using('System.Web.THttpUtility');
34Prado::using('System.Web.Javascripts.TJavaScript');
35Prado::using('System.Web.THttpRequest');
36Prado::using('System.Web.THttpResponse');
37Prado::using('System.Web.THttpSession');
38Prado::using('System.Web.Services.TPageService');
39Prado::using('System.Web.TAssetManager');
40Prado::using('System.I18N.TGlobalization');
41
42/**
43 * TApplication class.
44 *
45 * TApplication coordinates modules and services, and serves as a configuration
46 * context for all Prado components.
47 *
48 * TApplication uses a configuration file to specify the settings of
49 * the application, the modules, the services, the parameters, and so on.
50 *
51 * TApplication adopts a modular structure. A TApplication instance is a composition
52 * of multiple modules. A module is an instance of class implementing
53 * {@link IModule} interface. Each module accomplishes certain functionalities
54 * that are shared by all Prado components in an application.
55 * There are default modules and user-defined modules. The latter offers extreme
56 * flexibility of extending TApplication in a plug-and-play fashion.
57 * Modules cooperate with each other to serve a user request by following
58 * a sequence of lifecycles predefined in TApplication.
59 *
60 * TApplication has four modes that can be changed by setting {@link setMode Mode}
61 * property (in the application configuration file).
62 * - <b>Off</b> mode will prevent the application from serving user requests.
63 * - <b>Debug</b> mode is mainly used during application development. It ensures
64 *   the cache is always up-to-date if caching is enabled. It also allows
65 *   exceptions are displayed with rich context information if they occur.
66 * - <b>Normal</b> mode is mainly used during production stage. Exception information
67 *   will only be recorded in system error logs. The cache is ensured to be
68 *   up-to-date if it is enabled.
69 * - <b>Performance</b> mode is similar to <b>Normal</b> mode except that it
70 *   does not ensure the cache is up-to-date.
71 *
72 * TApplication dispatches each user request to a particular service which
73 * finishes the actual work for the request with the aid from the application
74 * modules.
75 *
76 * TApplication maintains a lifecycle with the following stages:
77 * - [construct] : construction of the application instance
78 * - [initApplication] : load application configuration and instantiate modules and the requested service
79 * - onBeginRequest : this event happens right after application initialization
80 * - onAuthentication : this event happens when authentication is needed for the current request
81 * - onAuthenticationComplete : this event happens right after the authentication is done for the current request
82 * - onAuthorization : this event happens when authorization is needed for the current request
83 * - onAuthorizationComplete : this event happens right after the authorization is done for the current request
84 * - onLoadState : this event happens when application state needs to be loaded
85 * - onLoadStateComplete : this event happens right after the application state is loaded
86 * - onPreRunService : this event happens right before the requested service is to run
87 * - runService : the requested service runs
88 * - onSaveState : this event happens when application needs to save its state
89 * - onSaveStateComplete : this event happens right after the application saves its state
90 * - onPreFlushOutput : this event happens right before the application flushes output to client side.
91 * - flushOutput : the application flushes output to client side.
92 * - onEndRequest : this is the last stage a request is being completed
93 * - [destruct] : destruction of the application instance
94 * Modules and services can attach their methods to one or several of the above
95 * events and do appropriate processing when the events are raised. By this way,
96 * the application is able to coordinate the activities of modules and services
97 * in the above order. To terminate an application before the whole lifecycle
98 * completes, call {@link completeRequest}.
99 *
100 * Examples:
101 * - Create and run a Prado application:
102 * <code>
103 * $application=new TApplication($configFile);
104 * $application->run();
105 * </code>
106 *
107 * @author Qiang Xue <qiang.xue@gmail.com>
108 * @version $Id$
109 * @package System
110 * @since 3.0
111 */
112class TApplication extends TComponent
113{
114    /**
115     * possible application mode.
116     * @deprecated deprecated since version 3.0.4 (use TApplicationMode constants instead)
117     */
118    const STATE_OFF='Off';
119    const STATE_DEBUG='Debug';
120    const STATE_NORMAL='Normal';
121    const STATE_PERFORMANCE='Performance';
122
123    /**
124     * Page service ID
125     */
126    const PAGE_SERVICE_ID='page';
127    /**
128     * Application configuration file name
129     */
130    const CONFIG_FILE='application.xml';
131    /**
132     * File extension for external config files
133     */
134    const CONFIG_FILE_EXT='.xml';
135    /**
136     * Runtime directory name
137     */
138    const RUNTIME_PATH='runtime';
139    /**
140     * Config cache file
141     */
142    const CONFIGCACHE_FILE='config.cache';
143    /**
144     * Global data file
145     */
146    const GLOBAL_FILE='global.cache';
147
148    /**
149     * @var array list of events that define application lifecycles
150     */
151    private static $_steps=array(
152        'onBeginRequest',
153        'onLoadState',
154        'onLoadStateComplete',
155        'onAuthentication',
156        'onAuthenticationComplete',
157        'onAuthorization',
158        'onAuthorizationComplete',
159        'onPreRunService',
160        'runService',
161        'onSaveState',
162        'onSaveStateComplete',
163        'onPreFlushOutput',
164        'flushOutput'
165    );
166
167    /**
168     * @var string application ID
169     */
170    private $_id;
171    /**
172     * @var string unique application ID
173     */
174    private $_uniqueID;
175    /**
176     * @var boolean whether the request is completed
177     */
178    private $_requestCompleted=false;
179    /**
180     * @var integer application state
181     */
182    private $_step;
183    /**
184     * @var array available services and their configurations indexed by service IDs
185     */
186    private $_services;
187    /**
188     * @var IService current service instance
189     */
190    private $_service;
191    /**
192     * @var array list of application modules
193     */
194    private $_modules=array();
195    /**
196     * @var TMap list of application parameters
197     */
198    private $_parameters;
199    /**
200     * @var string configuration file
201     */
202    private $_configFile;
203    /**
204     * @var string application base path
205     */
206    private $_basePath;
207    /**
208     * @var string directory storing application state
209     */
210    private $_runtimePath;
211    /**
212     * @var boolean if any global state is changed during the current request
213     */
214    private $_stateChanged=false;
215    /**
216     * @var array global variables (persistent across sessions, requests)
217     */
218    private $_globals=array();
219    /**
220     * @var string cache file
221     */
222    private $_cacheFile;
223    /**
224     * @var TErrorHandler error handler module
225     */
226    private $_errorHandler;
227    /**
228     * @var THttpRequest request module
229     */
230    private $_request;
231    /**
232     * @var THttpResponse response module
233     */
234    private $_response;
235    /**
236     * @var THttpSession session module, could be null
237     */
238    private $_session;
239    /**
240     * @var ICache cache module, could be null
241     */
242    private $_cache;
243    /**
244     * @var IStatePersister application state persister
245     */
246    private $_statePersister;
247    /**
248     * @var IUser user instance, could be null
249     */
250    private $_user;
251    /**
252     * @var TGlobalization module, could be null
253     */
254    private $_globalization;
255    /**
256     * @var TSecurityManager security manager module
257     */
258    private $_security;
259    /**
260     * @var TAssetManager asset manager module
261     */
262    private $_assetManager;
263    /**
264     * @var TAuthorizationRuleCollection collection of authorization rules
265     */
266    private $_authRules;
267    /**
268     * @var TApplicationMode application mode
269     */
270    private $_mode=TApplicationMode::Debug;
271
272    /**
273     * Constructor.
274     * Sets application base path and initializes the application singleton.
275     * Application base path refers to the root directory storing application
276     * data and code not directly accessible by Web users.
277     * By default, the base path is assumed to be the <b>protected</b>
278     * directory under the directory containing the current running script.
279     * @param string application base path or configuration file path.
280     *        If the parameter is a file, it is assumed to be the application
281     *        configuration file, and the directory containing the file is treated
282     *        as the application base path.
283     *        If it is a directory, it is assumed to be the application base path,
284     *        and within that directory, a file named <b>application.xml</b>
285     *        will be looked for. If found, the file is considered as the application
286     *        configuration file.
287     * @param boolean whether to cache application configuration. Defaults to true.
288     * @throws TConfigurationException if configuration file cannot be read or the runtime path is invalid.
289     */
290    public function __construct($basePath='protected',$cacheConfig=true)
291    {
292        // register application as a singleton
293        Prado::setApplication($this);
294
295        $this->resolvePaths($basePath);
296
297        if($cacheConfig)
298            $this->_cacheFile=$this->_runtimePath.DIRECTORY_SEPARATOR.self::CONFIGCACHE_FILE;
299
300        // generates unique ID by hashing the runtime path
301        $this->_uniqueID=md5($this->_runtimePath);
302        $this->_parameters=new TMap;
303        $this->_services=array(self::PAGE_SERVICE_ID=>array('TPageService',array(),null));
304        Prado::setPathOfAlias('Application',$this->_basePath);
305    }
306
307    /**
308     * Resolves application-relevant paths.
309     * This method is invoked by the application constructor
310     * to determine the application configuration file,
311     * application root path and the runtime path.
312     * @param string the application root path or the application configuration file
313     * @see setBasePath
314     * @see setRuntimePath
315     * @see setConfigurationFile
316     */
317    protected function resolvePaths($basePath)
318    {
319        // determine configuration path and file
320        if(empty($basePath) || ($basePath=realpath($basePath))===false)
321            throw new TConfigurationException('application_basepath_invalid',$basePath);
322        if(is_file($basePath.DIRECTORY_SEPARATOR.self::CONFIG_FILE))
323            $configFile=$basePath.DIRECTORY_SEPARATOR.self::CONFIG_FILE;
324        else if(is_file($basePath))
325        {
326            $configFile=$basePath;
327            $basePath=dirname($configFile);
328        }
329        else
330            $configFile=null;
331
332        // determine runtime path
333        $runtimePath=$basePath.DIRECTORY_SEPARATOR.self::RUNTIME_PATH;
334        if(is_writable($runtimePath))
335        {
336            if($configFile!==null)
337            {
338                $runtimePath.=DIRECTORY_SEPARATOR.basename($configFile).'-'.Prado::getVersion();
339                if(!is_dir($runtimePath))
340                {
341                    if(@mkdir($runtimePath)===false)
342                        throw new TConfigurationException('application_runtimepath_failed',$runtimePath);
343                    @chmod($runtimePath, PRADO_CHMOD); //make it deletable
344                }
345                $this->setConfigurationFile($configFile);
346            }
347            $this->setBasePath($basePath);
348            $this->setRuntimePath($runtimePath);
349        }
350        else
351            throw new TConfigurationException('application_runtimepath_invalid',$runtimePath);
352
353    }
354
355    /**
356     * Executes the lifecycles of the application.
357     * This is the main entry function that leads to the running of the whole
358     * Prado application.
359     */
360    public function run()
361    {
362        try
363        {
364            $this->initApplication();
365            $n=count(self::$_steps);
366            $this->_step=0;
367            $this->_requestCompleted=false;
368            while($this->_step<$n)
369            {
370                if($this->_mode===self::STATE_OFF)
371                    throw new THttpException(503,'application_unavailable');
372                if($this->_requestCompleted)
373                    break;
374                $method=self::$_steps[$this->_step];
375                Prado::trace("Executing $method()",'System.TApplication');
376                $this->$method();
377                $this->_step++;
378            }
379        }
380        catch(Exception $e)
381        {
382            $this->onError($e);
383        }
384        $this->onEndRequest();
385    }
386
387    /**
388     * Completes current request processing.
389     * This method can be used to exit the application lifecycles after finishing
390     * the current cycle.
391     */
392    public function completeRequest()
393    {
394        $this->_requestCompleted=true;
395    }
396
397    /**
398     * @return boolean whether the current request is processed.
399     */
400    public function getRequestCompleted()
401    {
402        return $this->_requestCompleted;
403    }
404
405    /**
406     * Returns a global value.
407     *
408     * A global value is one that is persistent across users sessions and requests.
409     * @param string the name of the value to be returned
410     * @param mixed the default value. If $key is not found, $defaultValue will be returned
411     * @return mixed the global value corresponding to $key
412     */
413    public function getGlobalState($key,$defaultValue=null)
414    {
415        return isset($this->_globals[$key])?$this->_globals[$key]:$defaultValue;
416    }
417
418    /**
419     * Sets a global value.
420     *
421     * A global value is one that is persistent across users sessions and requests.
422     * Make sure that the value is serializable and unserializable.
423     * @param string the name of the value to be set
424     * @param mixed the global value to be set
425     * @param mixed the default value. If $key is not found, $defaultValue will be returned
426     */
427    public function setGlobalState($key,$value,$defaultValue=null)
428    {
429        $this->_stateChanged=true;
430        if($value===$defaultValue)
431            unset($this->_globals[$key]);
432        else
433            $this->_globals[$key]=$value;
434    }
435
436    /**
437     * Clears a global value.
438     *
439     * The value cleared will no longer be available in this request and the following requests.
440     * @param string the name of the value to be cleared
441     */
442    public function clearGlobalState($key)
443    {
444        $this->_stateChanged=true;
445        unset($this->_globals[$key]);
446    }
447
448    /**
449     * Loads global values from persistent storage.
450     * This method is invoked when {@link onLoadState OnLoadState} event is raised.
451     * After this method, values that are stored in previous requests become
452     * available to the current request via {@link getGlobalState}.
453     */
454    protected function loadGlobals()
455    {
456        $this->_globals=$this->getApplicationStatePersister()->load();
457    }
458
459    /**
460     * Saves global values into persistent storage.
461     * This method is invoked when {@link onSaveState OnSaveState} event is raised.
462     */
463    protected function saveGlobals()
464    {
465        if($this->_stateChanged)
466        {
467            $this->_stateChanged=false;
468            $this->getApplicationStatePersister()->save($this->_globals);
469        }
470    }
471
472    /**
473     * @return string application ID
474     */
475    public function getID()
476    {
477        return $this->_id;
478    }
479
480    /**
481     * @param string application ID
482     */
483    public function setID($value)
484    {
485        $this->_id=$value;
486    }
487
488    /**
489     * @return string an ID that uniquely identifies this Prado application from the others
490     */
491    public function getUniqueID()
492    {
493        return $this->_uniqueID;
494    }
495
496    /**
497     * @return TApplicationMode application mode. Defaults to TApplicationMode::Debug.
498     */
499    public function getMode()
500    {
501        return $this->_mode;
502    }
503
504    /**
505     * @param TApplicationMode application mode
506     */
507    public function setMode($value)
508    {
509        $this->_mode=TPropertyValue::ensureEnum($value,'TApplicationMode');
510    }
511
512    /**
513     * @return string the directory containing the application configuration file (absolute path)
514     */
515    public function getBasePath()
516    {
517        return $this->_basePath;
518    }
519
520    /**
521     * @param string the directory containing the application configuration file
522     */
523    public function setBasePath($value)
524    {
525        $this->_basePath=$value;
526    }
527
528    /**
529     * @return string the application configuration file (absolute path)
530     */
531    public function getConfigurationFile()
532    {
533        return $this->_configFile;
534    }
535
536    /**
537     * @param string the application configuration file (absolute path)
538     */
539    public function setConfigurationFile($value)
540    {
541        $this->_configFile=$value;
542    }
543
544    /**
545     * @return string the directory storing cache data and application-level persistent data. (absolute path)
546     */
547    public function getRuntimePath()
548    {
549        return $this->_runtimePath;
550    }
551
552    /**
553     * @param string the directory storing cache data and application-level persistent data. (absolute path)
554     */
555    public function setRuntimePath($value)
556    {
557        $this->_runtimePath=$value;
558    }
559
560    /**
561     * @return IService the currently requested service
562     */
563    public function getService()
564    {
565        return $this->_service;
566    }
567
568    /**
569     * @param IService the currently requested service
570     */
571    public function setService($value)
572    {
573        $this->_service=$value;
574    }
575
576    /**
577     * Adds a module to application.
578     * Note, this method does not do module initialization.
579     * @param string ID of the module
580     * @param IModule module object
581     */
582    public function setModule($id,IModule $module)
583    {
584        if(isset($this->_modules[$id]))
585            throw new TConfigurationException('application_moduleid_duplicated',$id);
586        else
587            $this->_modules[$id]=$module;
588    }
589
590    /**
591     * @return IModule the module with the specified ID, null if not found
592     */
593    public function getModule($id)
594    {
595        return isset($this->_modules[$id])?$this->_modules[$id]:null;
596    }
597
598    /**
599     * @return array list of loaded application modules, indexed by module IDs
600     */
601    public function getModules()
602    {
603        return $this->_modules;
604    }
605
606    /**
607     * Returns the list of application parameters.
608     * Since the parameters are returned as a {@link TMap} object, you may use
609     * the returned result to access, add or remove individual parameters.
610     * @return TMap the list of application parameters
611     */
612    public function getParameters()
613    {
614        return $this->_parameters;
615    }
616
617    /**
618     * @return THttpRequest the request module
619     */
620    public function getRequest()
621    {
622        if(!$this->_request)
623        {
624            $this->_request=new THttpRequest;
625            $this->_request->init(null);
626        }
627        return $this->_request;
628    }
629
630    /**
631     * @param THttpRequest the request module
632     */
633    public function setRequest(THttpRequest $request)
634    {
635        $this->_request=$request;
636    }
637
638    /**
639     * @return THttpResponse the response module
640     */
641    public function getResponse()
642    {
643        if(!$this->_response)
644        {
645            $this->_response=new THttpResponse;
646            $this->_response->init(null);
647        }
648        return $this->_response;
649    }
650
651    /**
652     * @param THttpRequest the request module
653     */
654    public function setResponse(THttpResponse $response)
655    {
656        $this->_response=$response;
657    }
658
659    /**
660     * @return THttpSession the session module, null if session module is not installed
661     */
662    public function getSession()
663    {
664        if(!$this->_session)
665        {
666            $this->_session=new THttpSession;
667            $this->_session->init(null);
668        }
669        return $this->_session;
670    }
671
672    /**
673     * @param THttpSession the session module
674     */
675    public function setSession(THttpSession $session)
676    {
677        $this->_session=$session;
678    }
679
680    /**
681     * @return TErrorHandler the error handler module
682     */
683    public function getErrorHandler()
684    {
685        if(!$this->_errorHandler)
686        {
687            $this->_errorHandler=new TErrorHandler;
688            $this->_errorHandler->init(null);
689        }
690        return $this->_erro