Routing::getAffectedRoutes()   C
last analyzed

Complexity

Conditions 19
Paths 96

Size

Total Lines 77
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 46
nc 96
nop 2
dl 0
loc 77
rs 5.1697
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
// +---------------------------------------------------------------------------+
4
// | This file is part of the Agavi package.                                   |
5
// | Copyright (c) 2005-2011 the Agavi Project.                                |
6
// |                                                                           |
7
// | For the full copyright and license information, please view the LICENSE   |
8
// | file that was distributed with this source code. You can also view the    |
9
// | LICENSE file online at http://www.agavi.org/LICENSE.txt                   |
10
// |   vi: set noexpandtab:                                                    |
11
// |   Local Variables:                                                        |
12
// |   indent-tabs-mode: t                                                     |
13
// |   End:                                                                    |
14
// +---------------------------------------------------------------------------+
15
16
namespace Agavi\Routing;
17
18
use Agavi\Config\Config;
19
use Agavi\Config\ConfigCache;
20
use Agavi\Exception\AgaviException;
21
use Agavi\Response\Response;
22
use Agavi\Util\ArrayPathDefinition;
23
use Agavi\Util\ParameterHolder;
24
use Agavi\Core\Context;
25
use Agavi\Util\Toolkit;
26
27
/**
28
 * Routing allows you to centralize your entry point urls in your web
29
 * application.
30
 *
31
 * @package    agavi
32
 * @subpackage routing
33
 *
34
 * @author     Dominik del Bondio <[email protected]>
35
 * @author     David Zülke <[email protected]>
36
 * @copyright  Authors
37
 * @copyright  The Agavi Project
38
 *
39
 * @since      0.11.0
40
 *
41
 * @version    $Id$
42
 */
43
abstract class Routing extends ParameterHolder
44
{
45
    const ANCHOR_NONE = 0;
46
    const ANCHOR_START = 1;
47
    const ANCHOR_END = 2;
48
49
    /**
50
     * @var        array An array of route information
51
     */
52
    protected $routes = array();
53
54
    /**
55
     * @var        Context A Context instance.
56
     */
57
    protected $context = null;
58
59
    /**
60
     * @var        string Route input.
61
     */
62
    protected $input = null;
63
64
    /**
65
     * @var        RoutingArraySource[] An array of RoutingArraySource.
66
     */
67
    protected $sources = array();
68
69
    /**
70
     * @var        string Route prefix to use with gen()
71
     */
72
    protected $prefix = '';
73
74
    /**
75
     * @var        array An array of default options for gen()
76
     */
77
    protected $defaultGenOptions = array();
78
79
    /**
80
     * @var        array An array of default options presets for gen()
81
     */
82
    protected $genOptionsPresets = array();
83
84
    /**
85
     * Constructor.
86
     *
87
     * @author     David Zülke <[email protected]>
88
     * @since      0.11.0
89
     */
90
    public function __construct()
91
    {
92
        // for now, we still use this setting as default.
93
        // will be removed in 1.1
94
        $this->setParameter('enabled', Config::get('core.use_routing', true));
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
95
        
96
        $this->defaultGenOptions = array_merge($this->defaultGenOptions, array(
97
            'relative' => true,
98
            'refill_all_parameters' => false,
99
            'omit_defaults' => false,
100
        ));
101
    }
102
    
103
    /**
104
     * Initialize the routing instance.
105
     *
106
     * @param      Context $context    The Context.
107
     * @param      array   $parameters An array of initialization parameters.
108
     *
109
     * @author     Dominik del Bondio <[email protected]>
110
     * @author     David Zülke <[email protected]>
111
     * @since      0.11.0
112
     */
113
    public function initialize(Context $context, array $parameters = array())
114
    {
115
        $this->context = $context;
116
117
        $this->setParameters($parameters);
118
119
        $this->defaultGenOptions = array_merge(
120
            $this->defaultGenOptions,
121
            $this->getParameter('default_gen_options', array())
122
        );
123
124
        $this->genOptionsPresets = array_merge(
125
            $this->genOptionsPresets,
126
            $this->getParameter('gen_options_presets', array())
127
        );
128
        
129
        // and load the config.
130
        $this->loadConfig();
131
    }
132
133
    /**
134
     * Load the routing.xml configuration file.
135
     *
136
     * @author     David Zülke <[email protected]>
137
     * @since      0.11.0
138
     */
139
    protected function loadConfig()
140
    {
141
        $cfg = Config::get('core.config_dir') . '/routing.xml';
142
        // allow missing routing.xml when routing is not enabled
143
        if ($this->isEnabled() || is_readable($cfg)) {
144
            $this->importRoutes(unserialize(file_get_contents(ConfigCache::checkConfig($cfg, $this->context->getName()))));
145
        }
146
    }
147
148
    /**
149
     * Do any necessary startup work after initialization.
150
     *
151
     * This method is not called directly after initialize().
152
     *
153
     * @author     David Zülke <[email protected]>
154
     * @since      0.11.0
155
     */
156
    public function startup()
0 ignored issues
show
Coding Style introduced by
startup uses the super-global variable $_ENV which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
startup uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
157
    {
158
        $this->sources['_ENV'] = new RoutingArraySource($_ENV);
159
160
        $this->sources['_SERVER'] = new RoutingArraySource($_SERVER);
161
162
        if (Config::get('core.use_security')) {
163
            $this->sources['user'] = new RoutingUserSource($this->context->getUser());
164
        }
165
    }
166
167
    /**
168
     * Execute the shutdown procedure.
169
     *
170
     * @author     David Zülke <[email protected]>
171
     * @since      0.11.0
172
     */
173
    public function shutdown()
174
    {
175
    }
176
177
    /**
178
     * Check if this routing instance is enabled.
179
     *
180
     * @return     bool Whether or not routing is enabled.
181
     *
182
     * @author     David Zülke <[email protected]>
183
     * @since      1.0.0
184
     */
185
    public function isEnabled()
186
    {
187
        return $this->getParameter('enabled') === true;
188
    }
189
190
    /**
191
     * Retrieve the current application context.
192
     *
193
     * @return     Context A Context instance.
194
     *
195
     * @author     Dominik del Bondio <[email protected]>
196
     * @since      0.11.0
197
     */
198
    final public function getContext()
199
    {
200
        return $this->context;
201
    }
202
203
    /**
204
     * Retrieve the info about a named route for this routing instance.
205
     *
206
     * @return     mixed The route info or null if the route doesn't exist.
207
     *
208
     * @author     Dominik del Bondio <[email protected]>
209
     * @since      0.11.0
210
     */
211
    final public function getRoute($name)
212
    {
213
        if (!isset($this->routes[$name])) {
214
            return null;
215
        }
216
        return $this->routes[$name];
217
    }
218
219
    /**
220
     * Retrieve the input for this routing instance.
221
     *
222
     * @return     string The input.
223
     *
224
     * @author     Dominik del Bondio <[email protected]>
225
     * @since      0.11.0
226
     */
227
    final public function getInput()
228
    {
229
        return $this->input;
230
    }
231
232
    /**
233
     * Retrieve the prefix for this routing instance.
234
     *
235
     * @return     string The prefix.
236
     *
237
     * @author     Dominik del Bondio <[email protected]>
238
     * @since      0.11.0
239
     */
240
    final public function getPrefix()
241
    {
242
        return $this->prefix;
243
    }
244
245
    /**
246
     * Adds a route to this routing instance.
247
     *
248
     * @param      string $route   A string with embedded regexp.
249
     * @param      array  $options An array with options. The array can contain following
250
     *                   items:
251
     *                   <ul>
252
     *                    <li>name</li>
253
     *                    <li>stop</li>
254
     *                    <li>output_type</li>
255
     *                    <li>module</li>
256
     *                    <li>controller</li>
257
     *                    <li>parameters</li>
258
     *                    <li>ignores</li>
259
     *                    <li>defaults</li>
260
     *                    <li>childs</li>
261
     *                    <li>callbacks</li>
262
     *                    <li>imply</li>
263
     *                    <li>cut</li>
264
     *                    <li>source</li>
265
     *                   </ul>
266
     * @param      string $parent   The name of the parent route (if any).
267
     *
268
     * @return     string The name of the route.
269
     *
270
     * @author     Dominik del Bondio <[email protected]>
271
     * @since      0.11.0
272
     */
273
    public function addRoute($route, array $options = array(), $parent = null)
274
    {
275
        // catch the old options from the route which has to be overwritten
276
        if (isset($options['name']) && isset($this->routes[$options['name']])) {
277
            $defaultOpts = $this->routes[$options['name']]['opt'];
278
279
            // when the parent is set and differs from the parent of the route to be overwritten bail out
280
            if ($parent !== null && $defaultOpts['parent'] != $parent) {
281
                throw new AgaviException('You are trying to overwrite a route but are not staying in the same hierarchy');
282
            }
283
284
            if ($parent === null) {
285
                $parent = $defaultOpts['parent'];
286
            } else {
287
                $defaultOpts['parent'] = $parent;
288
            }
289
        } else {
290
            $defaultOpts = array('name' => Toolkit::uniqid(), 'stop' => true, 'output_type' => null, 'module' => null, 'controller' => null, 'parameters' => array(), 'ignores' => array(), 'defaults' => array(), 'childs' => array(), 'callbacks' => array(), 'imply' => false, 'cut' => null, 'source' => null, 'method' => null, 'constraint' => array(), 'locale' => null, 'pattern_parameters' => array(), 'optional_parameters' => array(), 'parent' => $parent, 'reverseStr' => '', 'nostops' => array(), 'anchor' => self::ANCHOR_NONE);
291
        }
292
        // retain backwards compatibility to 0.11
293
        if (isset($options['callback'])) {
294
            $options['callbacks'] = array(array('class' => $options['callback'], 'parameters' => array()));
295
            unset($options['callback']);
296
        }
297
298
        if (isset($options['defaults'])) {
299
            foreach ($options['defaults'] as $name => &$value) {
300
                $val = $pre = $post = null;
0 ignored issues
show
Unused Code introduced by
$val is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
301
                if (preg_match('#(.*)\{(.*)\}(.*)#', $value, $match)) {
302
                    $pre = $match[1];
303
                    $val = $match[2];
304
                    $post = $match[3];
305
                } else {
306
                    $val = $value;
307
                }
308
309
                $value = $this->createValue($val)->setPrefix($pre)->setPostfix($post);
310
            }
311
        }
312
313
        // set the default options + user opts
314
        $options = array_merge($defaultOpts, $options);
315
        list($regexp, $options['reverseStr'], $routeParams, $options['anchor']) = $this->parseRouteString($route);
316
317
        $params = array();
318
319
        // transfer the parameters and fill available automatic defaults
320
        foreach ($routeParams as $name => $param) {
321
            $params[] = $name;
322
323
            if ($param['is_optional']) {
324
                $options['optional_parameters'][$name] = true;
325
            }
326
327
            if (!isset($options['defaults'][$name]) && ($param['pre'] || $param['val'] || $param['post'])) {
328
                unset($param['is_optional']);
329
                $options['defaults'][$name] = $this->createValue($param['val'])->setPrefix($param['pre'])->setPostfix($param['post']);
330
            }
331
        }
332
333
        $options['pattern_parameters'] = $params;
334
335
        // remove all ignore from the parameters in the route
336
        foreach ($options['ignores'] as $ignore) {
337
            if (($key = array_search($ignore, $params)) !== false) {
338
                unset($params[$key]);
339
            }
340
        }
341
342
        $routeName = $options['name'];
343
344
        // parse all the setting values for dynamic variables
345
        // check if 2 nodes with the same name in the same execution tree exist
346
        foreach ($this->routes as $name => $route) {
347
            // if a route with this route as parent exist check if its really a child of our route
348
            if ($route['opt']['parent'] == $routeName && !in_array($name, $options['childs'])) {
349
                throw new AgaviException('The route ' . $routeName . ' specifies a child route with the same name');
350
            }
351
        }
352
353
        // direct childs/parents with the same name aren't caught by the above check
354
        if ($routeName == $parent) {
355
            throw new AgaviException('The route ' . $routeName . ' specifies a child route with the same name');
356
        }
357
358
        // if we are a child route, we need add this route as a child to the parent
359
        if ($parent !== null) {
360
            foreach ($this->routes[$parent]['opt']['childs'] as $name) {
361
                if ($name == $routeName) {
362
                    // we're overwriting a route, so unlike when first adding the route, there are more routes after this that might also be non-stopping, but we obviously don't want those, so we need to bail out at this point
363
                    break;
364
                }
365
                $route = $this->routes[$name];
366
                if (!$route['opt']['stop']) {
367
                    $options['nostops'][] = $name;
368
                }
369
            }
370
            $this->routes[$parent]['opt']['childs'][] = $routeName;
371
        } else {
372
            foreach ($this->routes as $name => $route) {
373
                if ($name == $routeName) {
374
                    // we're overwriting a route, so unlike when first adding the route, there are more routes after this that might also be non-stopping, but we obviously don't want those, so we need to bail out at this point
375
                    break;
376
                }
377
                if (!$route['opt']['stop'] && !$route['opt']['parent']) {
378
                    $options['nostops'][] = $name;
379
                }
380
            }
381
        }
382
        
383
        // make sure we have no duplicates in the nostops (can happen when a route is overwritten)
384
        $options['nostops'] = array_unique($options['nostops']);
385
386
        $route = array('rxp' => $regexp, 'par' => $params, 'opt' => $options, 'matches' => array());
387
        $this->routes[$routeName] = $route;
388
389
        return $routeName;
390
    }
391
392
    /**
393
     * Retrieve the internal representation of the route info.
394
     *
395
     * @return     array The info about all routes.
396
     *
397
     * @author     Dominik del Bondio <[email protected]>
398
     * @since      0.11.0
399
     */
400
    public function exportRoutes()
401
    {
402
        return $this->routes;
403
    }
404
405
    /**
406
     * Sets the internal representation of the route info.
407
     *
408
     * @param      array $route The info about all routes.
0 ignored issues
show
Documentation introduced by
There is no parameter named $route. Did you maybe mean $routes?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
409
     *
410
     * @author     Dominik del Bondio <[email protected]>
411
     * @since      0.11.0
412
     */
413
    public function importRoutes(array $routes)
414
    {
415
        $this->routes = $routes;
416
    }
417
418
    /**
419
     * Retrieves the routes which need to be taken into account when generating
420
     * the reverse string of a routing to be generated.
421
     *
422
     * @param      string $route       The route name(s, delimited by +) to calculate.
423
     * @param      bool   $isNullRoute Set to true if the requested route was 'null' or
424
     *                                 'null' + 'xxx'
425
     *
426
     * @return     array A list of names of affected routes.
427
     *
428
     * @author     Dominik del Bondio <[email protected]>
429
     * @since      0.11.0
430
     */
431
    public function getAffectedRoutes($route, &$isNullRoute = false)
432
    {
433
        $includedRoutes = array();
434
        $excludedRoutes = array();
435
        
436
        if ($route === null) {
437
            $includedRoutes = array_reverse($this->getContext()->getRequest()->getAttribute('matched_routes', 'org.agavi.routing', array()));
438
            $isNullRoute = true;
439
        } elseif (strlen($route) > 0) {
440
            if ($route[0] == '-' || $route[0] == '+') {
441
                $includedRoutes = array_reverse($this->getContext()->getRequest()->getAttribute('matched_routes', 'org.agavi.routing', array()));
442
                $isNullRoute = true;
443
            }
444
            
445
            $routeParts = preg_split('#(-|\+)#', $route, -1, PREG_SPLIT_DELIM_CAPTURE);
446
            $prevDelimiter = '+';
447
            foreach ($routeParts as $part) {
448
                if ($part == '+' || $part == '-') {
449
                    $prevDelimiter = $part;
450
                }
451
                
452
                if ($prevDelimiter == '+') {
453
                    $includedRoutes[] = $part;
454
                } else { // $prevDelimiter == '-'
455
                    $excludedRoutes[] = $part;
456
                }
457
            }
458
        }
459
        
460
        $excludedRoutes = array_flip($excludedRoutes);
461
462
        if ($includedRoutes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $includedRoutes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
463
            $route = $includedRoutes[0];
464
            // TODO: useful comment here
465
            unset($includedRoutes[0]);
466
        }
467
        
468
        $myRoutes = array();
469
        foreach ($includedRoutes as $r) {
470
            $myRoutes[$r] = true;
471
        }
472
473
        $affectedRoutes = array();
474
475
        if (isset($this->routes[$route])) {
476
            $parent = $route;
477
            do {
478
                if (!isset($excludedRoutes[$parent])) {
479
                    $affectedRoutes[] = $parent;
480
                }
481
                $r = $this->routes[$parent];
482
483
                foreach (array_reverse($r['opt']['nostops']) as $noStop) {
484
                    $myR = $this->routes[$noStop];
485
                    if (isset($myRoutes[$noStop])) {
486
                        unset($myRoutes[$noStop]);
487
                    } elseif (!$myR['opt']['imply']) {
488
                        continue;
489
                    }
490
491
                    if (!isset($excludedRoutes[$noStop])) {
492
                        $affectedRoutes[] = $noStop;
493
                    }
494
                }
495
496
                $parent = $r['opt']['parent'];
497
            } while ($parent);
498
        } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
499
            // TODO: error handling - route with the given name does not exist
500
        }
501
502
        if (count($myRoutes)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
503
            // TODO: error handling - we couldn't find some of the nonstopping rules
504
        }
505
506
        return $affectedRoutes;
507
    }
508
    
509
    /**
510
     * Get a list of all parameter matches which where matched in execute()
511
     * in the given routes.
512
     *
513
     * @param      array $routeNames An array of route names.
514
     *
515
     * @return     array The matched parameters as name => value.
516
     *
517
     * @author     Dominik del Bondio <[email protected]>
518
     * @since      1.0.0
519
     */
520
    public function getMatchedParameters(array $routeNames)
521
    {
522
        $params = array();
523
        foreach ($routeNames as $name) {
524
            if (isset($this->routes[$name])) {
525
                $route = $this->routes[$name];
526
                $params = array_merge($params, $route['matches']);
527
            }
528
        }
529
        return $params;
530
    }
531
    
532
    /**
533
     * Get a complete list of gen() options based on the given, probably
534
     * incomplete, options array, and/or options preset name(s).
535
     *
536
     * @param      mixed $input An array of gen options and names of options presets
537
     *                   or just the name of a single option preset.
538
     *
539
     * @return     array A complete array of options.
540
     *
541
     * @throws     \Exception If the given preset name doesn't exist.
542
     *
543
     * @author     David Zülke <[email protected]>
544
     * @since      0.11.0
545
     */
546
    protected function resolveGenOptions($input = array())
547
    {
548
        if (is_string($input)) {
549
            // A single option preset was given
550
            if (isset($this->genOptionsPresets[$input])) {
551
                return array_merge($this->defaultGenOptions, $this->genOptionsPresets[$input]);
552
            }
553
        } elseif (is_array($input)) {
554
            $genOptions = $this->defaultGenOptions;
555
            foreach ($input as $key => $value) {
556
                if (is_numeric($key)) {
557
                    // Numeric key – it's an option preset
558
                    if (isset($this->genOptionsPresets[$value])) {
559
                        $genOptions = array_merge($genOptions, $this->genOptionsPresets[$value]);
560
                    } else {
561
                        throw new AgaviException('Undefined Routing gen() options preset "' . $value . '"');
562
                    }
563
                } else {
564
                    // String key – it's an option
565
                    $genOptions[$key] = $value;
566
                }
567
            }
568
            return $genOptions;
569
        }
570
        throw new AgaviException('Unexpected type "' . gettype($input) . '" used as Routing gen() option preset identifier');
571
    }
572
    
573
    /**
574
     * Adds the matched parameters from the 'null' routes to the given parameters
575
     * (without overwriting existing ones)
576
     *
577
     * @param      array $routeNames The route names
578
     * @param      array $params     The parameters
579
     *
580
     * @return     array The new parameters
581
     *
582
     * @author     Dominik del Bondio <[email protected]>
583
     * @since      1.0.0
584
     */
585
    public function fillGenNullParameters(array $routeNames, array $params)
586
    {
587
        return array_merge($this->getMatchedParameters($routeNames), $params);
588
    }
589
    
590
    /**
591
     * Builds the routing information (result string, all kinds of parameters)
592
     * for the given routes.
593
     *
594
     * @param      array $options    The options
595
     * @param      array $routeNames The names of the routes to generate
596
     * @param      array $params     The parameters supplied by the user
597
     *
598
     * @return     array
599
     *
600
     * @author     Dominik del Bondio <[email protected]>
601
     * @since      1.0.0
602
     */
603
    protected function assembleRoutes(array $options, array $routeNames, array $params)
604
    {
605
        $uri = '';
606
        $defaultParams = array();
607
        $availableParams = array();
608
        $matchedParams = array(); // the merged incoming matched params of implied routes
609
        $optionalParams = array();
610
        $firstRoute = true;
611
        
612
        foreach ($routeNames as $routeName) {
613
            $r = $this->routes[$routeName];
614
615
            $myDefaults = $r['opt']['defaults'];
616
617
            if (count($r['opt']['callbacks']) > 0) {
618 View Code Duplication
                if (!isset($r['callback_instances'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
619
                    foreach ($r['opt']['callbacks'] as $key => $callback) {
620
                        /** @var RoutingCallback $instance */
621
                        $instance = new $callback['class']();
622
                        $instance->setParameters($callback['parameters']);
623
                        $instance->initialize($this->context, $r);
624
                        $r['callback_instances'][$key] = $instance;
625
                    }
626
                }
627
                foreach ($r['callback_instances'] as $callbackInstance) {
628
                    $paramsCopy = $params;
629
                    $isLegacyCallback = false;
630
                    if ($callbackInstance instanceof LegacyRoutingCallbackInterface) {
631
                        $isLegacyCallback = true;
632
                        // convert all routing values to strings so legacy callbacks don't break
633
                        $defaultsCopy = $myDefaults;
634
                        foreach ($paramsCopy as &$param) {
635
                            if ($param instanceof RoutingValueInterface) {
636
                                $param = $param->getValue();
637
                            }
638
                        }
639
                        foreach ($defaultsCopy as &$default) {
640
                            if ($default instanceof RoutingValueInterface) {
641
                                $default = array(
642
                                    'pre' => $default->getPrefix(),
643
                                    'val' => $default->getValue(),
644
                                    'post' => $default->getPostfix(),
645
                                );
646
                            }
647
                        }
648
                        $changedParamsCopy = $paramsCopy;
649
                        if (!$callbackInstance->onGenerate($defaultsCopy, $paramsCopy, $options)) {
0 ignored issues
show
Bug introduced by
The method onGenerate() does not seem to exist on object<Agavi\Routing\Leg...utingCallbackInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
650
                            continue 2;
651
                        }
652
                        // find all params changed in the callback, but ignore unset() parameters since they will be filled in at a later stage (and doing something the them would prevent default values being inserted after unset()tting of a parameter)
653
                        $diff = array();
654
                        foreach ($paramsCopy as $key => $value) {
655
                            if (!array_key_exists($key, $changedParamsCopy) || $changedParamsCopy[$key] !== $value) {
656
                                $diff[$key] = $value;
657
                            }
658
                        }
659
                        // do *not* use this instead, it will segfault in PHP < 5.2.6:
660
                        // $diff = array_udiff_assoc($paramsCopy, $changedParamsCopy, array($this, 'onGenerateParamDiffCallback'));
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
661
                        // likely caused by http://bugs.php.net/bug.php?id=42838 / http://cvs.php.net/viewvc.cgi/php-src/ext/standard/array.c?r1=1.308.2.21.2.51&r2=1.308.2.21.2.52
662
                    } else {
663
                        if (!$callbackInstance->onGenerate($myDefaults, $params, $options)) {
664
                            continue 2;
665
                        }
666
                        // find all params changed in the callback, but ignore unset() parameters since they will be filled in at a later stage (and doing something the them would prevent default values being inserted after unset()tting of a parameter)
667
                        $diff = array();
668 View Code Duplication
                        foreach ($params as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
669
                            if (!array_key_exists($key, $paramsCopy) || $paramsCopy[$key] !== $value) {
670
                                $diff[$key] = $value;
671
                            }
672
                        }
673
                        // do *not* use this instead, it will segfault in PHP < 5.2.6:
674
                        // $diff = array_udiff_assoc($params, $paramsCopy, array($this, 'onGenerateParamDiffCallback'));
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
675
                        // likely caused by http://bugs.php.net/bug.php?id=42838 / http://cvs.php.net/viewvc.cgi/php-src/ext/standard/array.c?r1=1.308.2.21.2.51&r2=1.308.2.21.2.52
676
                    }
677
                    
678
                    if (count($diff)) {
679
                        $diffKeys = array_keys($diff);
680
                        foreach ($diffKeys as $key) {
681
                            // NEVER assign this value as a reference, as PHP will go completely bonkers if we use a reference here (it marks the entry in the array as a reference, so modifying the value in $params in a callback means it gets modified in $paramsCopy as well)
682
                            // if the callback was a legacy callback, the array to read the values from is different (since everything was cast to strings before running the callback)
683
                            $value = $isLegacyCallback ? $paramsCopy[$key] : $params[$key];
684
                            if ($value !== null && !($value instanceof RoutingValueInterface)) {
685
                                $routingValue = $this->createValue($value, false);
686
                                if (isset($myDefaults[$key])) {
687
                                    if ($myDefaults[$key] instanceof RoutingValueInterface) {
688
                                        // clone the default value so pre and postfix are preserved
689
                                        /** @var RoutingValue $routingValue */
690
                                        $routingValue = clone $myDefaults[$key];
691
                                        // BC: When setting a value in a callback it was supposed to be already encoded
692
                                        $routingValue->setValue($value)->setValueNeedsEncoding(false);
693
                                    } else {
694
                                        // $myDefaults[$key] can only be an array at this stage
695
                                        $routingValue->setPrefix($myDefaults[$key]['pre'])->setPrefixNeedsEncoding(false);
696
                                        $routingValue->setPostfix($myDefaults[$key]['post'])->setPostfixNeedsEncoding(false);
697
                                    }
698
                                }
699
                                $value = $routingValue;
700
                            }
701
                            // for writing no legacy check mustn't be done, since that would mean the changed value would get lost
702
                            $params[$key] = $value;
703
                        }
704
                    }
705
                }
706
            }
707
708
            // if the route has a source we shouldn't put its stuff in the generated string
709
            if ($r['opt']['source']) {
710
                continue;
711
            }
712
713
            $matchedParams = array_merge($matchedParams, $r['matches']);
714
            $optionalParams = array_merge($optionalParams, $r['opt']['optional_parameters']);
715
716
            $availableParams = array_merge($availableParams, array_reverse($r['opt']['pattern_parameters']));
717
718
            if ($firstRoute || $r['opt']['cut'] || (count($r['opt']['childs']) && $r['opt']['cut'] === null)) {
719
                if ($r['opt']['anchor'] & self::ANCHOR_START || $r['opt']['anchor'] == self::ANCHOR_NONE) {
720
                    $uri = $r['opt']['reverseStr'] . $uri;
721
                } else {
722
                    $uri = $uri . $r['opt']['reverseStr'];
723
                }
724
            }
725
726
            $defaultParams = array_merge($defaultParams, $myDefaults);
727
            $firstRoute = false;
728
        }
729
        
730
        $availableParams = array_reverse($availableParams);
731
        
732
        return array(
733
            'uri' => $uri,
734
            'options' => $options,
735
            'user_parameters' => $params,
736
            'available_parameters' => $availableParams,
737
            'matched_parameters' => $matchedParams,
738
            'optional_parameters' => $optionalParams,
739
            'default_parameters' => $defaultParams,
740
        );
741
    }
742
    
743
    /**
744
     * Adds all matched parameters to the supplied parameters. Will not overwrite
745
     * already existing parameters.
746
     *
747
     * @param      array $options       The options
748
     * @param      array $params        The parameters supplied by the user
749
     * @param      array $matchedParams The parameters which matched in execute()
750
     *
751
     * @return     array The $params with the added matched parameters
752
     *
753
     * @author     Dominik del Bondio <[email protected]>
754
     * @since      1.0.0
755
     */
756
    protected function refillAllMatchedParameters(array $options, array $params, array $matchedParams)
757
    {
758
        if (!empty($options['refill_all_parameters'])) {
759 View Code Duplication
            foreach ($matchedParams as $name => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
760
                if (!(isset($params[$name]) || array_key_exists($name, $params))) {
761
                    $params[$name] = $this->createValue($value, true);
762
                }
763
            }
764
        }
765
        
766
        return $params;
767
    }
768
    
769
    /**
770
     * Adds all parameters which were matched in the incoming routes to the
771
     * generated route up the first user supplied parameter (from left to right)
772
     * Also adds the default value for all non optional parameters the user
773
     * didn't supply.
774
     *
775
     * @param      array $options The options
776
     * @param      array $originalUserParams The parameters originally passed to gen()
777
     * @param      array $params The parameters
778
     * @param      array $availableParams A list of parameter names available for the route
779
     * @param      array $matchedParams The matched parameters from execute() for the route
780
     * @param      array $optionalParams the optional parameters for the route
781
     * @param      array $defaultParams the default parameters for the route
782
     *
783
     * @return     array The 'final' parameters
784
     *
785
     * @author     Dominik del Bondio <[email protected]>
786
     * @since      1.0.0
787
     */
788
    protected function refillMatchedAndDefaultParameters(array $options, array $originalUserParams, array $params, array $availableParams, array $matchedParams, array $optionalParams, array $defaultParams)
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
789
    {
790
        $refillValue = true;
791
        $finalParams = array();
792
        foreach ($availableParams as $name) {
793
            // loop all params and fill all with the matched parameters
794
            // until a user (not callback) supplied parameter is encountered.
795
            // After that only check defaults. Parameters supplied from the user
796
            // or via callback always have precedence
797
798
            // keep track if a user supplied parameter has already been encountered
799
            if ($refillValue && (isset($originalUserParams[$name]) || array_key_exists($name, $originalUserParams))) {
800
                $refillValue = false;
801
            }
802
803
            // these 'aliases' are just for readability of the lower block
804
            $isOptional = isset($optionalParams[$name]);
805
            $hasMatched = isset($matchedParams[$name]);
806
            $hasDefault = isset($defaultParams[$name]);
807
            $hasUserCallbackParam = (isset($params[$name]) || array_key_exists($name, $params));
808
809
            if ($hasUserCallbackParam) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
810
                // anything a user or callback supplied has precedence
811
                // and since the user params are handled afterwards, skip them here
812
            } elseif ($refillValue && $hasMatched) {
813
                // Use the matched input
814
                $finalParams[$name] = $this->createValue($matchedParams[$name], true);
815
            } elseif ($hasDefault) {
816
                // now we just need to check if there are defaults for this available param and fill them in if applicable
817
                $default = $defaultParams[$name];
818
                if (!$isOptional || strlen($default->getValue()) > 0) {
819
                    $finalParams[$name] = clone $default;
820
                } elseif ($isOptional) {
821
                    // there is no default or incoming match for this optional param, so remove it
822
                    $finalParams[$name] = null;
823
                }
824
            }
825
        }
826
827
        return $finalParams;
828
    }
829
    
830
    /**
831
     * Adds the user supplied parameters to the 'final' parameters for the route.
832
     *
833
     * @param      array $options The options
834
     * @param      array $params The user parameters
835
     * @param      array $finalParams The 'final' parameters
836
     * @param      array $availableParams A list of parameter names available for the route
837
     * @param      array $optionalParams the optional parameters for the route
838
     * @param      array $defaultParams the default parameters for the route
839
     *
840
     * @return     array The 'final' parameters
841
     *
842
     * @author     Dominik del Bondio <[email protected]>
843
     * @since      1.0.0
844
     */
845
    protected function fillUserParameters(array $options, array $params, array $finalParams, array $availableParams, array $optionalParams, array $defaultParams)
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
846
    {
847
        $availableParamsAsKeys = array_flip($availableParams);
848
849
        foreach ($params as $name => $param) {
850
            if (!(isset($finalParams[$name]) || array_key_exists($name, $finalParams))) {
851
                if ($param === null && isset($optionalParams[$name])) {
852
                    // null was set for an optional parameter
853
                    $finalParams[$name] = $param;
854
                } else {
855
                    if (isset($defaultParams[$name])) {
856
                        if ($param === null || ($param instanceof RoutingValue && $param->getValue() === null)) {
857
                            // the user set the parameter to null, to signal that the default value should be used
858
                            $param = clone $defaultParams[$name];
859
                        }
860
                        $finalParams[$name] = $param;
861
                    } elseif (isset($availableParamsAsKeys[$name])) {
862
                        // when the parameter was available in one of the routes
863
                        $finalParams[$name] = $param;
864
                    }
865
                }
866
            }
867
        }
868
869
        return $finalParams;
870
    }
871
872
    /**
873
     * Adds the user supplied parameters to the 'final' parameters for the route.
874
     *
875
     * @param      array $options The options
876
     * @param      array $finalParams The 'final' parameters
877
     * @param      array $availableParams A list of parameter names available for the route
878
     * @param      array $optionalParams the optional parameters for the route
879
     * @param      array $defaultParams the default parameters for the route
880
     *
881
     * @return     array The 'final' parameters
882
     *
883
     * @author     Dominik del Bondio <[email protected]>
884
     * @since      1.0.0
885
     */
886
    protected function removeMatchingDefaults(array $options, array $finalParams, array $availableParams, array $optionalParams, array $defaultParams)
887
    {
888
        // if omit_defaults is set, we should not put optional values into the result string in case they are equal to their default value - even if they were given as a param
889
        if (!empty($options['omit_defaults'])) {
890
            // remove the optional parameters from the pattern beginning from right to the left, in case they are equal to their default
891
            foreach (array_reverse($availableParams) as $name) {
892
                if (isset($optionalParams[$name])) {
893
                    // the isset() could be replaced by
894
                    // "!array_key_exists($name, $finalParams) || $finalParams[$name] === null"
895
                    // to clarify that null is explicitly allowed here
896
                    if (!isset($finalParams[$name]) ||
897
                            (
898
                                isset($defaultParams[$name]) &&
899
                                $finalParams[$name]->getValue() == $defaultParams[$name]->getValue() &&
900
                                (!$finalParams[$name]->hasPrefix() || $finalParams[$name]->getPrefix() == $defaultParams[$name]->getPrefix()) &&
901
                                (!$finalParams[$name]->hasPostfix() || $finalParams[$name]->getPostfix() == $defaultParams[$name]->getPostfix())
902
                            )
903
                    ) {
904
                        $finalParams[$name] = null;
905
                    } else {
906
                        break;
907
                    }
908
                } else {
909
                    break;
910
                }
911
            }
912
        }
913
        
914
        return $finalParams;
915
    }
916
    
917
    /**
918
     * Updates the pre and postfixes in the final params from the default
919
     * pre and postfix if available and if it hasn't been set yet by the user.
920
     *
921
     * @param      array $finalParams The 'final' parameters
922
     * @param      array $defaultParams the default parameters for the route
923
     *
924
     * @return     array The 'final' parameters
925
     *
926
     * @author     Dominik del Bondio <[email protected]>
927
     * @since      1.0.0
928
     */
929
    protected function updatePrefixAndPostfix(array $finalParams, array $defaultParams)
930
    {
931
        foreach ($finalParams as $name => $param) {
932
            if ($param === null) {
933
                continue;
934
            }
935
            
936
            if (isset($defaultParams[$name])) {
937
                // update the pre- and postfix from the default if they are not set in the routing value
938
                $default = $defaultParams[$name];
939
                if (!$param->hasPrefix() && $default->hasPrefix()) {
940
                    $param->setPrefix($default->getPrefix());
941
                }
942
                if (!$param->hasPostfix() && $default->hasPostfix()) {
943
                    $param->setPostfix($default->getPostfix());
944
                }
945
            }
946
        }
947
        return $finalParams;
948
    }
949
    
950
    /**
951
     * Encodes all 'final' parameters.
952
     *
953
     * @param      array $options The 'final' parameters
954
     * @param      array $params The default parameters for the route
955
     *
956
     * @return     array The 'final' parameters
957
     *
958
     * @author     Dominik del Bondio <[email protected]>
959
     * @since      1.0.0
960
     */
961
    protected function encodeParameters(array $options, array $params)
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
962
    {
963
        foreach ($params as &$param) {
964
            $param = $this->encodeParameter($param);
965
        }
966
        return $params;
967
    }
968
    
969
    /**
970
     * Encodes a single parameter.
971
     *
972
     * @param      mixed $parameter A RoutingValue object or a string
973
     *
974
     * @return     string The encoded parameter
975
     *
976
     * @author     Dominik del Bondio <[email protected]>
977
     * @since      1.0.0
978
     */
979
    protected function encodeParameter($parameter)
980
    {
981
        if ($parameter instanceof RoutingValue) {
982
            return sprintf('%s%s%s',
983
                $parameter->getPrefixNeedsEncoding()  ? $this->escapeOutputParameter($parameter->getPrefix())  : $parameter->getPrefix(),
984
                $parameter->getValueNeedsEncoding()   ? $this->escapeOutputParameter($parameter->getValue())   : $parameter->getValue(),
985
                $parameter->getPostfixNeedsEncoding() ? $this->escapeOutputParameter($parameter->getPostfix()) : $parameter->getPostfix()
986
            );
987
        } else {
988
            return $this->escapeOutputParameter($parameter);
989
        }
990
    }
991
    
992
    /**
993
     * Converts all members of an array to AgaviIRoutingValues.
994
     *
995
     * @param      array $parameters The parameters
996
     *
997
     * @return     array An array containing all parameters as RoutingValues
998
     *
999
     * @author     Dominik del Bondio <[email protected]>
1000
     * @since      1.0.0
1001
     */
1002
    protected function convertParametersToRoutingValues(array $parameters)
1003
    {
1004
        if (count($parameters)) {
1005
            // make sure everything in $parameters is a routing value
1006
            foreach ($parameters as &$param) {
1007
                if (!$param instanceof RoutingValue) {
1008
                    if ($param !== null) {
1009
                        $param = $this->createValue($param);
1010
                    }
1011
                } else {
1012
                    // make sure the routing value the user passed to gen() is not modified
1013
                    $param = clone $param;
1014
                }
1015
            }
1016
            return $parameters;
1017
        } else {
1018
            return array();
1019
        }
1020
    }
1021
1022
    /**
1023
     * Generate a formatted Agavi URL.
1024
     *
1025
     * @param      string $route   A route name.
1026
     * @param      array  $params  An associative array of parameters.
1027
     * @param      mixed  $options An array of options, or the name of an options preset.
1028
     *
1029
     * @return     array An array containing the generated route path, the
1030
     *                   (possibly modified) parameters, and the (possibly
1031
     *                   modified) options.
1032
     *
1033
     * @author     Dominik del Bondio <[email protected]>
1034
     * @author     David Zülke <[email protected]>
1035
     * @since      0.11.0
1036
     */
1037
    public function gen($route, array $params = array(), $options = array())
1038
    {
1039
        if (array_key_exists('prefix', $options)) {
1040
            $prefix = (string) $options['prefix'];
1041
        } else {
1042
            $prefix = $this->getPrefix();
1043
        }
1044
        
1045
        $isNullRoute = false;
1046
        $routes = $this->getAffectedRoutes($route, $isNullRoute);
1047
        
1048
        if (count($routes) == 0) {
1049
            return array($route, array(), $options, $params, $isNullRoute);
1050
        }
1051
        
1052
        if ($isNullRoute) {
1053
            // for gen(null) and friends all matched parameters are inserted before the
1054
            // supplied params are backuped
1055
            $params = $this->fillGenNullParameters($routes, $params);
1056
        }
1057
        
1058
        $params = $this->convertParametersToRoutingValues($params);
1059
        // we need to store the original params since we will be trying to fill the
1060
        // parameters up to the first user supplied parameter
1061
        $originalParams = $params;
1062
        
1063
        $assembledInformation = $this->assembleRoutes($options, $routes, $params);
1064
        
1065
        $options = $assembledInformation['options'];
1066
        
1067
        $params = $assembledInformation['user_parameters'];
1068
        
1069
        $params = $this->refillAllMatchedParameters($options, $params, $assembledInformation['matched_parameters']);
1070
        $finalParams = $this->refillMatchedAndDefaultParameters($options, $originalParams, $params, $assembledInformation['available_parameters'], $assembledInformation['matched_parameters'], $assembledInformation['optional_parameters'], $assembledInformation['default_parameters']);
1071
        $finalParams = $this->fillUserParameters($options, $params, $finalParams, $assembledInformation['available_parameters'], $assembledInformation['optional_parameters'], $assembledInformation['default_parameters']);
1072
        $finalParams = $this->removeMatchingDefaults($options, $finalParams, $assembledInformation['available_parameters'], $assembledInformation['optional_parameters'], $assembledInformation['default_parameters']);
1073
        $finalParams = $this->updatePrefixAndPostfix($finalParams, $assembledInformation['default_parameters']);
1074
1075
        // remember the params that are not in any pattern (could be extra query params, for example, set by a callback), use the parameter state after the callbacks have been run and defaults have been inserted. We also need to take originalParams into account for the case that a value was unset in a callback (which requires us to restore the old value). The array_merge is safe for this task since everything changed, etc appears in $params and overwrites the values from $originalParams
1076
        $extras = array_diff_key(array_merge($originalParams, $params), $finalParams);
1077
        // but since the values are expected as plain values and not routing values, convert the routing values back to
1078
        // 'plain' values
1079
        foreach ($extras as &$extra) {
1080
            $extra = ($extra instanceof RoutingValue) ? $extra->getValue() : $extra;
1081
        }
1082
1083
        $params = $finalParams;
1084
1085
        $params = $this->encodeParameters($options, $params);
1086
1087
        $from = array();
1088
        $to = array();
1089
        
1090
1091
        // remove not specified available parameters
1092
        foreach (array_unique($assembledInformation['available_parameters']) as $name) {
1093
            if (!isset($params[$name])) {
1094
                $from[] = '(:' . $name . ':)';
1095
                $to[] = '';
1096
            }
1097
        }
1098
1099
        foreach ($params as $n => $p) {
1100
            $from[] = '(:' . $n . ':)';
1101
            $to[] = $p;
1102
        }
1103
1104
        $uri = str_replace($from, $to, $assembledInformation['uri']);
1105
        return array($prefix . $uri, $params, $options, $extras, $isNullRoute);
1106
    }
1107
    
1108
    
1109
    /**
1110
     * Escapes an argument to be used in an generated route.
1111
     *
1112
     * @param      string $string The argument to be escaped.
1113
     *
1114
     * @return     string The escaped argument.
1115
     *
1116
     * @author     Dominik del Bondio <[email protected]>
1117
     * @since      0.11.0
1118
     */
1119
    public function escapeOutputParameter($string)
1120
    {
1121
        return (string)$string;
1122
    }
1123
1124
    /**
1125
     * Matches the input against the routing info and sets the info as request
1126
     * parameter.
1127
     *
1128
     * @return     mixed An ExecutionContainer as a result of this execution,
1129
     *                   or an AgaviResponse if a callback returned one.
1130
     *
1131
     * @author     Dominik del Bondio <[email protected]>
1132
     * @since      0.11.0
1133
     */
1134
    public function execute()
1135
    {
1136
        $rq = $this->context->getRequest();
1137
1138
        $rd = $rq->getRequestData();
1139
1140
        $tm = $this->context->getTranslationManager();
1141
        
1142
        $container = $this->context->getDispatcher()->createExecutionContainer();
1143
1144
        if (!$this->isEnabled()) {
1145
            // routing disabled, just bail out here
1146
            return $container;
1147
        }
1148
1149
        $matchedRoutes = array();
1150
1151
        $input = $this->input;
1152
1153
        $vars = array();
1154
        $ot = null;
0 ignored issues
show
Unused Code introduced by
$ot is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1155
        $locale = null;
0 ignored issues
show
Unused Code introduced by
$locale is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1156
        $method = null;
0 ignored issues
show
Unused Code introduced by
$method is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1157
        
1158
        $umap = $rq->getParameter('use_module_controller_parameters');
1159
        $ma = $rq->getParameter('module_accessor');
1160
        $aa = $rq->getParameter('controller_accessor');
1161
        
1162
        $requestMethod = $rq->getMethod();
1163
1164
        $routes = array();
1165
        // get all top level routes
1166
        foreach ($this->routes as $name => $route) {
1167
            if (!$route['opt']['parent']) {
1168
                $routes[] = $name;
1169
            }
1170
        }
1171
1172
        // prepare the working stack with the root routes
1173
        $routeStack = array($routes);
1174
1175
        do {
1176
            $routes = array_pop($routeStack);
1177
            foreach ($routes as $key) {
1178
                $route =& $this->routes[$key];
1179
                $opts =& $route['opt'];
1180
                if (count($opts['constraint']) == 0 || in_array($requestMethod, $opts['constraint'])) {
1181 View Code Duplication
                    if (count($opts['callbacks']) > 0 && !isset($route['callback_instances'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1182
                        foreach ($opts['callbacks'] as $key => $callback) {
1183
                            /** @var RoutingCallback $instance */
1184
                            $instance = new $callback['class']();
1185
                            $instance->initialize($this->context, $route);
1186
                            $instance->setParameters($callback['parameters']);
1187
                            $route['callback_instances'][$key] = $instance;
1188
                        }
1189
                    }
1190
1191
                    $match = array();
1192
                    if ($this->parseInput($route, $input, $match)) {
1193
                        $varsBackup = $vars;
1194
                        
1195
                        // backup the container, must be done here already
1196
                        if (count($opts['callbacks']) > 0) {
1197
                            $containerBackup = $container;
1198
                            $container = clone $container;
1199
                        }
1200
                        
1201
                        $ign = array();
1202
                        if (count($opts['ignores']) > 0) {
1203
                            $ign = array_flip($opts['ignores']);
1204
                        }
1205
1206
                        /**
1207
                         * @var $value RoutingValue;
1208
                         */
1209
                        foreach ($opts['defaults'] as $key => $value) {
1210
                            if (!isset($ign[$key]) && $value->getValue() !== null) {
1211
                                $vars[$key] = $value->getValue();
1212
                            }
1213
                        }
1214
1215
                        foreach ($route['par'] as $param) {
1216 View Code Duplication
                            if (isset($match[$param]) && $match[$param][1] != -1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1217
                                $vars[$param] = $match[$param][0];
1218
                            }
1219
                        }
1220
1221
                        foreach ($match as $name => $m) {
1222
                            if (is_string($name) && $m[1] != -1) {
1223
                                $route['matches'][$name] = $m[0];
1224
                            }
1225
                        }
1226
1227
                        // /* ! Only use the parameters from this route for expandVariables !
1228
                        // matches are arrays with value and offset due to PREG_OFFSET_CAPTURE, and we want index 0, the value, which reset() will give us. Long story short, this removes the offset from the individual match
1229
                        $matchvals = array_map('reset', $match);
1230
                        // */
1231
                        /* ! Use the parameters from ALL routes for expandVariables !
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1232
						$matchvals = $vars;
1233
						// ignores need of the current route need to be added
1234
						$foreach($opts['ignores'] as $ignore) {
1235
							if(isset($match[$ignore]) && $match[$ignore][1] != -1) {
1236
								$matchvals[$ignore] = $match[$ignore][0];
1237
							}
1238
						}
1239
						// */
1240
1241
                        if ($opts['module']) {
1242
                            $module = Toolkit::expandVariables($opts['module'], $matchvals);
1243
                            $container->setModuleName($module);
1244
                            if ($umap) {
1245
                                $vars[$ma] = $module;
1246
                            }
1247
                        }
1248
1249
                        if ($opts['controller']) {
1250
                            $controller = Toolkit::expandVariables($opts['controller'], $matchvals);
1251
                            $container->setControllerName($controller);
1252
                            if ($umap) {
1253
                                $vars[$aa] = $controller;
1254
                            }
1255
                        }
1256
1257
                        if ($opts['output_type']) {
1258
                            // set the output type if necessary
1259
                            // here no explicit check is done, since in 0.11 this is compared against null
1260
                            // which can never be the result of expandVariables
1261
                            $ot = Toolkit::expandVariables($opts['output_type'], $matchvals);
1262
                            
1263
                            // we need to wrap in try/catch here (but not further down after the callbacks have run) for BC
1264
                            // and because it makes sense - maybe a callback checks or changes the output type name
1265
                            try {
1266
                                $container->setOutputType($this->context->getDispatcher()->getOutputType($ot));
1267
                            } catch (AgaviException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1268
                            }
1269
                        }
1270
1271
                        if ($opts['locale']) {
1272
                            $localeBackup = $tm->getCurrentLocaleIdentifier();
1273
                            
1274
                            // set the locale if necessary
1275
                            if ($locale = Toolkit::expandVariables($opts['locale'], $matchvals)) {
1276
                                // the if is here for bc reasons, since if $opts['locale'] only contains variable parts
1277
                                // expandVariables could possibly return an empty string in which case the pre 1.0 routing
1278
                                // didn't set the variable
1279
                                
1280
                                // we need to wrap in try/catch here (but not further down after the callbacks have run) for BC
1281
                                // and because it makes sense - maybe a callback checks or changes the locale name
1282
                                try {
1283
                                    $tm->setLocale($locale);
1284
                                } catch (AgaviException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1285
                                }
1286
                            }
1287
                        } else {
1288
                            // unset it explicitly, so that further down, the isset() check doesn't set back a value from a previous iteration!
1289
                            $localeBackup = null;
1290
                        }
1291
1292
                        if ($opts['method']) {
1293
                            // set the request method if necessary
1294
                            if ($method = Toolkit::expandVariables($opts['method'], $matchvals)) {
1295
                                // the if is here for bc reasons, since if $opts['method'] only contains variable parts
1296
                                // expandVariables could possibly return an empty string in which case the pre 1.0 routing
1297
                                // didn't set the variable
1298
                                $rq->setMethod($method);
1299
                                // and on the already created container, too!
1300
                                $container->setRequestMethod($method);
1301
                            }
1302
                        }
1303
1304
                        if (count($opts['callbacks']) > 0) {
1305
                            if (count($opts['ignores']) > 0) {
1306
                                // add ignored variables to the callback vars
1307
                                foreach ($vars as $name => &$var) {
1308
                                    $vars[$name] =& $var;
1309
                                }
1310
                                foreach ($opts['ignores'] as $ignore) {
1311 View Code Duplication
                                    if (isset($match[$ignore]) && $match[$ignore][1] != -1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1312
                                        $vars[$ignore] = $match[$ignore][0];
1313
                                    }
1314
                                }
1315
                            }
1316
                            $callbackSuccess = true;
1317
                            /** @var RoutingCallback $callbackInstance */
1318
                            foreach ($route['callback_instances'] as $callbackInstance) {
1319
                                // call onMatched on all callbacks until one of them returns false
1320
                                // then restore state and call onNotMatched on that same callback
1321
                                // after that, call onNotMatched for all remaining callbacks of that route
1322
                                if ($callbackSuccess) {
1323
                                    // backup stuff which could be changed in the callback so we are
1324
                                    // able to determine which values were changed in the callback
1325
                                    $oldModule = $container->getModuleName();
1326
                                    $oldController = $container->getControllerName();
1327
                                    $oldOutputTypeName = $container->getOutputType() ? $container->getOutputType()->getName() : null;
1328
                                    if (null === $tm) {
1329
                                        $oldLocale = null;
1330
                                    } else {
1331
                                        $oldLocale = $tm->getCurrentLocaleIdentifier();
1332
                                    }
1333
                                    $oldRequestMethod = $rq->getMethod();
1334
                                    $oldContainerMethod = $container->getRequestMethod();
1335
1336
                                    $onMatched = $callbackInstance->onMatched($vars, $container);
1337
                                    if ($onMatched instanceof Response) {
1338
                                        return $onMatched;
1339
                                    }
1340
                                    if (!$onMatched) {
1341
                                        $callbackSuccess = false;
1342
                                        
1343
                                        // reset the matches array. it must be populated by the time onMatched() is called so matches can be modified in a callback
1344
                                        $route['matches'] = array();
1345
                                        // restore the variables from the variables which were set before this route matched
1346
                                        $vars = $varsBackup;
1347
                                        // reset all relevant container data we already set in the container for this (now non matching) route
1348
                                        $container = $containerBackup;
0 ignored issues
show
Bug introduced by
The variable $containerBackup 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...
1349
                                        // restore locale
1350
                                        if (isset($localeBackup)) {
1351
                                            $tm->setLocale($localeBackup);
1352
                                        }
1353
                                        // restore request method
1354
                                        $rq->setMethod($container->getRequestMethod());
1355
                                    }
1356
                                }
1357
                                
1358
                                // always call onNotMatched if $callbackSuccess == false, even if we just called onMatched() on the same instance. this is expected behavior
1359
                                if (!$callbackSuccess) {
1360
                                    $onNotMatched = $callbackInstance->onNotMatched($container);
1361
                                    if ($onNotMatched instanceof Response) {
1362
                                        return $onNotMatched;
1363
                                    }
1364
                                    
1365
                                    // continue with the next callback
1366
                                    continue;
1367
                                }
1368
                                
1369
                                // /* ! Only use the parameters from this route for expandVariables !
1370
                                $expandVars = $vars;
1371
                                $routeParamsAsKey = array_flip($route['par']);
1372
                                // only use parameters which are defined in this route or are new
1373
                                foreach ($expandVars as $name => $value) {
1374
                                    if (!isset($routeParamsAsKey[$name]) && array_key_exists($name, $varsBackup)) {
1375
                                        unset($expandVars[$name]);
1376
                                    }
1377
                                }
1378
                                // */
1379
                                /* ! Use the parameters from ALL routes for expandVariables !
1380
								$expandVars = $vars;
1381
								// */
1382
                                
1383
                                
1384
                                // if the callback didn't change the value, execute expandVariables again since
1385
                                // the callback could have changed one of the values which expandVariables uses
1386
                                // to evaluate the contents of the attribute in question (e.g. module="${zomg}")
1387 View Code Duplication
                                if ($opts['module'] && $oldModule == $container->getModuleName() && (!$umap || !array_key_exists($ma, $vars) || $oldModule == $vars[$ma])) {
0 ignored issues
show
Bug introduced by
The variable $oldModule 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...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1388
                                    $module = Toolkit::expandVariables($opts['module'], $expandVars);
1389
                                    $container->setModuleName($module);
1390
                                    if ($umap) {
1391
                                        $vars[$ma] = $module;
1392
                                    }
1393
                                }
1394 View Code Duplication
                                if ($opts['controller'] && $oldController == $container->getControllerName() && (!$umap || !array_key_exists($aa, $vars) || $oldController == $vars[$aa])) {
0 ignored issues
show
Bug introduced by
The variable $oldController 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...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1395
                                    $controller = Toolkit::expandVariables($opts['controller'], $expandVars);
1396
                                    $container->setControllerName($controller);
1397
                                    if ($umap) {
1398
                                        $vars[$aa] = $controller;
1399
                                    }
1400
                                }
1401
                                if ($opts['output_type'] && $oldOutputTypeName == ($container->getOutputType() ? $container->getOutputType()->getName() : null)) {
0 ignored issues
show
Bug introduced by
The variable $oldOutputTypeName 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...
1402
                                    $ot = Toolkit::expandVariables($opts['output_type'], $expandVars);
1403
                                    $container->setOutputType($this->context->getDispatcher()->getOutputType($ot));
1404
                                }
1405
                                if ($opts['locale'] && $oldLocale == $tm->getCurrentLocaleIdentifier()) {
0 ignored issues
show
Bug introduced by
The variable $oldLocale 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...
1406
                                    if ($locale = Toolkit::expandVariables($opts['locale'], $expandVars)) {
1407
                                        // see above for the reason of the if
1408
                                        $tm->setLocale($locale);
1409
                                    }
1410
                                }
1411
                                if ($opts['method']) {
1412
                                    if ($oldRequestMethod == $rq->getMethod() && $oldContainerMethod == $container->getRequestMethod()) {
1413
                                        if ($method = Toolkit::expandVariables($opts['method'], $expandVars)) {
1414
                                            // see above for the reason of the if
1415
                                            $rq->setMethod($method);
1416
                                            $container->setRequestMethod($method);
1417
                                        }
1418
                                    } elseif ($oldContainerMethod != $container->getRequestMethod()) {
0 ignored issues
show
Bug introduced by
The variable $oldContainerMethod 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...
1419
                                        // copy the request method to the request (a method set on the container
1420
                                        // in a callback always has precedence over request methods set on the request)
1421
                                        $rq->setMethod($container->getRequestMethod());
1422
                                    } elseif ($oldRequestMethod != $rq->getMethod()) {
0 ignored issues
show
Bug introduced by
The variable $oldRequestMethod 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...
1423
                                        // copy the request method to the container
1424
                                        $container->setRequestMethod($rq->getMethod());
1425
                                    }
1426
                                }
1427
                                
1428
                                // one last thing we need to do: see if one of the callbacks modified the 'controller' or 'module' vars inside $vars if $umap is on
1429
                                // we then need to write those back to the container, unless they changed THERE, too, in which case the container values take precedence
1430
                                if ($umap && $oldModule == $container->getModuleName() && array_key_exists($ma, $vars) && $vars[$ma] != $oldModule) {
1431
                                    $container->setModuleName($vars[$ma]);
1432
                                }
1433
                                if ($umap && $oldController == $container->getControllerName() && array_key_exists($aa, $vars) && $vars[$aa] != $oldController) {
1434
                                    $container->setControllerName($vars[$aa]);
1435
                                }
1436
                            }
1437
                            if (!$callbackSuccess) {
1438
                                // jump straight to the next route
1439
                                continue;
1440
                            } else {
1441
                                // We added the ignores to the route variables so the callback receives them, so restore them from vars backup.
1442
                                // Restoring them from the backup is necessary since otherwise a value which has been set before this route
1443
                                // and which was ignored in this route would take the ignored value instead of keeping the old one.
1444
                                // And variables which have not been set in an earlier routes need to be removed again
1445
                                foreach ($opts['ignores'] as $ignore) {
1446
                                    if (array_key_exists($ignore, $varsBackup)) {
1447
                                        $vars[$ignore] = $varsBackup[$ignore];
1448
                                    } else {
1449
                                        unset($vars[$ignore]);
1450
                                    }
1451
                                }
1452
                            }
1453
                        }
1454
1455
                        $matchedRoutes[] = $opts['name'];
1456
1457
                        if ($opts['cut'] || (count($opts['childs']) && $opts['cut'] === null)) {
1458
                            if ($route['opt']['source'] !== null) {
1459
                                $s =& $this->sources[$route['opt']['source']];
1460
                            } else {
1461
                                $s =& $input;
1462
                            }
1463
1464
                            $ni = '';
1465
                            // if the route didn't match from the start of the input preserve the 'prefix'
1466
                            if ($match[0][1] > 0) {
1467
                                $ni = substr($s, 0, $match[0][1]);
1468
                            }
1469
                            $ni .= substr($s, $match[0][1] + strlen($match[0][0]));
1470
                            $s = $ni;
1471
                        }
1472
1473
                        if (count($opts['childs'])) {
1474
                            // our childs need to be processed next and stop processing 'afterwards'
1475
                            $routeStack[] = $opts['childs'];
1476
                            break;
1477
                        }
1478
1479
                        if ($opts['stop']) {
1480
                            break;
1481
                        }
1482
                    } else {
1483
                        if (count($opts['callbacks']) > 0) {
1484
                            /** @var RoutingCallback $callbackInstance */
1485
                            foreach ($route['callback_instances'] as $callbackInstance) {
1486
                                $onNotMatched = $callbackInstance->onNotMatched($container);
1487
                                if ($onNotMatched instanceof Response) {
1488
                                    return $onNotMatched;
1489
                                }
1490
                            }
1491
                        }
1492
                    }
1493
                }
1494
            }
1495
        } while (count($routeStack) > 0);
1496
1497
        // put the vars into the request
1498
        $rd->setParameters($vars);
1499
1500
        if ($container->getModuleName() === null || $container->getControllerName() === null) {
1501
            // no route which supplied the required parameters matched, use 404 controller
1502
            $container->setModuleName(Config::get('controllers.error_404_module'));
1503
            $container->setControllerName(Config::get('controllers.error_404_controller'));
1504
            
1505
            if ($umap) {
1506
                $rd->setParameters(array(
1507
                    $ma => $container->getModuleName(),
1508
                    $aa => $container->getControllerName(),
1509
                ));
1510
            }
1511
        }
1512
1513
        // set the list of matched route names as a request attribute
1514
        $rq->setAttribute('matched_routes', $matchedRoutes, 'org.agavi.routing');
1515
1516
        // return a list of matched route names
1517
        return $container;
1518
    }
1519
1520
    /**
1521
     * Performs as match of the route against the input
1522
     *
1523
     * @param      array  $route   The route info array.
1524
     * @param      string $input   The input.
1525
     * @param      array  $matches The array where the matches will be stored to.
1526
     *
1527
     * @return     bool Whether the regexp matched.
1528
     *
1529
     * @author     Dominik del Bondio <[email protected]>
1530
     * @since      0.11.0
1531
     */
1532
    protected function parseInput(array $route, $input, &$matches)
1533
    {
1534
        if ($route['opt']['source'] !== null) {
1535
            $parts = ArrayPathDefinition::getPartsFromPath($route['opt']['source']);
1536
            $partArray = $parts['parts'];
1537
            $count = count($partArray);
1538
            if ($count > 0 && isset($this->sources[$partArray[0]])) {
1539
                $input = $this->sources[$partArray[0]];
1540
                if ($count > 1) {
1541
                    array_shift($partArray);
1542
                    if (is_array($input)) {
1543
                        $input = ArrayPathDefinition::getValue($partArray, $input);
1544
                    } elseif ($input instanceof RoutingSourceInterface) {
1545
                        $input = $input->getSource($partArray);
1546
                    }
1547
                }
1548
            }
1549
        }
1550
        return preg_match($route['rxp'], $input, $matches, PREG_OFFSET_CAPTURE);
1551
    }
1552
1553
    /**
1554
     * Parses a route pattern string.
1555
     *
1556
     * @param      string $str The route pattern.
1557
     *
1558
     * @return     array The info for this route pattern.
1559
     *
1560
     * @author     Dominik del Bondio <[email protected]>
1561
     * @since      0.11.0
1562
     */
1563
    protected function parseRouteString($str)
1564
    {
1565
        $vars = array();
1566
        $rxStr = '';
1567
        $reverseStr = '';
1568
1569
        $anchor = 0;
1570
        $anchor |= (substr($str, 0, 1) == '^') ? self::ANCHOR_START : 0;
1571
        $anchor |= (substr($str, -1) == '$') ? self::ANCHOR_END : 0;
1572
1573
        $str = substr($str, (int)$anchor & self::ANCHOR_START, $anchor & self::ANCHOR_END ? -1 : strlen($str));
1574
1575
        $rxChars = implode('', array('.', '\\', '+', '*', '?', '[', '^', ']', '$', '(', ')', '{', '}', '=', '!', '<', '>', '|', ':'));
1576
1577
        $len = strlen($str);
1578
        $state = 'start';
1579
        $tmpStr = '';
1580
        $inEscape = false;
1581
1582
        $rxName = null;
1583
        $rxInner = null;
1584
        $rxPrefix = null;
1585
        $rxPostfix = null;
1586
        $parenthesisCount = 0;
1587
        $bracketCount = 0;
1588
        $hasBrackets = false;
1589
1590
        for ($i = 0; $i < $len; ++$i) {
1591
            $atEnd = $i + 1 == $len;
1592
1593
            $c = $str[$i];
1594
1595
            if (!$atEnd && !$inEscape && $c == '\\') {
1596
                $cNext = $str[$i + 1];
1597
1598
                if (($cNext == '\\') ||
1599
                    ($state == 'start' && $cNext == '(') ||
1600
                    ($state == 'rxStart' && in_array($cNext, array('(',')','{','}')))
1601
                ) {
1602
                    $inEscape = true;
1603
                    continue;
1604
                }
1605
                if ($state == 'afterRx' && $cNext == '?') {
1606
                    $inEscape = false;
1607
                    $state = 'start';
1608
                    continue;
1609
                }
1610
            } elseif ($inEscape) {
1611
                $tmpStr .= $c;
1612
                $inEscape = false;
1613
                continue;
1614
            }
1615
1616
            if ($state == 'start') {
1617
                // start of regular expression block
1618
                if ($c == '(') {
1619
                    $rxStr .= preg_quote($tmpStr, '#');
1620
                    $reverseStr .= $tmpStr;
1621
1622
                    $tmpStr = '';
1623
                    $state = 'rxStart';
1624
                    $rxName = $rxInner = $rxPrefix = $rxPostfix = null;
1625
                    $parenthesisCount = 1;
1626
                    $bracketCount = 0;
1627
                    $hasBrackets = false;
1628
                } else {
1629
                    $tmpStr .= $c;
1630
                }
1631
1632
                if ($atEnd) {
1633
                    $rxStr .= preg_quote($tmpStr, '#');
1634
                    $reverseStr .= $tmpStr;
1635
                }
1636
            } elseif ($state == 'rxStart') {
1637
                if ($c == '{') {
1638
                    ++$bracketCount;
1639
                    if ($bracketCount == 1) {
1640
                        $hasBrackets = true;
1641
                        $rxPrefix = $tmpStr;
1642
                        $tmpStr = '';
1643
                    } else {
1644
                        $tmpStr .= $c;
1645
                    }
1646
                } elseif ($c == '}') {
1647
                    --$bracketCount;
1648
                    if ($bracketCount == 0) {
1649
                        list($rxName, $rxInner) = $this->parseParameterDefinition($tmpStr);
1650
                        $tmpStr = '';
1651
                    } else {
1652
                        $tmpStr .= $c;
1653
                    }
1654
                } elseif ($c == '(') {
1655
                    ++$parenthesisCount;
1656
                    $tmpStr .= $c;
1657
                } elseif ($c == ')') {
1658
                    --$parenthesisCount;
1659
                    if ($parenthesisCount > 0) {
1660
                        $tmpStr .= $c;
1661
                    } else {
1662
                        if ($parenthesisCount < 0) {
1663
                            throw new AgaviException('The pattern ' . $str . ' contains an unbalanced set of parentheses!');
1664
                        }
1665
1666
                        if (!$hasBrackets) {
1667
                            list($rxName, $rxInner) = $this->parseParameterDefinition($tmpStr);
1668
                        } else {
1669
                            if ($bracketCount != 0) {
1670
                                throw new AgaviException('The pattern ' . $str . ' contains an unbalanced set of brackets!');
1671
                            }
1672
                            $rxPostfix = $tmpStr;
1673
                        }
1674
1675
                        if (!$rxName) {
1676
                            $myRx = $rxPrefix . $rxInner . $rxPostfix;
1677
                            // if the entire regular expression doesn't contain any regular expression character we can safely append it to the reverseStr
1678
                            //if(strlen($myRx) == strcspn($myRx, $rxChars)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1679
                            if (strpbrk($myRx, $rxChars) === false) {
1680
                                $reverseStr .= $myRx;
1681
                            }
1682
                            $rxStr .= str_replace('#', '\#', sprintf('(%s)', $myRx));
1683
                        } else {
1684
                            $rxStr .= str_replace('#', '\#', sprintf('(%s(?P<%s>%s)%s)', $rxPrefix, $rxName, $rxInner, $rxPostfix));
1685
                            $reverseStr .= sprintf('(:%s:)', $rxName);
1686
1687
                            if (!isset($vars[$rxName])) {
1688
                                if (strpbrk($rxPrefix, $rxChars) !== false) {
1689
                                    $rxPrefix = null;
1690
                                }
1691
                                if (strpbrk($rxInner, $rxChars) !== false) {
1692
                                    $rxInner = null;
1693
                                }
1694
                                if (strpbrk($rxPostfix, $rxChars) !== false) {
1695
                                    $rxPostfix = null;
1696
                                }
1697
1698
                                $vars[$rxName] = array('pre' => $rxPrefix, 'val' => $rxInner, 'post' => $rxPostfix, 'is_optional' => false);
1699
                            }
1700
                        }
1701
1702
                        $tmpStr = '';
1703
                        $state = 'afterRx';
1704
                    }
1705
                } else {
1706
                    $tmpStr .= $c;
1707
                }
1708
1709
                if ($atEnd && $parenthesisCount != 0) {
1710
                    throw new AgaviException('The pattern ' . $str . ' contains an unbalanced set of parentheses!');
1711
                }
1712
            } elseif ($state == 'afterRx') {
1713
                if ($c == '?') {
1714
                    // only record the optional state when the pattern had a name
1715
                    if (isset($vars[$rxName])) {
1716
                        $vars[$rxName]['is_optional'] = true;
1717
                    }
1718
                    $rxStr .= $c;
1719
                } else {
1720
                    // let the start state parse the char
1721
                    --$i;
1722
                }
1723
1724
                $state = 'start';
1725
            }
1726
        }
1727
1728
        $rxStr = sprintf('#%s%s%s#', $anchor & self::ANCHOR_START ? '^' : '', $rxStr, $anchor & self::ANCHOR_END ? '$' : '');
1729
        return array($rxStr, $reverseStr, $vars, $anchor);
1730
    }
1731
1732
    /**
1733
     * Parses an embedded regular expression in the route pattern string.
1734
     *
1735
     * @param      string $def The definition.
1736
     *
1737
     * @return     array The name and the regexp.
1738
     *
1739
     * @author     Dominik del Bondio <[email protected]>
1740
     * @since      0.11.0
1741
     */
1742
    protected function parseParameterDefinition($def)
1743
    {
1744
        preg_match('#(?:([a-z0-9_-]+):)?(.*)#i', $def, $match);
1745
        return array($match[1] !== '' ? $match[1] : null, $match[2]);
1746
    }
1747
    
1748
    /**
1749
     * Creates and initializes a new RoutingValue.
1750
     *
1751
     * @param      mixed $value              The value of the returned routing value.
1752
     * @param      bool  $valueNeedsEncoding Whether the $value needs to be encoded.
1753
     *
1754
     * @return     RoutingValue
1755
     *
1756
     * @author     Dominik del Bondio <[email protected]>
1757
     * @since      1.0.0
1758
     */
1759
    public function createValue($value, $valueNeedsEncoding = true)
1760
    {
1761
        $value = new RoutingValue($value, $valueNeedsEncoding);
1762
        $value->initialize($this->context);
1763
        return $value;
1764
    }
1765
}
1766