1<?php 2// +----------------------------------------------------------------------+ 3// | PEAR_Warning | 4// +----------------------------------------------------------------------+ 5// | Copyright (c) 2004 The PEAR Group | 6// +----------------------------------------------------------------------+ 7// | This source file is subject to version 3.0 of the PHP license, | 8// | that is bundled with this package in the file LICENSE, and is | 9// | available at through the world-wide-web at | 10// | http://www.php.net/license/3_0.txt. | 11// | If you did not receive a copy of the PHP license and are unable to | 12// | obtain it through the world-wide-web, please send a note to | 13// | license@php.net so we can mail you a copy immediately. | 14// +----------------------------------------------------------------------+ 15// | Authors: Greg Beaver <cellog@php.net> | 16// +----------------------------------------------------------------------+ 17// 18require_once 'PEAR/Exception.php'; 19 20/** 21 * Exception class for internal PEAR_Warning exceptions 22 * @package PEAR 23 */ 24class PEAR_WarningException extends PEAR_Exception {} 25 26interface PEAR_WarningInterface 27{ 28 /** 29 * Get the severity of this warning ('warning', 'notice') 30 * @return string 31 */ 32 public function getLevel(); 33} 34 35/** 36 * Warning mechanism for PEAR PHP5-only packages. 37 * 38 * For users: 39 * 40 * Unlike PEAR_ErrorStack, PEAR_Warning is designed to be used on a transactional basis. 41 * 42 * <code> 43 * <?php 44 * require_once 'PEAR/Warning.php'; 45 * require_once 'PEAR/Exception.php'; 46 * class Mypackage_Exception extends PEAR_Exception {} 47 * PEAR_Warning::begin(); 48 * $c = new Somepackage; 49 * $c->doSomethingComplex(); 50 * if (PEAR_Warning::hasWarnings()) { 51 * $warnings = PEAR_Warning::end(); 52 * throw new Mypackage_Exception('unclean doSomethingComplex', $warnings); 53 * } else { 54 * $c->doSomethingElse(); 55 * } 56 * ?> 57 * </code> 58 * 59 * Only warnings that occur between ::begin() and ::end() will be processed. Remember, 60 * a warning is a non-fatal error, exceptions will be used for unrecoverable errors in 61 * all PEAR packages, and you should follow this model to be safe! 62 * 63 * For developers: 64 * 65 * This class can be used globally or locally. For global use, a 66 * series of static methods have been provided. The class is designed 67 * for lazy loading, and so the following code will work, and increase 68 * efficiency on production servers: 69 * 70 * <code> 71 * <?php 72 * if (class_exists('PEAR_Warning')) { 73 * PEAR_Warning::add(1, 'mypackage', 'possible mis-spelling of something'); 74 * } 75 * ?> 76 * </code> 77 * 78 * This means that PEAR_Warning can literally be used without the need for 79 * require_once 'PEAR/Warning.php';! 80 * 81 * You can also pass in an exception class as a warning 82 * 83 * <code> 84 * <?php 85 * class MyPackage_Warning extends PEAR_Exception {} 86 * PEAR_Warning::add(new MyPackage_Warning('some info')); 87 * ?> 88 * </code> 89 * 90 * An interface is provided to allow for severity differentiation 91 * 92 * <code> 93 * <?php 94 * class MyPackage_Warning extends PEAR_Exception implements PEAR_WarningInterface 95 * { 96 * private $_level = 'warning'; 97 * function __construct($message, $level = 'warning', $p1 = null, $p2 = null) 98 * { 99 * $this->_level = $level; 100 * parent::__construct($message, $p1, $p2); 101 * } 102 * 103 * public function getLevel() 104 * { 105 * return $this->_level; 106 * } 107 * } 108 * PEAR_Warning::add(new MyPackage_Warning('some info', 'notice')); 109 * ?> 110 * </code> 111 * 112 * This can be used with {@link setErrorHandling()} to ignore warnings of different severities 113 * for complex error situations. 114 * 115 * For local situations like an internal warning system for a parser that may become the cause 116 * of a single PEAR_Exception, PEAR_Warning can also be instantiated and used without any connection 117 * to the global warning stack. 118 * @package PEAR 119 */ 120class PEAR_Warning 121{ 122 /** 123 * properties used for global warning stacks 124 */ 125 protected static $_hasWarnings = false; 126 127 protected static $warnings = array(); 128 protected static $go = false; 129 protected static $levels = array('warning', 'notice'); 130 131 private static $_observers = array(); 132 private static $_uniqueid = 0; 133 /** 134 * properties used for instantiation of private warning stack 135 */ 136 private $_warnings = array(); 137 private $_go = false; 138 private $_context; 139 140 /** 141 * Begin tracking all global warnings 142 */ 143 static public function begin() 144 { 145 if (class_exists('PEAR_ErrorStack')) { 146 PEAR_ErrorStack::setPEARWarningCallback(array('PEAR_Warning', '_catch')); 147 } 148 self::$go = true; 149 self::$_hasWarnings = false; 150 } 151 152 /** 153 * @return bool 154 */ 155 static public function hasWarnings() 156 { 157 return self::$_hasWarnings; 158 } 159 160 /** 161 * Stop tracking global warnings 162 * @return array an array of all warnings in array and PEAR_Exception format 163 * suitable for use as a PEAR_Exception cause 164 */ 165 static public function end() 166 { 167 if (class_exists('PEAR_ErrorStack')) { 168 PEAR_ErrorStack::setPEARWarningCallback(false); 169 } 170 self::$go = false; 171 self::$_hasWarnings = false; 172 $a = self::$warnings; 173 self::$warnings = array(); 174 return $a; 175 } 176 177 /** 178 * @param mixed A valid callback that accepts either a 179 * PEAR_Exception or PEAR_ErrorStack-style array 180 * @param string The name of the observer. Use this if you want 181 * to remove it later with removeObserver(). 182 * {@link getUniqueId()} can be used to generate a label 183 */ 184 public static function addObserver($callback, $label = 'default') 185 { 186 self::$_observers[$label] = $callback; 187 } 188 189 /** 190 * @param mixed observer ID 191 */ 192 public static function removeObserver($label = 'default') 193 { 194 unset(self::$_observers[$label]); 195 } 196 197 /** 198 * @return int unique identifier for an observer 199 */ 200 public static function getUniqueId() 201 { 202 return self::$_uniqueid++; 203 } 204 205 /** 206 * Set the warning levels that should be captured by the warning mechanism 207 * 208 * WARNING: no error checking or spell checking. 209 * @param array 210 */ 211 public static function setErrorHandling($levels) 212 { 213 self::$_levels = $levels; 214 } 215 216 /** 217 * Add a warning to the global warning stack. 218 * 219 * Note: if you want file/line context, use an exception object 220 * @param PEAR_Exception|string|int Either pass in an exception to use as the warning, or an 221 * error code or some other error class differentiation technique 222 * @param string Package is required if $codeOrException is not a PEAR_Exception object 223 * @param string Error message, use %param% to do automatic parameter replacement from $params 224 * @param array Error parameters 225 * @param string Error level, use the English name 226 227 * @throws PEAR_WarningException if $codeOrException is not a PEAR_Exception and $package is not set 228 */ 229 static public function add($codeOrException, $package = '', $msg = '', $params = array(), 230 $level = 'warning') 231 { 232 if ($codeOrException instanceof PEAR_Exception) { 233 if ($codeOrException instanceof PEAR_WarningInterface) { 234 if (in_array($codeOrException->getLevel(), self::$levels)) { 235 self::_signal($codeOrException); 236 } 237 } else { 238 self::_signal($codeOrException); 239 } 240 } else { 241 if (empty($package)) { 242 throw new PEAR_WarningException('Package must be set for a non-exception warning'); 243 } 244 if (in_array($level, self::$levels)) { 245 $warning = self::_formatWarning($codeOrException, $package, $level, $msg, $params); 246 self::_signal($warning); 247 } 248 } 249 if (self::$go) { 250 self::$_hasWarnings = true; 251 self::$warnings[] = $warning; 252 } 253 } 254 255 /** 256 * @param string the package name, or other context information that can be used 257 * to differentiate this warning from warnings thrown by other packages 258 * @throws PEAR_WarningException if $context is not a string 259 */ 260 public function __construct($context) 261 { 262 if (!is_string($context)) { 263 throw new PEAR_WarningException('$context constructor argument must be a string'); 264 } 265 $this->_context = $context; 266 } 267 268 /** 269 * Local stack function for adding a warning - note that package is not needed, as it is 270 * defined in the constructor. 271 * 272 * Note: if you want file/line context, use an exception object 273 * @param PEAR_Exception|string|int Either pass in an exception to use as the warning, or an 274 * error code or some other error class differentiation technique 275 * @param string Error message, use %param% to do automatic parameter replacement from $params 276 * @param array Error parameters 277 * @param string Error level, use the English name 278 */ 279 public function localAdd($code, $msg = '', $params = array(), $level = 'warning') 280 { 281 if ($codeOrException instanceof PEAR_Exception) { 282 $this->_warnings[] = $codeOrException; 283 } else { 284 $warning = self::_formatWarning($codeOrException, $this->_context, $level, $msg); 285 $this->_warnings[] = $warning; 286 } 287 } 288 289 /** 290 * Begin a local warning stack session 291 */ 292 public function localBegin() 293 { 294 $this->_warnings = array(); 295 $this->_go = true; 296 } 297 298 /** 299 * End a local warning stack session 300 * @return array 301 */ 302 public function localEnd() 303 { 304 $a = $this->_warnings; 305 $this->_warnings = array(); 306 $this->_go = false; 307 return $a; 308 } 309 310 /** 311 * Do not use this function directly - it should only be used by PEAR_ErrorStack 312 * @access private 313 */ 314 static public function _catch($err) 315 { 316 self::_signal($err); 317 } 318 319 private static function _signal($warning) 320 { 321 foreach (self::$_observers as $func) { 322 if (is_callable($func)) { 323 call_user_func($func, $this); 324 continue; 325 } 326 settype($func, 'array'); 327 switch ($func[0]) { 328 case PEAR_EXCEPTION_PRINT : 329 $f = (isset($func[1])) ? $func[1] : '%s'; 330 printf($f, $this->getMessage()); 331 break; 332 case PEAR_EXCEPTION_TRIGGER : 333 $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE; 334 trigger_error($this->getMessage(), $f); 335 break; 336 case PEAR_EXCEPTION_DIE : 337 $f = (isset($func[1])) ? $func[1] : '%s'; 338 die(printf($f, $this->getMessage())); 339 break; 340 default: 341 trigger_error('invalid observer type', E_USER_WARNING); 342 } 343 } 344 } 345 346 static private function _formatWarning($code, $package, $level, $msg, $params, $backtrace) 347 { 348 return array('package' => $package, 349 'code' => $code, 350 'level' => $level, 351 'message' => self::_formatMessage($msg, $params), 352 'params' => $params); 353 } 354 355 static private function _formatMessage($msg, $params) 356 { 357 if (count($params)) { 358 foreach ($params as $name => $val) { 359 if (strpos($msg, '%' . $name . '%') !== false) { 360 if (is_array($val)) { 361 // don't pass in an array that you expect to display unless it is 1-dimensional! 362 $val = implode(', ', $val); 363 } 364 if (is_object($val)) { 365 if (method_exists($val, '__toString')) { 366 $val = $val->__toString(); 367 } else { 368 $val = 'Object'; 369 } 370 } 371 $msg = str_replace('%' . $name . '%', $val, $msg); 372 } 373 } 374 } 375 return $msg; 376 } 377} 378?>