Completed
Push — 4.0 ( 2ca4e1...0c3d7c )
by Marco
15:24
created

RoutingTable::readpath()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 76
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 76
rs 6.1941
cc 8
eloc 26
nc 8
nop 3

How to fix   Long Method   

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 namespace Comodojo\Dispatcher\Router;
2
3
use \Monolog\Logger;
4
use \Exception;
5
6
/**
7
 * @package     Comodojo Dispatcher
8
 * @author      Marco Giovinazzi <[email protected]>
9
 * @author      Marco Castiello <[email protected]>
10
 * @license     GPL-3.0+
11
 *
12
 * LICENSE:
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26
 */
27
28
class RoutingTable implements RoutingTableInterface {
29
30
    private $routes = array();
31
    private $logger;
32
33
    public function __construct(Logger $logger) {
34
35
        $this->logger = $logger;
36
37
    }
38
39 View Code Duplication
    public function put($route, $type, $class, $parameters = array()) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
40
41
        $folders = explode("/", $route);
42
43
        $regex = $this->readpath($folders);
44
45
        if (!isset($this->routes[$regex])) {
46
47
            $this->add($folders, $type, $class, $parameters);
48
49
        }
50
51
    }
52
53 View Code Duplication
    public function set($route, $type, $class, $parameters = array()) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
54
55
        $folders = explode("/", $route);
56
57
        $regex = $this->readpath($folders);
58
59
        if (isset($this->routes[$regex])) {
60
61
            $this->add($folders, $type, $class, $parameters);
62
63
        }
64
65
    }
66
67 View Code Duplication
    public function get($route) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
68
69
        $folders = explode("/", $route);
70
71
        $regex = $this->readpath($folders);
72
73
        if (isset($this->routes[$regex]))
74
            return $this->routes[$regex];
75
        else
76
            return null;
77
78
    }
79
80 View Code Duplication
    public function remove($route) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
81
82
        $folders = explode("/", $route);
83
84
        $regex = $this->readpath($folders);
85
86
        if (isset($this->routes[$regex])) unset($this->routes[$regex]);
87
88
    }
89
90
    public function routes() {
91
92
        return $this->routes;
93
94
    }
95
96
    public function defaultRoute() {
97
98
        return $this->get('/');
99
100
    }
101
    
102
    // This method read the route (folder by folder recursively) and build 
103
    // the global regular expression against which all the request URI will be compared
104
    private function readpath($folders = array(), &$value = null, $regex = '') {
105
        
106
        // if the first 'folder' is empty is removed
107
        while (!empty($folders) && empty($folders[0])) {
108
109
            array_shift($folders);
110
111
        }
112
113
        // if the 'folder' array is empty, the route has been fully analyzed
114
        // this is the exit condition from the recursive loop.
115
        if (empty($folders)) {
116
117
            return '^'.$regex.'[\/]?$';
118
119
        } else {
120
121
            // The first element of the array 'folders' is taken in order to be analyzed
122
            $folder  = array_shift($folders);
123
            
124
            // All the parameters of the route must be json strings
125
            $decoded = json_decode($folder, true);
126
127
            if (!is_null($decoded) && is_array($decoded)) {
128
129
                $param_regex    = '';
130
131
                $param_required = false;
132
133
                /* All the folders can include more than one parameter
134
                 * Eg: /service_name/{'param1': 'regex1', 'param2': 'regex2'}/
135
                 *     /calendar/{'ux_timestamp*': '\d{10}', 'microseconds': '\d{4}'}/
136
                 *
137
                 * The '*' at the end of the paramerter name implies that the parameter is required
138
                 * This example can be read as a calendar service that accepts both 
139
                 * timestamps in unix or javascript format.
140
                 *
141
                 * This is the reason of the following 'foreach'
142
                 */
143
                foreach ($decoded as $key => $string) {
144
145
                    $this->logger->debug("PARAMETER KEY: " . $key);
146
147
                    $this->logger->debug("PARAMETER STRING: " . $string);
148
                    
149
                    /* The key and the regex of every paramater is passed to the 'readparam'
150
                     * method which will build an appropriate regular expression and will understand 
151
                     * if the parameter is required and will build the $value['query'] object
152
                     */
153
                    $param_regex .= $this->readparam($key, $string, $param_required, $value);
154
155
                    $this->logger->debug("PARAMETER REGEX: " . $param_regex);
156
157
                }
158
                // Once the parameter is analyzed, the result is passed to the next iteration
159
                $this->readpath(
160
                    $folders,
161
                    $value,
162
                    $regex.'(?:\/'.$param_regex.')'. (($param_required)?'{1}':'?')
163
                );
164
165
            } else {
166
                // if the element is not a json string, I assume it's the service name
167
                array_push($value['service'], $folder);
168
169
                $this->readpath(
170
                    $folders,
171
                    $value,
172
                    $regex.'\/'.$folder
173
                );
174
175
            }
176
177
        }
178
179
    }
180
181
    // This method read a single parameter and build the regular expression
182
    private function readparam($key, $string, &$param_required, &$value) {
183
184
        $field_required = false;
185
186
        // If the field name ends with a '*', the parameter is considered as required
187
        if (preg_match('/^(.+)\*$/', $key, $bits)) {
188
189
            $key            = $bits[1];
190
            $field_required = true;
191
            $param_required = true;
192
193
        }
194
195
        // The $value['query'] field contains all regex which will be used by the collector to parse the route fields
196
        if (!is_null($value)) {
197
198
            $value['query'][$key] = array(
199
                'regex'    => $string,
200
                'required' => $required
0 ignored issues
show
Bug introduced by
The variable $required does not exist. Did you mean $param_required?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
201
            );
202
203
        }
204
205
        /* Every parameter can include it's own logic into the regular expression,
206
         * it can use backreferences and it's expected to be used against a single parameter.
207
         * This means that it can't be used as is to build the route regular expression,
208
         * Backreferences are not useful at this point and can make the regular expression more time consuming
209
         * and resource hungry. This is why they are replaced with the grouping parenthesis.
210
         * Eg: (value) changes in (?:value)
211
         *
212
         * Delimiting characters like '^' and '$' are also meaningless in the complete regular expression and
213
         * need to be removed. Contrariwise, wildcards must be delimited in order to keet the whole regular
214
         * expression consistent, hence a '?' is added to all the '.*' or '.+' that don't already have one.
215
         */
216
        $string = preg_replace('/(?<!\\)\((?!\?)/', '(?:', $string);
217
        $string = preg_replace('/\.([\*\+])(?!\?)/', '.\${1}?', $string);
218
        $string = preg_replace('/^[\^]/', '', $string);
219
        $string = preg_replace('/[\$]$/', '', $string);
220
221
        /* The produced regular expression is grouped and associated with its key (this means that the 'preg_match'
222
         * function will generate an associative array where the key/value association is preserved).
223
         * If the field is required, the regular expression is completed with a '{1}' (which make it compulsory),
224
         * otherwise a '?' is added.
225
         */
226
        return '(?P<' . $key . '>' . $string . ')' . (($field_required)?'{1}':'?');
227
228
    }
229
230
    // This method add a route to the supported list
231
    private function add($folders, $type, $class, $parameters) {
232
233
        // The values associated with a route are as follows:
234
        $value   = array(
235
            "type"       => $type,       // Type of route
236
            "class"      => $class,      // Class to be invoked
237
            "service"    => array(),     // Service name (it can be a list of namespaces plus a final service name)
238
            "parameters" => $parameters, // Parameters passed via the composer.json configuration (cache, ttl, etc...)
239
            "query"      => array()      // List of parameters with their regular expression that must be added among the query parameters
240
        );
241
242
        $this->logger->debug("ROUTE: " . $route);
0 ignored issues
show
Bug introduced by
The variable $route does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
243
244
        $this->logger->debug("PARAMETERS: " . var_export($value, true));
245
246
        // This method generate a global regular expression which will be able to match all the URI supported by the route
247
        $regex = $this->readpath($folders, $value);
248
249
        $this->logger->debug("ROUTE: " . $regex);
250
251
        $this->logger->debug("PARAMETERS: " . var_export($value, true));
252
253
        $this->routes[$regex] = $value;
254
255
    }
256
257
}
258