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