Completed
Push — master ( 4d1d47...1893cb )
by Vitaly
02:47
created

GenericRouteGenerator::getMethodParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 7
rs 9.4286
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: VITALYIEGOROV
5
 * Date: 26.10.15
6
 * Time: 10:50
7
 */
8
namespace samsonphp\router;
9
10
use \samsonframework\routing\RouteGeneratorInterface;
11
use \samsonframework\routing\RouteCollection;
12
use \samsonframework\routing\Route;
13
14
/**
15
 * This class is needed to generate routes for old SamsonPHP modules
16
 * @package samsonphp\router
17
 */
18
class GenericRouteGenerator
19
{
20
    /** Default controller name */
21
    const CTR_BASE = '__base';
22
    const CTR_CACHE_BASE = '__cache_base';
23
24
    /** Universal controller name */
25
    const CTR_UNI = '__handler';
26
    const CTR_CACHE_UNI = '__cache_handler';
27
28
    /** Post controller name */
29
    const CTR_POST = '__post';
30
    const CTR_CACHE_POST = '__cache_post';
31
32
    /** Put controller name */
33
    const CTR_PUT = '__put';
34
    const CTR_CACHE_PUT = '__cache_put';
35
36
    /** Delete controller name */
37
    const CTR_DELETE = '__delete';
38
    const CTR_CACHE_DELETE = '__cache_delete';
39
40
    /** Delete controller name */
41
    const CTR_UPDATE = '__update';
42
    const CTR_CACHE_UPDATE = '__cache_update';
43
44
    /** Controllers naming conventions */
45
46
    /** Procedural controller prefix */
47
    const PROC_PREFIX = '_';
48
    /** OOP controller prefix */
49
    const OBJ_PREFIX = '__';
50
    /** AJAX controller prefix */
51
    const ASYNC_PREFIX = 'async_';
52
    /** CACHE controller prefix */
53
    const CACHE_PREFIX = 'cache_';
54
55
    /** @var RouteCollection Generated routes collection */
56
    protected $routes;
57
58
    /** @var Object[] Collection of SamsonPHP modules */
59
    protected $modules;
60
61
    /**
62
     * @return RouteCollection Generated routes collection
63
     */
64
    public function &routes()
65
    {
66
        return $this->routes;
67
    }
68
69
    /**
70
     * GenericRouteGenerator constructor.
71
     * @param Module[] $modules
72
     */
73
    public function __construct(array & $modules)
74
    {
75
        $this->routes = new RouteCollection();
76
        $this->modules = &$modules;
77
    }
78
79
    /**
80
     * Load all SamsonPHP web-application routes.
81
     *
82
     * @return RouteCollection Collection of web-application routes
83
     */
84
    public function &generate()
85
    {
86
        foreach ($this->modules as $moduleID => & $module) {
87
            // Try to get module routes using interface method
88
            $moduleRoutes = method_exists($module, 'routes') ? $module->routes() : array();
89
90
            // There are no routes defined
91
            if (!sizeof($moduleRoutes)) {
92
                // Generate generic routes
93
                $moduleRoutes = $this->createGenericRoutes($module);
94
            }
95
96
            $this->routes = $this->routes->merge($moduleRoutes);
97
        }
98
99
        return $this->routes;
100
    }
101
102
    /**
103
     * Class method signature parameters.
104
     *
105
     * @param object $object Object
106
     * @param string $method Method name
107
     * @return array Method parameters
108
     */
109
    protected function getMethodParameters($object, $method)
110
    {
111
        // Analyze callback arguments
112
        $reflectionMethod = new \ReflectionMethod($object, $method);
113
114
        return $reflectionMethod->getParameters();
115
    }
116
117
    /**
118
     * Convert class method signature into route pattern with parameters.
119
     *
120
     * @param object $object Object
121
     * @param string $method Method name
122
     * @return string Pattern string with parameters placeholders
123
     */
124
    protected function buildMethodParameters($object, $method)
125
    {
126
        $pattern = array();
127
128
        // Analyze callback arguments
129
        foreach ($this->getMethodParameters($object, $method) as $parameter) {
130
            // Build pattern markers
131
            $pattern[] = '{' . $parameter->getName() . '}';
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
132
            //trace($parameter->getDefaultValue(),1);
133
        }
134
135
        return implode('/', $pattern);
136
    }
137
138
    /**
139
     * @param $module
140
     * @param $prefix
141
     * @param $method
142
     * @param $action
143
     * @param string $async
144
     * @return RouteCollection
145
     * @throws \samsonframework\routing\exception\IdentifierDuplication
146
     */
147
    protected function getParametrizedRoutes($module, $prefix, $method, $action = '', $async = '')
148
    {
149
        $routes = new RouteCollection();
150
151
        // Iterate method parameters list to find NOT optional parameters
152
        $parameters = array();
153
        $optionalParameters = array();
154
        foreach ($this->getMethodParameters($module, $method) as $parameter) {
155
            if (!$parameter->isOptional()) {
156
                // Append parameter to collection
157
                $parameters[] = '{' . $parameter->getName() . '}';
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
158
            } else {
159
                $optionalParameters[] = $parameter->getName();
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
160
            }
161
        }
162
163
        // Build controller action pattern
164
        $pattern = $prefix . '/';
165
        // Add controller action if passed
166
        $pattern = isset($action{0}) ? $pattern . $action . '/' : $pattern;
167
        // Add needed parameters
168
        $pattern .= implode('/', $parameters);
169
170
        $optionalPattern = $pattern;
171
172
        // Iterate all optional parameters
173
        foreach ($optionalParameters as $parameter) {
174
            // Add optional parameter as now we consider it needed
175
            $optionalPattern .= '{' . $parameter . '}/';
176
177
            // Add SamsonPHP specific async method
178 View Code Duplication
            foreach (Route::$METHODS as $httpMethod) {
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...
179
                // Add route for this controller action
180
                $routes->add(
181
                    new Route(
182
                        $optionalPattern,
183
                        array($module, $method), // Route callback
184
                        $module->id . '_' . $httpMethod . '_' . $method.'_'.$parameter, // Route identifier
185
                        $async . $httpMethod // Prepend async prefix to method if found
186
                    )
187
                );
188
            }
189
        }
190
191
        // Add SamsonPHP without optional parameters
192 View Code Duplication
        foreach (Route::$METHODS as $httpMethod) {
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...
193
            // Add route for this controller action
194
            $routes->add(
195
                new Route(
196
                    $pattern,
197
                    array($module, $method), // Route callback
198
                    $module->id . '_' . $httpMethod . '_' . $method, // Route identifier
199
                    $async . $httpMethod // Prepend async prefix to method if found
200
                )
201
            );
202
        }
203
204
        return $routes;
205
    }
206
207
    /**
208
     * Generate old-fashioned routes collection.
209
     *
210
     * @param Object $module
211
     * @return array
212
     */
213
    protected function createGenericRoutes(&$module)
214
    {
215
        /** @var RouteCollection $routes */
216
        $routes = new RouteCollection();
217
        /** @var callable $universalCallback */
218
        $universalCallback = null;
219
        $universalRoutes = new RouteCollection();
220
        /** @var Route $baseRoute */
221
        $baseRoute = null;
222
223
        // Iterate class methods
224
        foreach (get_class_methods($module) as $method) {
225
            $prefix = '/' . $module->id;
226
            // Try to find standard controllers
227
            switch (strtolower($method)) {
228
                case self::CTR_UNI: // Add generic controller action
229
                    $universalCallback = array($module, $method);
230
                    $universalRoutes->merge($this->getParametrizedRoutes($module, $prefix, $method));
231
                    break;
232
                case self::CTR_BASE: // Add base controller action
233
                    $baseRoute = new Route($prefix . '/', array($module, $method), $module->id . self::CTR_BASE);
234
                    break;
235
                case self::CTR_POST:// not implemented
236
                case self::CTR_PUT:// not implemented
237
                case self::CTR_DELETE:// not implemented
238
                    break;
239
240
                // Ignore magic methods
241
                case '__call':
242
                case '__wakeup':
243
                case '__sleep':
244
                case '__construct':
245
                case '__destruct':
246
                case '__set':
247
                case '__get':
248
                    break;
249
250
                // This is not special controller action
251
                default:
252
                    // Match controller action OOP pattern
253
                    if (preg_match('/^' . self::OBJ_PREFIX . '(?<async_>async_)?(?<cache_>cache_)?(?<action>.+)/i', $method, $matches)) {
254
                        $routes->merge($this->getParametrizedRoutes($module, $prefix, $method, $matches['action'], $matches[self::ASYNC_PREFIX]));
255
                    }
256
            }
257
        }
258
259
        // Add universal route
260
        $routes->merge($universalRoutes);
261
262
        // Add base controller action
263
        if (isset($baseRoute)) {
264
            $routes->add($baseRoute);
265
            // If we have not found base controller action but we have universal action
266
        } elseif (isset($universalCallback)) {
267
            // Bind its pattern to universal controller callback
268
            $routes->add(
269
                new Route(
270
                    $prefix . '/',
0 ignored issues
show
Bug introduced by
The variable $prefix 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...
271
                    $universalCallback,
272
                    $module->id . self::CTR_BASE
273
                )
274
            );
275
        }
276
277
        return $routes;
278
    }
279
}
280