1: <?php
2: /**
3: * CApplication class file.
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11: /**
12: * CApplication is the base class for all application classes.
13: *
14: * An application serves as the global context that the user request
15: * is being processed. It manages a set of application components that
16: * provide specific functionalities to the whole application.
17: *
18: * The core application components provided by CApplication are the following:
19: * <ul>
20: * <li>{@link getErrorHandler errorHandler}: handles PHP errors and
21: * uncaught exceptions. This application component is dynamically loaded when needed.</li>
22: * <li>{@link getSecurityManager securityManager}: provides security-related
23: * services, such as hashing, encryption. This application component is dynamically
24: * loaded when needed.</li>
25: * <li>{@link getStatePersister statePersister}: provides global state
26: * persistence method. This application component is dynamically loaded when needed.</li>
27: * <li>{@link getCache cache}: provides caching feature. This application component is
28: * disabled by default.</li>
29: * <li>{@link getMessages messages}: provides the message source for translating
30: * application messages. This application component is dynamically loaded when needed.</li>
31: * <li>{@link getCoreMessages coreMessages}: provides the message source for translating
32: * Yii framework messages. This application component is dynamically loaded when needed.</li>
33: * <li>{@link getUrlManager urlManager}: provides URL construction as well as parsing functionality.
34: * This application component is dynamically loaded when needed.</li>
35: * <li>{@link getRequest request}: represents the current HTTP request by encapsulating
36: * the $_SERVER variable and managing cookies sent from and sent to the user.
37: * This application component is dynamically loaded when needed.</li>
38: * <li>{@link getFormat format}: provides a set of commonly used data formatting methods.
39: * This application component is dynamically loaded when needed.</li>
40: * </ul>
41: *
42: * CApplication will undergo the following lifecycles when processing a user request:
43: * <ol>
44: * <li>load application configuration;</li>
45: * <li>set up error handling;</li>
46: * <li>load static application components;</li>
47: * <li>{@link onBeginRequest}: preprocess the user request;</li>
48: * <li>{@link processRequest}: process the user request;</li>
49: * <li>{@link onEndRequest}: postprocess the user request;</li>
50: * </ol>
51: *
52: * Starting from lifecycle 3, if a PHP error or an uncaught exception occurs,
53: * the application will switch to its error handling logic and jump to step 6 afterwards.
54: *
55: * @property string $id The unique identifier for the application.
56: * @property string $basePath The root directory of the application. Defaults to 'protected'.
57: * @property string $runtimePath The directory that stores runtime files. Defaults to 'protected/runtime'.
58: * @property string $extensionPath The directory that contains all extensions. Defaults to the 'extensions' directory under 'protected'.
59: * @property string $language The language that the user is using and the application should be targeted to.
60: * Defaults to the {@link sourceLanguage source language}.
61: * @property string $timeZone The time zone used by this application.
62: * @property CLocale $locale The locale instance.
63: * @property string $localeDataPath The directory that contains the locale data. It defaults to 'framework/i18n/data'.
64: * @property CNumberFormatter $numberFormatter The locale-dependent number formatter.
65: * The current {@link getLocale application locale} will be used.
66: * @property CDateFormatter $dateFormatter The locale-dependent date formatter.
67: * The current {@link getLocale application locale} will be used.
68: * @property CDbConnection $db The database connection.
69: * @property CErrorHandler $errorHandler The error handler application component.
70: * @property CSecurityManager $securityManager The security manager application component.
71: * @property CStatePersister $statePersister The state persister application component.
72: * @property CCache $cache The cache application component. Null if the component is not enabled.
73: * @property CPhpMessageSource $coreMessages The core message translations.
74: * @property CMessageSource $messages The application message translations.
75: * @property CHttpRequest $request The request component.
76: * @property CUrlManager $urlManager The URL manager component.
77: * @property CController $controller The currently active controller. Null is returned in this base class.
78: * @property string $baseUrl The relative URL for the application.
79: * @property string $homeUrl The homepage URL.
80: *
81: * @author Qiang Xue <qiang.xue@gmail.com>
82: * @package system.base
83: * @since 1.0
84: */
85: abstract class CApplication extends CModule
86: {
87: /**
88: * @var string the application name. Defaults to 'My Application'.
89: */
90: public $name='My Application';
91: /**
92: * @var string the charset currently used for the application. Defaults to 'UTF-8'.
93: */
94: public $charset='UTF-8';
95: /**
96: * @var string the language that the application is written in. This mainly refers to
97: * the language that the messages and view files are in. Defaults to 'en_us' (US English).
98: */
99: public $sourceLanguage='en_us';
100: /**
101: * @var string the class used to get locale data. Defaults to 'CLocale'.
102: */
103: public $localeClass='CLocale';
104:
105: private $_id;
106: private $_basePath;
107: private $_runtimePath;
108: private $_extensionPath;
109: private $_globalState;
110: private $_stateChanged;
111: private $_ended=false;
112: private $_language;
113: private $_homeUrl;
114:
115: /**
116: * Processes the request.
117: * This is the place where the actual request processing work is done.
118: * Derived classes should override this method.
119: */
120: abstract public function processRequest();
121:
122: /**
123: * Constructor.
124: * @param mixed $config application configuration.
125: * If a string, it is treated as the path of the file that contains the configuration;
126: * If an array, it is the actual configuration information.
127: * Please make sure you specify the {@link getBasePath basePath} property in the configuration,
128: * which should point to the directory containing all application logic, template and data.
129: * If not, the directory will be defaulted to 'protected'.
130: */
131: public function __construct($config=null)
132: {
133: Yii::setApplication($this);
134:
135: // set basePath at early as possible to avoid trouble
136: if(is_string($config))
137: $config=require($config);
138: if(isset($config['basePath']))
139: {
140: $this->setBasePath($config['basePath']);
141: unset($config['basePath']);
142: }
143: else
144: $this->setBasePath('protected');
145: Yii::setPathOfAlias('application',$this->getBasePath());
146: Yii::setPathOfAlias('webroot',dirname($_SERVER['SCRIPT_FILENAME']));
147: if(isset($config['extensionPath']))
148: {
149: $this->setExtensionPath($config['extensionPath']);
150: unset($config['extensionPath']);
151: }
152: else
153: Yii::setPathOfAlias('ext',$this->getBasePath().DIRECTORY_SEPARATOR.'extensions');
154: if(isset($config['aliases']))
155: {
156: $this->setAliases($config['aliases']);
157: unset($config['aliases']);
158: }
159:
160: $this->preinit();
161:
162: $this->initSystemHandlers();
163: $this->registerCoreComponents();
164:
165: $this->configure($config);
166: $this->attachBehaviors($this->behaviors);
167: $this->preloadComponents();
168:
169: $this->init();
170: }
171:
172:
173: /**
174: * Runs the application.
175: * This method loads static application components. Derived classes usually overrides this
176: * method to do more application-specific tasks.
177: * Remember to call the parent implementation so that static application components are loaded.
178: */
179: public function run()
180: {
181: if($this->hasEventHandler('onBeginRequest'))
182: $this->onBeginRequest(new CEvent($this));
183: register_shutdown_function(array($this,'end'),0,false);
184: $this->processRequest();
185: if($this->hasEventHandler('onEndRequest'))
186: $this->onEndRequest(new CEvent($this));
187: }
188:
189: /**
190: * Terminates the application.
191: * This method replaces PHP's exit() function by calling
192: * {@link onEndRequest} before exiting.
193: * @param integer $status exit status (value 0 means normal exit while other values mean abnormal exit).
194: * @param boolean $exit whether to exit the current request. This parameter has been available since version 1.1.5.
195: * It defaults to true, meaning the PHP's exit() function will be called at the end of this method.
196: */
197: public function end($status=0,$exit=true)
198: {
199: if($this->hasEventHandler('onEndRequest'))
200: $this->onEndRequest(new CEvent($this));
201: if($exit)
202: exit($status);
203: }
204:
205: /**
206: * Raised right BEFORE the application processes the request.
207: * @param CEvent $event the event parameter
208: */
209: public function onBeginRequest($event)
210: {
211: $this->raiseEvent('onBeginRequest',$event);
212: }
213:
214: /**
215: * Raised right AFTER the application processes the request.
216: * @param CEvent $event the event parameter
217: */
218: public function onEndRequest($event)
219: {
220: if(!$this->_ended)
221: {
222: $this->_ended=true;
223: $this->raiseEvent('onEndRequest',$event);
224: }
225: }
226:
227: /**
228: * Returns the unique identifier for the application.
229: * @return string the unique identifier for the application.
230: */
231: public function getId()
232: {
233: if($this->_id!==null)
234: return $this->_id;
235: else
236: return $this->_id=sprintf('%x',crc32($this->getBasePath().$this->name));
237: }
238:
239: /**
240: * Sets the unique identifier for the application.
241: * @param string $id the unique identifier for the application.
242: */
243: public function setId($id)
244: {
245: $this->_id=$id;
246: }
247:
248: /**
249: * Returns the root path of the application.
250: * @return string the root directory of the application. Defaults to 'protected'.
251: */
252: public function getBasePath()
253: {
254: return $this->_basePath;
255: }
256:
257: /**
258: * Sets the root directory of the application.
259: * This method can only be invoked at the begin of the constructor.
260: * @param string $path the root directory of the application.
261: * @throws CException if the directory does not exist.
262: */
263: public function setBasePath($path)
264: {
265: if(($this->_basePath=realpath($path))===false || !is_dir($this->_basePath))
266: throw new CException(Yii::t('yii','Application base path "{path}" is not a valid directory.',
267: array('{path}'=>$path)));
268: }
269:
270: /**
271: * Returns the directory that stores runtime files.
272: * @return string the directory that stores runtime files. Defaults to 'protected/runtime'.
273: */
274: public function getRuntimePath()
275: {
276: if($this->_runtimePath!==null)
277: return $this->_runtimePath;
278: else
279: {
280: $this->setRuntimePath($this->getBasePath().DIRECTORY_SEPARATOR.'runtime');
281: return $this->_runtimePath;
282: }
283: }
284:
285: /**
286: * Sets the directory that stores runtime files.
287: * @param string $path the directory that stores runtime files.
288: * @throws CException if the directory does not exist or is not writable
289: */
290: public function setRuntimePath($path)
291: {
292: if(($runtimePath=realpath($path))===false || !is_dir($runtimePath) || !is_writable($runtimePath))
293: throw new CException(Yii::t('yii','Application runtime path "{path}" is not valid. Please make sure it is a directory writable by the Web server process.',
294: array('{path}'=>$path)));
295: $this->_runtimePath=$runtimePath;
296: }
297:
298: /**
299: * Returns the root directory that holds all third-party extensions.
300: * @return string the directory that contains all extensions. Defaults to the 'extensions' directory under 'protected'.
301: */
302: public function getExtensionPath()
303: {
304: return Yii::getPathOfAlias('ext');
305: }
306:
307: /**
308: * Sets the root directory that holds all third-party extensions.
309: * @param string $path the directory that contains all third-party extensions.
310: * @throws CException if the directory does not exist
311: */
312: public function setExtensionPath($path)
313: {
314: if(($extensionPath=realpath($path))===false || !is_dir($extensionPath))
315: throw new CException(Yii::t('yii','Extension path "{path}" does not exist.',
316: array('{path}'=>$path)));
317: Yii::setPathOfAlias('ext',$extensionPath);
318: }
319:
320: /**
321: * Returns the language that the user is using and the application should be targeted to.
322: * @return string the language that the user is using and the application should be targeted to.
323: * Defaults to the {@link sourceLanguage source language}.
324: */
325: public function getLanguage()
326: {
327: return $this->_language===null ? $this->sourceLanguage : $this->_language;
328: }
329:
330: /**
331: * Specifies which language the application is targeted to.
332: *
333: * This is the language that the application displays to end users.
334: * If set null, it uses the {@link sourceLanguage source language}.
335: *
336: * Unless your application needs to support multiple languages, you should always
337: * set this language to null to maximize the application's performance.
338: * @param string $language the user language (e.g. 'en_US', 'zh_CN').
339: * If it is null, the {@link sourceLanguage} will be used.
340: */
341: public function setLanguage($language)
342: {
343: $this->_language=$language;
344: }
345:
346: /**
347: * Returns the time zone used by this application.
348: * This is a simple wrapper of PHP function date_default_timezone_get().
349: * @return string the time zone used by this application.
350: * @see http://php.net/manual/en/function.date-default-timezone-get.php
351: */
352: public function getTimeZone()
353: {
354: return date_default_timezone_get();
355: }
356:
357: /**
358: * Sets the time zone used by this application.
359: * This is a simple wrapper of PHP function date_default_timezone_set().
360: * @param string $value the time zone used by this application.
361: * @see http://php.net/manual/en/function.date-default-timezone-set.php
362: */
363: public function setTimeZone($value)
364: {
365: date_default_timezone_set($value);
366: }
367:
368: /**
369: * Returns the localized version of a specified file.
370: *
371: * The searching is based on the specified language code. In particular,
372: * a file with the same name will be looked for under the subdirectory
373: * named as the locale ID. For example, given the file "path/to/view.php"
374: * and locale ID "zh_cn", the localized file will be looked for as
375: * "path/to/zh_cn/view.php". If the file is not found, the original file
376: * will be returned.
377: *
378: * For consistency, it is recommended that the locale ID is given
379: * in lower case and in the format of LanguageID_RegionID (e.g. "en_us").
380: *
381: * @param string $srcFile the original file
382: * @param string $srcLanguage the language that the original file is in. If null, the application {@link sourceLanguage source language} is used.
383: * @param string $language the desired language that the file should be localized to. If null, the {@link getLanguage application language} will be used.
384: * @return string the matching localized file. The original file is returned if no localized version is found
385: * or if source language is the same as the desired language.
386: */
387: public function findLocalizedFile($srcFile,$srcLanguage=null,$language=null)
388: {
389: if($srcLanguage===null)
390: $srcLanguage=$this->sourceLanguage;
391: if($language===null)
392: $language=$this->getLanguage();
393: if($language===$srcLanguage)
394: return $srcFile;
395: $desiredFile=dirname($srcFile).DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.basename($srcFile);
396: return is_file($desiredFile) ? $desiredFile : $srcFile;
397: }
398:
399: /**
400: * Returns the locale instance.
401: * @param string $localeID the locale ID (e.g. en_US). If null, the {@link getLanguage application language ID} will be used.
402: * @return an instance of CLocale
403: */
404: public function getLocale($localeID=null)
405: {
406: return call_user_func_array(array($this->localeClass, 'getInstance'),array($localeID===null?$this->getLanguage():$localeID));
407: }
408:
409: /**
410: * Returns the directory that contains the locale data.
411: * @return string the directory that contains the locale data. It defaults to 'framework/i18n/data'.
412: * @since 1.1.0
413: */
414: public function getLocaleDataPath()
415: {
416: $vars=get_class_vars($this->localeClass);
417: if(empty($vars['dataPath']))
418: return Yii::getPathOfAlias('system.i18n.data');
419: return $vars['dataPath'];
420: }
421:
422: /**
423: * Sets the directory that contains the locale data.
424: * @param string $value the directory that contains the locale data.
425: * @since 1.1.0
426: */
427: public function setLocaleDataPath($value)
428: {
429: $property=new ReflectionProperty($this->localeClass,'dataPath');
430: $property->setValue($value);
431: }
432:
433: /**
434: * @return CNumberFormatter the locale-dependent number formatter.
435: * The current {@link getLocale application locale} will be used.
436: */
437: public function getNumberFormatter()
438: {
439: return $this->getLocale()->getNumberFormatter();
440: }
441:
442: /**
443: * Returns the locale-dependent date formatter.
444: * @return CDateFormatter the locale-dependent date formatter.
445: * The current {@link getLocale application locale} will be used.
446: */
447: public function getDateFormatter()
448: {
449: return $this->getLocale()->getDateFormatter();
450: }
451:
452: /**
453: * Returns the database connection component.
454: * @return CDbConnection the database connection
455: */
456: public function getDb()
457: {
458: return $this->getComponent('db');
459: }
460:
461: /**
462: * Returns the error handler component.
463: * @return CErrorHandler the error handler application component.
464: */
465: public function getErrorHandler()
466: {
467: return $this->getComponent('errorHandler');
468: }
469:
470: /**
471: * Returns the security manager component.
472: * @return CSecurityManager the security manager application component.
473: */
474: public function getSecurityManager()
475: {
476: return $this->getComponent('securityManager');
477: }
478:
479: /**
480: * Returns the state persister component.
481: * @return CStatePersister the state persister application component.
482: */
483: public function getStatePersister()
484: {
485: return $this->getComponent('statePersister');
486: }
487:
488: /**
489: * Returns the cache component.
490: * @return CCache the cache application component. Null if the component is not enabled.
491: */
492: public function getCache()
493: {
494: return $this->getComponent('cache');
495: }
496:
497: /**
498: * Returns the core message translations component.
499: * @return CPhpMessageSource the core message translations
500: */
501: public function getCoreMessages()
502: {
503: return $this->getComponent('coreMessages');
504: }
505:
506: /**
507: * Returns the application message translations component.
508: * @return CMessageSource the application message translations
509: */
510: public function getMessages()
511: {
512: return $this->getComponent('messages');
513: }
514:
515: /**
516: * Returns the request component.
517: * @return CHttpRequest the request component
518: */
519: public function getRequest()
520: {
521: return $this->getComponent('request');
522: }
523:
524: /**
525: * Returns the URL manager component.
526: * @return CUrlManager the URL manager component
527: */
528: public function getUrlManager()
529: {
530: return $this->getComponent('urlManager');
531: }
532:
533: /**
534: * @return CController the currently active controller. Null is returned in this base class.
535: * @since 1.1.8
536: */
537: public function getController()
538: {
539: return null;
540: }
541:
542: /**
543: * Creates a relative URL based on the given controller and action information.
544: * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'.
545: * @param array $params additional GET parameters (name=>value). Both the name and value will be URL-encoded.
546: * @param string $ampersand the token separating name-value pairs in the URL.
547: * @return string the constructed URL
548: */
549: public function createUrl($route,$params=array(),$ampersand='&')
550: {
551: return $this->getUrlManager()->createUrl($route,$params,$ampersand);
552: }
553:
554: /**
555: * Creates an absolute URL based on the given controller and action information.
556: * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'.
557: * @param array $params additional GET parameters (name=>value). Both the name and value will be URL-encoded.
558: * @param string $schema schema to use (e.g. http, https). If empty, the schema used for the current request will be used.
559: * @param string $ampersand the token separating name-value pairs in the URL.
560: * @return string the constructed URL
561: */
562: public function createAbsoluteUrl($route,$params=array(),$schema='',$ampersand='&')
563: {
564: $url=$this->createUrl($route,$params,$ampersand);
565: if(strpos($url,'http')===0)
566: return $url;
567: else
568: return $this->getRequest()->getHostInfo($schema).$url;
569: }
570:
571: /**
572: * Returns the relative URL for the application.
573: * This is a shortcut method to {@link CHttpRequest::getBaseUrl()}.
574: * @param boolean $absolute whether to return an absolute URL. Defaults to false, meaning returning a relative one.
575: * @return string the relative URL for the application
576: * @see CHttpRequest::getBaseUrl()
577: */
578: public function getBaseUrl($absolute=false)
579: {
580: return $this->getRequest()->getBaseUrl($absolute);
581: }
582:
583: /**
584: * @return string the homepage URL
585: */
586: public function getHomeUrl()
587: {
588: if($this->_homeUrl===null)
589: {
590: if($this->getUrlManager()->showScriptName)
591: return $this->getRequest()->getScriptUrl();
592: else
593: return $this->getRequest()->getBaseUrl().'/';
594: }
595: else
596: return $this->_homeUrl;
597: }
598:
599: /**
600: * @param string $value the homepage URL
601: */
602: public function setHomeUrl($value)
603: {
604: $this->_homeUrl=$value;
605: }
606:
607: /**
608: * Returns a global value.
609: *
610: * A global value is one that is persistent across users sessions and requests.
611: * @param string $key the name of the value to be returned
612: * @param mixed $defaultValue the default value. If the named global value is not found, this will be returned instead.
613: * @return mixed the named global value
614: * @see setGlobalState
615: */
616: public function getGlobalState($key,$defaultValue=null)
617: {
618: if($this->_globalState===null)
619: $this->loadGlobalState();
620: if(isset($this->_globalState[$key]))
621: return $this->_globalState[$key];
622: else
623: return $defaultValue;
624: }
625:
626: /**
627: * Sets a global value.
628: *
629: * A global value is one that is persistent across users sessions and requests.
630: * Make sure that the value is serializable and unserializable.
631: * @param string $key the name of the value to be saved
632: * @param mixed $value the global value to be saved. It must be serializable.
633: * @param mixed $defaultValue the default value. If the named global value is the same as this value, it will be cleared from the current storage.
634: * @see getGlobalState
635: */
636: public function setGlobalState($key,$value,$defaultValue=null)
637: {
638: if($this->_globalState===null)
639: $this->loadGlobalState();
640:
641: $changed=$this->_stateChanged;
642: if($value===$defaultValue)
643: {
644: if(isset($this->_globalState[$key]))
645: {
646: unset($this->_globalState[$key]);
647: $this->_stateChanged=true;
648: }
649: }
650: elseif(!isset($this->_globalState[$key]) || $this->_globalState[$key]!==$value)
651: {
652: $this->_globalState[$key]=$value;
653: $this->_stateChanged=true;
654: }
655:
656: if($this->_stateChanged!==$changed)
657: $this->attachEventHandler('onEndRequest',array($this,'saveGlobalState'));
658: }
659:
660: /**
661: * Clears a global value.
662: *
663: * The value cleared will no longer be available in this request and the following requests.
664: * @param string $key the name of the value to be cleared
665: */
666: public function clearGlobalState($key)
667: {
668: $this->setGlobalState($key,true,true);
669: }
670:
671: /**
672: * Loads the global state data from persistent storage.
673: * @see getStatePersister
674: * @throws CException if the state persister is not available
675: */
676: public function loadGlobalState()
677: {
678: $persister=$this->getStatePersister();
679: if(($this->_globalState=$persister->load())===null)
680: $this->_globalState=array();
681: $this->_stateChanged=false;
682: $this->detachEventHandler('onEndRequest',array($this,'saveGlobalState'));
683: }
684:
685: /**
686: * Saves the global state data into persistent storage.
687: * @see getStatePersister
688: * @throws CException if the state persister is not available
689: */
690: public function saveGlobalState()
691: {
692: if($this->_stateChanged)
693: {
694: $this->_stateChanged=false;
695: $this->detachEventHandler('onEndRequest',array($this,'saveGlobalState'));
696: $this->getStatePersister()->save($this->_globalState);
697: }
698: }
699:
700: /**
701: * Handles uncaught PHP exceptions.
702: *
703: * This method is implemented as a PHP exception handler. It requires
704: * that constant YII_ENABLE_EXCEPTION_HANDLER be defined true.
705: *
706: * This method will first raise an {@link onException} event.
707: * If the exception is not handled by any event handler, it will call
708: * {@link getErrorHandler errorHandler} to process the exception.
709: *
710: * The application will be terminated by this method.
711: *
712: * @param Exception $exception exception that is not caught
713: */
714: public function handleException($exception)
715: {
716: // disable error capturing to avoid recursive errors
717: restore_error_handler();
718: restore_exception_handler();
719:
720: $category='exception.'.get_class($exception);
721: if($exception instanceof CHttpException)
722: $category.='.'.$exception->statusCode;
723: // php <5.2 doesn't support string conversion auto-magically
724: $message=$exception->__toString();
725: if(isset($_SERVER['REQUEST_URI']))
726: $message.="\nREQUEST_URI=".$_SERVER['REQUEST_URI'];
727: if(isset($_SERVER['HTTP_REFERER']))
728: $message.="\nHTTP_REFERER=".$_SERVER['HTTP_REFERER'];
729: $message.="\n---";
730: Yii::log($message,CLogger::LEVEL_ERROR,$category);
731:
732: try
733: {
734: $event=new CExceptionEvent($this,$exception);
735: $this->onException($event);
736: if(!$event->handled)
737: {
738: // try an error handler
739: if(($handler=$this->getErrorHandler())!==null)
740: $handler->handle($event);
741: else
742: $this->displayException($exception);
743: }
744: }
745: catch(Exception $e)
746: {
747: $this->displayException($e);
748: }
749:
750: try
751: {
752: $this->end(1);
753: }
754: catch(Exception $e)
755: {
756: // use the most primitive way to log error
757: $msg = get_class($e).': '.$e->getMessage().' ('.$e->getFile().':'.$e->getLine().")\n";
758: $msg .= $e->getTraceAsString()."\n";
759: $msg .= "Previous exception:\n";
760: $msg .= get_class($exception).': '.$exception->getMessage().' ('.$exception->getFile().':'.$exception->getLine().")\n";
761: $msg .= $exception->getTraceAsString()."\n";
762: $msg .= '$_SERVER='.var_export($_SERVER,true);
763: error_log($msg);
764: exit(1);
765: }
766: }
767:
768: /**
769: * Handles PHP execution errors such as warnings, notices.
770: *
771: * This method is implemented as a PHP error handler. It requires
772: * that constant YII_ENABLE_ERROR_HANDLER be defined true.
773: *
774: * This method will first raise an {@link onError} event.
775: * If the error is not handled by any event handler, it will call
776: * {@link getErrorHandler errorHandler} to process the error.
777: *
778: * The application will be terminated by this method.
779: *
780: * @param integer $code the level of the error raised
781: * @param string $message the error message
782: * @param string $file the filename that the error was raised in
783: * @param integer $line the line number the error was raised at
784: */
785: public function handleError($code,$message,$file,$line)
786: {
787: if($code & error_reporting())
788: {
789: // disable error capturing to avoid recursive errors
790: restore_error_handler();
791: restore_exception_handler();
792:
793: $log="$message ($file:$line)\nStack trace:\n";
794: $trace=debug_backtrace();
795: // skip the first 3 stacks as they do not tell the error position
796: if(count($trace)>3)
797: $trace=array_slice($trace,3);
798: foreach($trace as $i=>$t)
799: {
800: if(!isset($t['file']))
801: $t['file']='unknown';
802: if(!isset($t['line']))
803: $t['line']=0;
804: if(!isset($t['function']))
805: $t['function']='unknown';
806: $log.="#$i {$t['file']}({$t['line']}): ";
807: if(isset($t['object']) && is_object($t['object']))
808: $log.=get_class($t['object']).'->';
809: $log.="{$t['function']}()\n";
810: }
811: if(isset($_SERVER['REQUEST_URI']))
812: $log.='REQUEST_URI='.$_SERVER['REQUEST_URI'];
813: Yii::log($log,CLogger::LEVEL_ERROR,'php');
814:
815: try
816: {
817: Yii::import('CErrorEvent',true);
818: $event=new CErrorEvent($this,$code,$message,$file,$line);
819: $this->onError($event);
820: if(!$event->handled)
821: {
822: // try an error handler
823: if(($handler=$this->getErrorHandler())!==null)
824: $handler->handle($event);
825: else
826: $this->displayError($code,$message,$file,$line);
827: }
828: }
829: catch(Exception $e)
830: {
831: $this->displayException($e);
832: }
833:
834: try
835: {
836: $this->end(1);
837: }
838: catch(Exception $e)
839: {
840: // use the most primitive way to log error
841: $msg = get_class($e).': '.$e->getMessage().' ('.$e->getFile().':'.$e->getLine().")\n";
842: $msg .= $e->getTraceAsString()."\n";
843: $msg .= "Previous error:\n";
844: $msg .= $log."\n";
845: $msg .= '$_SERVER='.var_export($_SERVER,true);
846: error_log($msg);
847: exit(1);
848: }
849: }
850: }
851:
852: /**
853: * Raised when an uncaught PHP exception occurs.
854: *
855: * An event handler can set the {@link CExceptionEvent::handled handled}
856: * property of the event parameter to be true to indicate no further error
857: * handling is needed. Otherwise, the {@link getErrorHandler errorHandler}
858: * application component will continue processing the error.
859: *
860: * @param CExceptionEvent $event event parameter
861: */
862: public function onException($event)
863: {
864: $this->raiseEvent('onException',$event);
865: }
866:
867: /**
868: * Raised when a PHP execution error occurs.
869: *
870: * An event handler can set the {@link CErrorEvent::handled handled}
871: * property of the event parameter to be true to indicate no further error
872: * handling is needed. Otherwise, the {@link getErrorHandler errorHandler}
873: * application component will continue processing the error.
874: *
875: * @param CErrorEvent $event event parameter
876: */
877: public function onError($event)
878: {
879: $this->raiseEvent('onError',$event);
880: }
881:
882: /**
883: * Displays the captured PHP error.
884: * This method displays the error in HTML when there is
885: * no active error handler.
886: * @param integer $code error code
887: * @param string $message error message
888: * @param string $file error file
889: * @param string $line error line
890: */
891: public function displayError($code,$message,$file,$line)
892: {
893: if(YII_DEBUG)
894: {
895: echo "<h1>PHP Error [$code]</h1>\n";
896: echo "<p>$message ($file:$line)</p>\n";
897: echo '<pre>';
898:
899: $trace=debug_backtrace();
900: // skip the first 3 stacks as they do not tell the error position
901: if(count($trace)>3)
902: $trace=array_slice($trace,3);
903: foreach($trace as $i=>$t)
904: {
905: if(!isset($t['file']))
906: $t['file']='unknown';
907: if(!isset($t['line']))
908: $t['line']=0;
909: if(!isset($t['function']))
910: $t['function']='unknown';
911: echo "#$i {$t['file']}({$t['line']}): ";
912: if(isset($t['object']) && is_object($t['object']))
913: echo get_class($t['object']).'->';
914: echo "{$t['function']}()\n";
915: }
916:
917: echo '</pre>';
918: }
919: else
920: {
921: echo "<h1>PHP Error [$code]</h1>\n";
922: echo "<p>$message</p>\n";
923: }
924: }
925:
926: /**
927: * Displays the uncaught PHP exception.
928: * This method displays the exception in HTML when there is
929: * no active error handler.
930: * @param Exception $exception the uncaught exception
931: */
932: public function displayException($exception)
933: {
934: if(YII_DEBUG)
935: {
936: echo '<h1>'.get_class($exception)."</h1>\n";
937: echo '<p>'.$exception->getMessage().' ('.$exception->getFile().':'.$exception->getLine().')</p>';
938: echo '<pre>'.$exception->getTraceAsString().'</pre>';
939: }
940: else
941: {
942: echo '<h1>'.get_class($exception)."</h1>\n";
943: echo '<p>'.$exception->getMessage().'</p>';
944: }
945: }
946:
947: /**
948: * Initializes the error handlers.
949: */
950: protected function initSystemHandlers()
951: {
952: if(YII_ENABLE_EXCEPTION_HANDLER)
953: set_exception_handler(array($this,'handleException'));
954: if(YII_ENABLE_ERROR_HANDLER)
955: set_error_handler(array($this,'handleError'),error_reporting());
956: }
957:
958: /**
959: * Registers the core application components.
960: * @see setComponents
961: */
962: protected function registerCoreComponents()
963: {
964: $components=array(
965: 'coreMessages'=>array(
966: 'class'=>'CPhpMessageSource',
967: 'language'=>'en_us',
968: 'basePath'=>YII_PATH.DIRECTORY_SEPARATOR.'messages',
969: ),
970: 'db'=>array(
971: 'class'=>'CDbConnection',
972: ),
973: 'messages'=>array(
974: 'class'=>'CPhpMessageSource',
975: ),
976: 'errorHandler'=>array(
977: 'class'=>'CErrorHandler',
978: ),
979: 'securityManager'=>array(
980: 'class'=>'CSecurityManager',
981: ),
982: 'statePersister'=>array(
983: 'class'=>'CStatePersister',
984: ),
985: 'urlManager'=>array(
986: 'class'=>'CUrlManager',
987: ),
988: 'request'=>array(
989: 'class'=>'CHttpRequest',
990: ),
991: 'format'=>array(
992: 'class'=>'CFormatter',
993: ),
994: );
995:
996: $this->setComponents($components);
997: }
998: }
999: