Issues (35)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

debug/handlers/Exception.php (13 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace nyx\diagnostics\debug\handlers;
2
3
// Internal dependencies
4
use nyx\diagnostics\debug\exceptions;
5
use nyx\diagnostics\debug\interfaces;
6
use nyx\diagnostics\debug;
7
use nyx\diagnostics\definitions;
8
9
/**
10
 * Exception Handler
11
 *
12
 * Responsible for handling Exceptions that have not get caught. Inspects them and provides its analyses to
13
 * registered Delegates {@see interfaces\Delegate}, which could be logging facilities, full page error displays
14
 * and the likes. All collection-like methods (add, remove, all etc.) manage the delegates and are named this way
15
 * for simplicity's sake.
16
 *
17
 * Delegates (both callables and actual Delegate instances) can be named and referred to by the set name for
18
 * white-and-black-listing and removal. Check {@see self::add()} and {@see self::set()} for information on the
19
 * naming behaviour.
20
 *
21
 * @package     Nyx\Diagnostics\Debug
22
 * @version     0.0.5
23
 * @author      Michal Chojnacki <[email protected]>
24
 * @copyright   2012-2016 Nyx Dev Team
25
 * @link        http://docs.muyo.io/nyx/diagnostics/debug.html
26
 * @todo        Buffer the output of delegates within self::handle() and optionally return it instead of sending?
27
 * @todo        Optional adding of Delegates upon Handler construction?
28
 */
29
class Exception extends debug\Handler implements interfaces\handlers\Exception
30
{
31
    /**
32
     * @var array   An array of registered interfaces\Delegate and callables. Each value in the array is an array
33
     *              containing two key => value pairs: The 'delegate' (callable | interfaces\Delegate) itself,
34
     *              and the 'priority' (int).
35
     */
36
    private $delegates = [];
37
38
    /**
39
     * @var array   The (cached) delegates applicable for the next call to self::handle().
40
     */
41
    private $applicable;
42
43
    /**
44
     * @var array   A list of Delegate *names* not allowed to handle the exception currently being handled.
45
     */
46
    private $blacklist = [];
47
48
    /**
49
     * @var array   A list of Delegate *names* allowed to handle the exception currently being handled.
50
     */
51
    private $whitelist = [];
52
53
    /**
54
     * @var bool    Whether the blacklist overrides the whitelist. Ie. when a given Delegate name is present in
55
     *              both arrays it will be treated as blacklisted.
56
     */
57
    private $prioritizeBlacklist = true;
58
59
    /**
60
     * @var int     The currently highest priority assigned to any Delegate/callable.
61
     */
62
    private $highestPriority = 0;
63
64
    /**
65
     * Registers the given or this Exception Handler with PHP.
66
     *
67
     * @param   interfaces\handlers\Exception   $handler    An optional, already instantiated Exception Handler
68
     *                                                      instance. If none is given, a new one will be
69
     *                                                      instantiated.
70
     * @return  Exception                                   An instance of the Exception Handler which got registered.
71
     *                                                      Either the same as the given one or if none was given,
72
     *                                                      a new instance.
73
     */
74
    public static function register(interfaces\handlers\Exception $handler = null) : Exception
75
    {
76
        set_exception_handler([$handler ?: $handler = new static, 'handle']);
77
78
        return $handler;
79
    }
80
81
    /**
82
     * {@inheritDoc}
83
     *
84
     * Walks through all stacked Delegates in the order of their priority and passes the inspected Exception to
85
     * them until one of them returns a STOP or QUIT signal as defined in definitions\Signals or no more Delegates
86
     * are left.
87
     *
88
     * @param   \Throwable  $exception
89
     */
90
    public function handle(\Throwable $exception)
91
    {
92
        // Being Emitter Aware we are bound to comply to the Events Definition.
93
        // self::emitDebugEvent() will return null when no Emitter is present. Otherwise we'll get the Exception
94
        // after it's been processed by Event Listeners so we need to overwrite it here.
95
        if (null !== $response = $this->emitDebugEvent(definitions\Events::DEBUG_EXCEPTION_BEFORE, $exception)) {
96
            $exception = $response;
97
        }
98
99
        // First of all run all Conditions. The method will return true if we are to prevent further execution.
100
        if ($this->runConditions($exception)) {
101
            return;
102
        }
103
104
        // Whether we will quit after the loop. Set to true when one of the Delegates returns a QUIT signal.
105
        $quit = false;
106
107
        // Get the applicable Delegates.
108
        $delegates = array_intersect_key($this->delegates, array_flip($this->getApplicable()));
109
110
        // If we've got anything to call later on, proceed.
111
        if (!empty($delegates)) {
112
            // Sort the Delegates by their priority.
113
            $this->sort($delegates);
114
115
            // Inspect the Exception we got, which will give us an Inspector instance we can pass along.
116
            $inspector = $this->inspect($exception);
117
118
            // Walk through all applicable Delegates and let them handle the Exception until done or one of them
119
            // returns a STOP/QUIT signal.
120
            foreach ($delegates as $v) {
121
                // If we're dealing with a Delegate instance, call its handle() method. If it's a casual callable,
122
                // call it and pass the Inspector as the only argument.
123
                $response = $v['delegate'] instanceof interfaces\Delegate
124
                    ? $v['delegate']->handle($inspector)
125
                    : call_user_func($v['delegate'], $inspector);
126
127
                // Make it easier for inheriting children to act upon the response if it's not a signal.
128
                $response = $this->handleDelegateResponse($response, $inspector);
129
130
                // Let's check if we've got a signal as response. If it's QUIT, we'll set a flag and handle it
131
                // after the loop.
132
                if (($response & definitions\Signals::QUIT) === definitions\Signals::QUIT) {
133
                    $quit = true;
134
                }
135
136
                // QUIT includes STOP so this will catch both situations.
137
                if (($response & definitions\Signals::STOP) === definitions\Signals::STOP) {
138
                    break;
139
                }
140
            }
141
        }
142
143
        // Now that we are done looping, time to attempt to emit the appropriate Event.
144
        $this->emitDebugEvent(definitions\Events::DEBUG_EXCEPTION_AFTER, $exception);
145
146
        // If we were told to quit and the Handler allows this, do eet.
147
        if ($quit and $this->doesAllowQuit()) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
148
            exit;
149
        }
150
151
        // Clear all lists. See the notes for {@see self::whitelist()} and {@see self::blacklist()} why Delegates
152
        // should be listed, for consistency, by Conditions *only*.
153
        $this->applicable = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $applicable.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
154
        $this->whitelist  = $this->blacklist = [];
155
    }
156
157
    /**
158
     * Similar to {@see self::add()} but uses a different argument order - the name is required (as it is used
159
     * as a key) and the method will overwrite the given key (name) if it is already set. The usage of add()
160
     * instead is recommended.
161
     *
162
     * @param   string                          $name       The name of the Delegate.
163
     * @param   interfaces\Delegate|callable    $delegate   The Delegate to be inserted into the stack.
164
     * @param   int                             $priority   The priority at which the Delegate should be invoked
165
     *                                                      when an exception gets handled.
166
     *                                                      Also {@see self::getHighestPriority()}.
167
     * @return  $this
168
     * @throws  \InvalidArgumentException                   When the argument passed is neither a callable nor
169
     *                                                      a Delegate.
170
     * @throws  \InvalidArgumentException                   When the name contains invalid characters. Only letters,
171
     *                                                      digits and backslashes are allowed.
172
     */
173
    public function set(string $name, $delegate, int $priority = 0) : self
174
    {
175
        // Make sure we've got a type we can work with.
176
        if (!$delegate instanceof interfaces\Delegate and !is_callable($delegate)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
177
            throw new \InvalidArgumentException("Exception handling delegates must be callables or instances of nyx\\diagnostics\\interfaces\\Delegate.");
178
        }
179
180
        // Perform a little check to make sure we'll be able to black/whitelist with a regexp afterwards.
181
        if (!preg_match('/^[\\\d\w]+$/', $name)) {
182
            throw new \InvalidArgumentException("Delegate names may only contain letters, digits and backslashes, [$name] given.");
183
        }
184
185
        $this->delegates[$name] = ['delegate' => $delegate, 'priority' => $priority];
186
        $this->applicable = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $applicable.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
187
188
        // Keep track of the highest priority currently assigned.
189
        if ($priority > $this->highestPriority) {
190
            $this->highestPriority = $priority;
191
        }
192
193
        return $this;
194
    }
195
196
    /**
197
     * Adds the given Delegate with the given optional priority to the stack.
198
     *
199
     * @param   interfaces\Delegate|callable    $delegate   The Delegate to be inserted into the stack.
200
     * @param   string                          $name       The name of the Delegate. Has to be unique. If none is
201
     *                                                      given, the full (ie. with namespace) classname will be
202
     *                                                      be for Delegates and "c\{mt_rand()}" for callables.
203
     *                                                      *Also* has to be unique. In other words - If you add an
204
     *                                                      instance of the same class (or even the same instance)
205
     *                                                      multiple times, assign different names.
206
     * @param   int                             $priority   The priority at which the Delegate should be invoked
207
     *                                                      when an exception gets handled.
208
     *                                                      Also {@see self::getHighestPriority()}.
209
     * @return  $this
210
     * @throws  \OverflowException                          When the given name (or when not given, the class name)
211
     *                                                      is already set.
212
     */
213
    public function add($delegate, string $name = null, int $priority = 0) : self
214
    {
215
        // Which name should we use?
216
        // Micro-optimization note: mt_rand() turned out to be several times faster than uniqid and somewhat
217
        // faster than counting the current number of delegates.
218
        $name = $name ?: ($delegate instanceof interfaces\Delegate ? get_class($delegate) : 'c\\'.mt_rand());
219
220
        if (isset($this->delegates[$name])) {
221
            throw new \OverflowException("A Delegate with the given name [$name] is already set.");
222
        }
223
224
        return $this->set($name, $delegate, $priority);
225
    }
226
227
    /**
228
     * Removes a specific Delegate or callable from the stack.
229
     *
230
     * Important note: This will remove all *instances* of the given Delegate if it passes the strict match unless
231
     * $all is set to false *or* a name is used instead of an instance as the first argument to this method, since
232
     * Delegate names are unique within this Handler.
233
     *
234
     * @param   string|interfaces\Delegate|callable     $delegate   Either the name of the Delegate/callable or
235
     *                                                              an actual instance to search for.
236
     * @param   bool                                    $all        Whether to search for all matches (true) or stop
237
     *                                                              after the first match (false).
238
     * @return  $this
239
     */
240
    public function remove($delegate, bool $all = true) : self
241
    {
242
        // When a string was passed, we will need to distinguish what to compare within our search.
243
        $name = is_string($delegate) ? $delegate : null;
244
245
        foreach ($this->delegates as $k => $v) {
246
            if (($name and $k === $name) or (!$name and $v['delegate'] === $delegate)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
247
                unset($this->delegates[$k]);
248
                $this->applicable = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $applicable.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
249
250
                if ($name or false === $all) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
251
                    break;
252
                }
253
            }
254
        }
255
256
        return $this;
257
    }
258
259
    /**
260
     * Adds the given Delegate name to the blacklist.
261
     *
262
     * Note: Blacklisting *should* be performed within Conditions as the blacklist will be cleared after each
263
     * handle() call and therefore the blacklist only applies to the very next call. Normally this shouldn't be
264
     * an issue as the default assumption is that the Exception Handler only gets invoked for uncaught exceptions,
265
     * but... well, you catch the drift.
266
     *
267
     * @param   string|array    $name   A regex of the Delegates to blacklist or an array of REs. The regex must
268
     *                                  be provided *without* the delimiters as they will be added automatically
269
     *                                  (therefore - no special flags etc.).
270
     * @return  $this
271
     */
272
    public function blacklist($name) : self
273
    {
274
        return $this->pushToList($this->blacklist, $name);
275
    }
276
277
    /**
278
     * Adds the given Delegate name to the whitelist.
279
     *
280
     * Note: Whitelisting *should* be performed within Conditions as the whitelist will be cleared after each
281
     * handle() call and therefore the whitelist only applies to the very next call. Normally this shouldn't be
282
     * an issue as the default assumption is that the Exception Handler only gets invoked for uncaught exceptions,
283
     * but... well, you catch the drift.
284
     *
285
     * @param   string|array    $name   A regex of the Delegates to whitelist or an array of REs. The regex must
286
     *                                  be provided *without* the delimiters as they will be added automatically
287
     *                                  (therefore - no special flags etc.).
288
     * @return  $this
289
     */
290
    public function whitelist($name) : self
291
    {
292
        return $this->pushToList($this->whitelist, $name);
293
    }
294
295
    /**
296
     * Returns all registered Delegates, in the order they were added. See {@see self::$delegates} for more info
297
     * on the structure.
298
     *
299
     * @return  array
300
     */
301
    public function all() : array
302
    {
303
        return $this->delegates;
304
    }
305
306
    /**
307
     * Returns an array containing the names of all Delegates which are applicable for the next call to
308
     * self::handle(), ie. after computing which of them are black-or-whitelisted.
309
     *
310
     * @return  array
311
     */
312
    public function getApplicable() : array
313
    {
314
        // If we've already compiled the list and nothing changed, return it.
315
        if (null !== $this->applicable) {
316
            return $this->applicable;
317
        }
318
319
        $delegates = array_keys($this->delegates);
320
321
        // No need for any fancy magic if we've got no black/whitelist.
322
        if (empty($this->whitelist) and empty($this->blacklist)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
323
            return $this->applicable = $delegates;
324
        }
325
326
        // Compile the lists by performing regex matches over the existing Delegate names.
327
        if (!empty($this->whitelist)) {
328
            $whitelist = $this->compileList($this->whitelist, $delegates);
329
        }
330
331
        if (!empty($this->blacklist)) {
332
            $blacklist = $this->compileList($this->blacklist, $delegates);
333
        }
334
335
        // No need to proceed further if we didn't actually black/whitelist any currently added Delegates.
336
        if (empty($whitelist) and empty($blacklist)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
337
            return $this->applicable = $delegates;
338
        }
339
340
        // Further checks.
341
        if (empty($whitelist)) {
342
            return $this->applicable = array_diff($delegates, $blacklist);
0 ignored issues
show
The variable $blacklist does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
343
        }
344
345
        if (empty($blacklist)) {
346
            return $this->applicable = $whitelist;
347
        }
348
349
        // Well, seems neither of the lists is empty, so time for a little magic.
350
        return $this->applicable = $this->prioritizeBlacklist
351
            ? array_diff($whitelist, $blacklist)
352
            : array_intersect($whitelist, $blacklist);
353
    }
354
355
    /**
356
     * Removes all Delegates from the stack.
357
     *
358
     * @return  $this
359
     */
360
361
    public function flush()
362
    {
363
        $this->delegates = [];
364
        $this->applicable = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $applicable.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
365
366
        return $this;
367
    }
368
369
    /**
370
     * Sets whether the blacklist overrides the whitelist. Ie. when a given Delegate name is present in both arrays
371
     * it will be treated as blacklisted.
372
     *
373
     * @param   bool    $bool   True to have the blacklist override the whitelist, false otherwise.
374
     */
375
376
    public function setPrioritizeBlacklist($bool)
377
    {
378
        $this->prioritizeBlacklist = (bool) $bool;
379
    }
380
381
    /**
382
     * Checks whether the blacklist overrides the whitelist. Ie. when a given Delegate name is present in both
383
     * arrays it will be treated as blacklisted.
384
     *
385
     * @return  bool    True when the blacklist overrides the whitelist, false otherwise.
386
     */
387
388
    public function doesPrioritizeBlacklist()
389
    {
390
        return $this->prioritizeBlacklist;
391
    }
392
393
    /**
394
     * Returns the highest priority currently assigned to any Delegate/callable.
395
     *
396
     * Could be used to provide sane priorities when adding Delegates, if you want to ensure that at the given
397
     * runtime moment the given Delegate takes top-priority but assume that something even more important might
398
     * get registered and therefore don't want to use some ridiculously high int like max_int.
399
     *
400
     * @return  int
401
     */
402
403
    public function getHighestPriority()
404
    {
405
        return $this->highestPriority;
406
    }
407
408
    /**
409
     * Sorts the given Delegates by their priority, highest first.
410
     *
411
     * @param   array   &$delegates  The Delegates to sort.
412
     * @return  $this
413
     */
414
    protected function sort(array &$delegates) : self
415
    {
416
        uasort($delegates, function($a, $b) {
417
            return $b['priority'] - $a['priority'];
418
        });
419
420
        return $this;
421
    }
422
423
    /**
424
     * Pushes the given values to either the black-or-white-list.
425
     *
426
     * @param   array           &$list      Either $this->blacklist or $this->whitelist.
427
     * @param   string|array    $name       {@see self::blacklist()} or {@see self::whitelist()}
428
     * @return  $this
429
     */
430
    protected function pushToList(array &$list, $name) : self
431
    {
432
        // Handle arrays (recursively).
433
        if (is_array($name)) {
434
            foreach ($name as $single) {
435
                $this->pushToList($list, $single);
436
            }
437
            return $this;
438
        }
439
440
        $list[] = $name;
441
442
        // Reset the applicable Delegates as we will need to recompile the list.
443
        $this->applicable = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $applicable.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
444
445
        return $this;
446
    }
447
448
    /**
449
     * Compiles the given black-or-white-list based on the given Delegate names, ie. run regex matches against
450
     * the Delegate names.
451
     *
452
     * @param   array   &$list      Either $this->blacklist or $this->whitelist.
453
     * @param   array   $delegates  An array of Delegate names that should be considered.
454
     * @return  array               The compiled List.
455
     */
456
    protected function compileList(array &$list, array $delegates)
457
    {
458
        $return = [];
459
460
        foreach ($list as $name) {
461
            $return[] = preg_grep('/'.$name.'/', $delegates);
462
        }
463
464
        return call_user_func_array('array_merge', $return);
465
    }
466
467
    /**
468
     * Returns an Inspector instance for the given exception. Kept separately from self::handle() in case you
469
     * intend to use a custom Inspector.
470
     *
471
     * @param   \Exception          $exception  The exception which is to be inspected.
472
     * @return  debug\Inspector                 An exception Inspector instance.
473
     */
474
    protected function inspect(\Exception $exception) : debug\Inspector
475
    {
476
        return new debug\Inspector($exception, $this);
477
    }
478
479
    /**
480
     * Handles a Delegate's response before the handle() method resolves the return signal. Allows to override
481
     * the signal by the Handler or act upon responses that are not signals.
482
     *
483
     * You might use this method to, for example, intercept delegate responses, generate a HTTP Response from
484
     * them and stop further delegation by returning a STOP signal yourself.
485
     *
486
     * @param   mixed               $response   The response of the last Delegate.
487
     * @param   debug\Inspector     $inspector  The Inspector handling the Exception.
488
     * @return  mixed
489
     */
490
    protected function handleDelegateResponse($response, debug\Inspector $inspector)
491
    {
492
        return $response;
493
    }
494
}
495