Completed
Push — 4.0 ( 0e3cc0...125a8d )
by Marco
03:38
created

Parser::read()   C

Complexity

Conditions 10
Paths 20

Size

Total Lines 86
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 10.1626

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 86
ccs 30
cts 34
cp 0.8824
rs 5.3068
cc 10
eloc 30
nc 20
nop 3
crap 10.1626

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 namespace Comodojo\Dispatcher\Router;
2
3
use \Comodojo\Dispatcher\Router\Route;
4
use \Psr\Log\LoggerInterface;
5
use \Comodojo\Exception\DispatcherException;
6
use \Exception;
7
8
/**
9
 * @package     Comodojo Dispatcher
10
 * @author      Marco Giovinazzi <[email protected]>
11
 * @author      Marco Castiello <[email protected]>
12
 * @license     GPL-3.0+
13
 *
14
 * LICENSE:
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License
27
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28
 */
29
30
class Parser {
31
32
    public $logger;
33
34 1
    public function __construct(LoggerInterface $logger) {
35
36 1
        $this->logger = $logger;
37
38 1
    }
39
40
    // This method read the route (folder by folder recursively) and build
41
    // the global regular expression against which all the request URI will be compared
42 1
    public function read($folders = array(), Route $value = null, $regex = '') {
43
44 1
        if (is_null($value)) {
45
46 1
            $value = new Route();
47
48 1
        }
49
50
        // if the first 'folder' is empty is removed
51 1
        while (!empty($folders) && empty($folders[0])) {
52
53
            array_shift($folders);
54
55
        }
56
57
        // if the 'folder' array is empty, the route has been fully analyzed
58
        // this is the exit condition from the recursive loop.
59 1
        if (empty($folders)) {
60
61 1
            return '^'.$regex.'[\/]?$';
62
63
        } else {
64
65
            // The first element of the array 'folders' is taken in order to be analyzed
66 1
            $folder  = array_shift($folders);
67
68
            // All the parameters of the route must be json strings
69 1
            $decoded = json_decode($folder, true);
70
71 1
            if (!is_null($decoded) && is_array($decoded)) {
72
73 1
                $param_regex = '';
74
75 1
                $param_required = false;
76
77
                /* All the folders can include more than one parameter
78
                 * Eg: /service_name/{'param1': 'regex1', 'param2': 'regex2'}/
79
                 *     /calendar/{'ux_timestamp*': '\d{10}', 'microseconds': '\d{4}'}/
80
                 *
81
                 * The '*' at the end of the paramerter name implies that the parameter is required
82
                 * This example can be read as a calendar service that accepts both
83
                 * timestamps in unix or javascript format.
84
                 *
85
                 * This is the reason of the following 'foreach'
86
                 */
87 1
                foreach ($decoded as $key => $string) {
88
89 1
                    $this->logger->debug("Route parser - parameter key: $key");
90
91 1
                    $this->logger->debug("Route parser - parameter string: $string");
92
93
                    /* The key and the regex of every paramater is passed to the 'param'
94
                     * method which will build an appropriate regular expression and will understand
95
                     * if the parameter is required and will build the Route query object
96
                     */
97 1
                    $param_regex .= $this->param($key, $string, $value);
98
99 1
                    if ($value->isQueryRequired($key)) {
100
                        $param_required = true;
101
                    }
102
103 1
                    $this->logger->debug("Route parser - parameter regex: $param_regex");
104
105 1
                }
106
                // Once the parameter is analyzed, the result is passed to the next iteration
107 1
                return $this->read(
108 1
                    $folders,
109 1
                    $value,
110 1
                    $regex.'(?:\/'.$param_regex.')'.(($param_required) ? '{1}' : '?')
111 1
                );
112
113
            } else {
114
                // if the element is not a json string, I assume it's the service name
115 1
                $value->addService($folder);
116
117 1
                return $this->read(
118 1
                    $folders,
119 1
                    $value,
120 1
                    $regex.'\/'.$folder
121 1
                );
122
123
            }
124
125
        }
126
127
    }
128
129
    // This method read a single parameter and build the regular expression
130 1
    private function param($key, $string, $value) {
131
132 1
        $field_required = false;
133
134
        // If the field name ends with a '*', the parameter is considered as required
135 1
        if (preg_match('/^(.+)\*$/', $key, $bits)) {
136
137 1
            $key = $bits[1];
138 1
            $field_required = true;
139
140 1
        }
141
142
        // The $value query object contains all regex which will be used by the collector to parse the route fields
143 1
        $value->setQuery($key, $string, $field_required);
144
145
        /* Every parameter can include it's own logic into the regular expression,
146
         * it can use backreferences and it's expected to be used against a single parameter.
147
         * This means that it can't be used as is to build the route regular expression,
148
         * Backreferences are not useful at this point and can make the regular expression more time consuming
149
         * and resource hungry. This is why they are replaced with the grouping parenthesis.
150
         * Eg: (value) changes in (?:value)
151
         *
152
         * Delimiting characters like '^' and '$' are also meaningless in the complete regular expression and
153
         * need to be removed. Contrariwise, wildcards must be delimited in order to keet the whole regular
154
         * expression consistent, hence a '?' is added to all the '.*' or '.+' that don't already have one.
155
         */
156 1
        $string = preg_replace("/(?<!\\\\)\\((?!\\?)/", '(?:', $string);
157 1
        $string = preg_replace("/\\.([\\*\\+])(?!\\?)/", '.${1}?', $string);
158 1
        $string = preg_replace("/^[\\^]/", '', $string);
159 1
        $string = preg_replace("/[\\$]$/", '', $string);
160
161
        /* The produced regular expression is grouped and associated with its key (this means that the 'preg_match'
162
         * function will generate an associative array where the key/value association is preserved).
163
         * If the field is required, the regular expression is completed with a '{1}' (which make it compulsory),
164
         * otherwise a '?' is added.
165
         */
166 1
        return '(?P<'.$key.'>'.$string.')'.(($field_required) ? '{1}' : '?');
167
168
    }
169
170
}
171