PEAR_Exception   F
last analyzed

Complexity

Total Complexity 66

Size/Duplication

Total Lines 359
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 169
dl 0
loc 359
rs 3.12
c 0
b 0
f 0
wmc 66

14 Methods

Rating   Name   Duplication   Size   Complexity  
A toText() 0 11 2
A removeObserver() 0 3 1
C getCauseMessage() 0 53 17
A getErrorClass() 0 4 1
A getErrorData() 0 3 1
A getUniqueId() 0 3 1
B __construct() 0 27 10
C toHtml() 0 60 16
A addObserver() 0 3 1
A getErrorMethod() 0 4 1
A getCause() 0 3 1
A __toString() 0 6 2
B signal() 0 23 9
A getTraceSafe() 0 10 3

How to fix   Complexity   

Complex Class

Complex classes like PEAR_Exception often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PEAR_Exception, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
3
/**
4
 * PEAR_Exception
5
 *
6
 * PHP version 5
7
 *
8
 * @category  PEAR
9
 * @package   PEAR_Exception
10
 * @author    Tomas V. V. Cox <[email protected]>
11
 * @author    Hans Lellelid <[email protected]>
12
 * @author    Bertrand Mansion <[email protected]>
13
 * @author    Greg Beaver <[email protected]>
14
 * @copyright 1997-2009 The Authors
15
 * @license   http://opensource.org/licenses/bsd-license.php New BSD License
16
 * @link      http://pear.php.net/package/PEAR_Exception
17
 * @since     File available since Release 1.0.0
18
 */
19
namespace PEAR\Exception;
20
21
use Exception;
22
23
/**
24
 * Base PEAR_Exception Class
25
 *
26
 * 1) Features:
27
 *
28
 * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception))
29
 * - Definable triggers, shot when exceptions occur
30
 * - Pretty and informative error messages
31
 * - Added more context info available (like class, method or cause)
32
 * - cause can be a PEAR_Exception or an array of mixed
33
 *   PEAR_Exceptions/PEAR_ErrorStack warnings
34
 * - callbacks for specific exception classes and their children
35
 *
36
 * 2) Ideas:
37
 *
38
 * - Maybe a way to define a 'template' for the output
39
 *
40
 * 3) Inherited properties from PHP Exception Class:
41
 *
42
 * protected $message
43
 * protected $code
44
 * protected $line
45
 * protected $file
46
 * private   $trace
47
 *
48
 * 4) Inherited methods from PHP Exception Class:
49
 *
50
 * __clone
51
 * __construct
52
 * getMessage
53
 * getCode
54
 * getFile
55
 * getLine
56
 * getTraceSafe
57
 * getTraceSafeAsString
58
 * __toString
59
 *
60
 * 5) Usage example
61
 *
62
 * <code>
63
 *  require_once 'PEAR/PEAR_Exception.php';
64
 *
65
 *  class Test {
66
 *     function foo() {
67
 *         throw new PEAR_Exception('Error Message', ERROR_CODE);
68
 *     }
69
 *  }
70
 *
71
 *  function myLogger($pear_exception) {
72
 *     echo $pear_exception->getMessage();
73
 *  }
74
 *  // each time a exception is thrown the 'myLogger' will be called
75
 *  // (its use is completely optional)
76
 *  PEAR_Exception::addObserver('myLogger');
77
 *  $test = new Test;
78
 *  try {
79
 *     $test->foo();
80
 *  } catch (PEAR_Exception $e) {
81
 *     print $e;
82
 *  }
83
 * </code>
84
 *
85
 * @category  PEAR
86
 * @package   PEAR_Exception
87
 * @author    Tomas V.V.Cox <[email protected]>
88
 * @author    Hans Lellelid <[email protected]>
89
 * @author    Bertrand Mansion <[email protected]>
90
 * @author    Greg Beaver <[email protected]>
91
 * @copyright 1997-2009 The Authors
92
 * @license   http://opensource.org/licenses/bsd-license.php New BSD License
93
 * @version   Release: @package_version@
94
 * @link      http://pear.php.net/package/PEAR_Exception
95
 * @since     Class available since Release 1.0.0
96
 */
97
class PEAR_Exception extends Exception
98
{
99
    const OBSERVER_PRINT = -2;
100
    const OBSERVER_TRIGGER = -4;
101
    const OBSERVER_DIE = -8;
102
    protected $cause;
103
    private static $_observers = array();
104
    private static $_uniqueid = 0;
105
    private $_trace;
106
107
    /**
108
     * Supported signatures:
109
     *  - PEAR_Exception(string $message);
110
     *  - PEAR_Exception(string $message, int $code);
111
     *  - PEAR_Exception(string $message, Exception $cause);
112
     *  - PEAR_Exception(string $message, Exception $cause, int $code);
113
     *  - PEAR_Exception(string $message, PEAR_Error $cause);
114
     *  - PEAR_Exception(string $message, PEAR_Error $cause, int $code);
115
     *  - PEAR_Exception(string $message, array $causes);
116
     *  - PEAR_Exception(string $message, array $causes, int $code);
117
     *
118
     * @param string                              $message exception message
119
     * @param int|Exception|PEAR_Error|array|null $p2      exception cause
120
     * @param int|null                            $p3      exception code or null
121
     */
122
    public function __construct($message, $p2 = null, $p3 = null)
123
    {
124
        if (is_int($p2)) {
125
            $code = $p2;
126
            $this->cause = null;
127
        } elseif (is_object($p2) || is_array($p2)) {
128
            // using is_object allows both Exception and PEAR_Error
129
            if (is_object($p2) && !($p2 instanceof Exception)) {
130
                if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) {
0 ignored issues
show
Bug introduced by
The type PEAR\Exception\PEAR_Error was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
131
                    throw new PEAR_Exception(
132
                        'exception cause must be Exception, ' .
133
                        'array, or PEAR_Error'
134
                    );
135
                }
136
            }
137
            $code = $p3;
138
            if (is_array($p2) && isset($p2['message'])) {
139
                // fix potential problem of passing in a single warning
140
                $p2 = array($p2);
141
            }
142
            $this->cause = $p2;
143
        } else {
144
            $code = null;
145
            $this->cause = null;
146
        }
147
        parent::__construct($message, $code);
148
        $this->signal();
149
    }
150
151
    /**
152
     * Add an exception observer
153
     *
154
     * @param mixed  $callback - A valid php callback, see php func is_callable()
155
     *                         - A PEAR_Exception::OBSERVER_* constant
156
     *                         - An array(const PEAR_Exception::OBSERVER_*,
157
     *                           mixed $options)
158
     * @param string $label    The name of the observer. Use this if you want
159
     *                         to remove it later with removeObserver()
160
     *
161
     * @return void
162
     */
163
    public static function addObserver($callback, $label = 'default')
164
    {
165
        self::$_observers[$label] = $callback;
166
    }
167
168
    /**
169
     * Remove an exception observer
170
     *
171
     * @param string $label Name of the observer
172
     *
173
     * @return void
174
     */
175
    public static function removeObserver($label = 'default')
176
    {
177
        unset(self::$_observers[$label]);
178
    }
179
180
    /**
181
     * Generate a unique ID for an observer
182
     *
183
     * @return int unique identifier for an observer
184
     */
185
    public static function getUniqueId()
186
    {
187
        return self::$_uniqueid++;
188
    }
189
190
    /**
191
     * Send a signal to all observers
192
     *
193
     * @return void
194
     */
195
    protected function signal()
196
    {
197
        foreach (self::$_observers as $func) {
198
            if (is_callable($func)) {
199
                call_user_func($func, $this);
200
                continue;
201
            }
202
            settype($func, 'array');
203
            switch ($func[0]) {
204
            case self::OBSERVER_PRINT :
205
                $f = (isset($func[1])) ? $func[1] : '%s';
206
                printf($f, $this->getMessage());
207
                break;
208
            case self::OBSERVER_TRIGGER :
209
                $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
210
                trigger_error($this->getMessage(), $f);
211
                break;
212
            case self::OBSERVER_DIE :
213
                $f = (isset($func[1])) ? $func[1] : '%s';
214
                die(printf($f, $this->getMessage()));
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
215
                break;
216
            default:
217
                trigger_error('invalid observer type', E_USER_WARNING);
218
            }
219
        }
220
    }
221
222
    /**
223
     * Return specific error information that can be used for more detailed
224
     * error messages or translation.
225
     *
226
     * This method may be overridden in child exception classes in order
227
     * to add functionality not present in PEAR_Exception and is a placeholder
228
     * to define API
229
     *
230
     * The returned array must be an associative array of parameter => value like so:
231
     * <pre>
232
     * array('name' => $name, 'context' => array(...))
233
     * </pre>
234
     *
235
     * @return array
236
     */
237
    public function getErrorData()
238
    {
239
        return array();
240
    }
241
242
    /**
243
     * Returns the exception that caused this exception to be thrown
244
     *
245
     * @return Exception|array The context of the exception
246
     */
247
    public function getCause()
248
    {
249
        return $this->cause;
250
    }
251
252
    /**
253
     * Function must be public to call on caused exceptions
254
     *
255
     * @param array $causes Array that gets filled.
256
     *
257
     * @return void
258
     */
259
    public function getCauseMessage(&$causes)
260
    {
261
        $trace = $this->getTraceSafe();
262
        $cause = array('class'   => get_class($this),
263
                       'message' => $this->message,
264
                       'file' => 'unknown',
265
                       'line' => 'unknown');
266
        if (isset($trace[0])) {
267
            if (isset($trace[0]['file'])) {
268
                $cause['file'] = $trace[0]['file'];
269
                $cause['line'] = $trace[0]['line'];
270
            }
271
        }
272
        $causes[] = $cause;
273
        if ($this->cause instanceof PEAR_Exception) {
274
            $this->cause->getCauseMessage($causes);
275
        } elseif ($this->cause instanceof Exception) {
276
            $causes[] = array('class'   => get_class($this->cause),
277
                              'message' => $this->cause->getMessage(),
278
                              'file' => $this->cause->getFile(),
279
                              'line' => $this->cause->getLine());
280
        } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) {
281
            $causes[] = array('class' => get_class($this->cause),
282
                              'message' => $this->cause->getMessage(),
283
                              'file' => 'unknown',
284
                              'line' => 'unknown');
285
        } elseif (is_array($this->cause)) {
286
            foreach ($this->cause as $cause) {
287
                if ($cause instanceof PEAR_Exception) {
288
                    $cause->getCauseMessage($causes);
289
                } elseif ($cause instanceof Exception) {
290
                    $causes[] = array('class'   => get_class($cause),
291
                                   'message' => $cause->getMessage(),
292
                                   'file' => $cause->getFile(),
293
                                   'line' => $cause->getLine());
294
                } elseif (class_exists('PEAR_Error')
295
                    && $cause instanceof PEAR_Error
296
                ) {
297
                    $causes[] = array('class' => get_class($cause),
298
                                      'message' => $cause->getMessage(),
299
                                      'file' => 'unknown',
300
                                      'line' => 'unknown');
301
                } elseif (is_array($cause) && isset($cause['message'])) {
302
                    // PEAR_ErrorStack warning
303
                    $causes[] = array(
304
                        'class' => $cause['package'],
305
                        'message' => $cause['message'],
306
                        'file' => isset($cause['context']['file']) ?
307
                                            $cause['context']['file'] :
308
                                            'unknown',
309
                        'line' => isset($cause['context']['line']) ?
310
                                            $cause['context']['line'] :
311
                                            'unknown',
312
                    );
313
                }
314
            }
315
        }
316
    }
317
318
    /**
319
     * Build a backtrace and return it
320
     *
321
     * @return array Backtrace
322
     */
323
    public function getTraceSafe()
324
    {
325
        if (!isset($this->_trace)) {
326
            $this->_trace = $this->getTrace();
327
            if (empty($this->_trace)) {
328
                $backtrace = debug_backtrace();
329
                $this->_trace = array($backtrace[count($backtrace)-1]);
330
            }
331
        }
332
        return $this->_trace;
333
    }
334
335
    /**
336
     * Gets the first class of the backtrace
337
     *
338
     * @return string Class name
339
     */
340
    public function getErrorClass()
341
    {
342
        $trace = $this->getTraceSafe();
343
        return $trace[0]['class'];
344
    }
345
346
    /**
347
     * Gets the first method of the backtrace
348
     *
349
     * @return string Method/function name
350
     */
351
    public function getErrorMethod()
352
    {
353
        $trace = $this->getTraceSafe();
354
        return $trace[0]['function'];
355
    }
356
357
    /**
358
     * Converts the exception to a string (HTML or plain text)
359
     *
360
     * @return string String representation
361
     *
362
     * @see toHtml()
363
     * @see toText()
364
     */
365
    public function __toString()
366
    {
367
        if (isset($_SERVER['REQUEST_URI'])) {
368
            return $this->toHtml();
369
        }
370
        return $this->toText();
371
    }
372
373
    /**
374
     * Generates a HTML representation of the exception
375
     *
376
     * @return string HTML code
377
     */
378
    public function toHtml()
379
    {
380
        $trace = $this->getTraceSafe();
381
        $causes = array();
382
        $this->getCauseMessage($causes);
383
        $html =  '<table style="border: 1px" cellspacing="0">' . "\n";
384
        foreach ($causes as $i => $cause) {
385
            $html .= '<tr><td colspan="3" style="background: #ff9999">'
386
               . str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
387
               . htmlspecialchars($cause['message'])
388
                . ' in <b>' . $cause['file'] . '</b> '
389
               . 'on line <b>' . $cause['line'] . '</b>'
390
               . "</td></tr>\n";
391
        }
392
        $html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n"
393
               . '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>'
394
               . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>'
395
               . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n";
396
397
        foreach ($trace as $k => $v) {
398
            $html .= '<tr><td style="text-align: center;">' . $k . '</td>'
399
                   . '<td>';
400
            if (!empty($v['class'])) {
401
                $html .= $v['class'] . $v['type'];
402
            }
403
            $html .= $v['function'];
404
            $args = array();
405
            if (!empty($v['args'])) {
406
                foreach ($v['args'] as $arg) {
407
                    if (is_null($arg)) {
408
                        $args[] = 'null';
409
                    } else if (is_array($arg)) {
410
                        $args[] = 'Array';
411
                    } else if (is_object($arg)) {
412
                        $args[] = 'Object('.get_class($arg).')';
413
                    } else if (is_bool($arg)) {
414
                        $args[] = $arg ? 'true' : 'false';
415
                    } else if (is_int($arg) || is_double($arg)) {
416
                        $args[] = $arg;
417
                    } else {
418
                        $arg = (string)$arg;
419
                        $str = htmlspecialchars(substr($arg, 0, 16));
420
                        if (strlen($arg) > 16) {
421
                            $str .= '&hellip;';
422
                        }
423
                        $args[] = "'" . $str . "'";
424
                    }
425
                }
426
            }
427
            $html .= '(' . implode(', ', $args) . ')'
428
                   . '</td>'
429
                   . '<td>' . (isset($v['file']) ? $v['file'] : 'unknown')
430
                   . ':' . (isset($v['line']) ? $v['line'] : 'unknown')
431
                   . '</td></tr>' . "\n";
432
        }
433
        $html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>'
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $k seems to be defined by a foreach iteration on line 397. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
434
               . '<td>{main}</td>'
435
               . '<td>&nbsp;</td></tr>' . "\n"
436
               . '</table>';
437
        return $html;
438
    }
439
440
    /**
441
     * Generates text representation of the exception and stack trace
442
     *
443
     * @return string
444
     */
445
    public function toText()
446
    {
447
        $causes = array();
448
        $this->getCauseMessage($causes);
449
        $causeMsg = '';
450
        foreach ($causes as $i => $cause) {
451
            $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
452
                   . $cause['message'] . ' in ' . $cause['file']
453
                   . ' on line ' . $cause['line'] . "\n";
454
        }
455
        return $causeMsg . $this->getTraceAsString();
456
    }
457
}
458