Completed
Push — master ( 465cbd...e0d13e )
by Marco
06:44 queued 11s
created

Parser::read()   C

Complexity

Conditions 10
Paths 20

Size

Total Lines 79
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 10.178

Importance

Changes 0
Metric Value
dl 0
loc 79
c 0
b 0
f 0
ccs 29
cts 33
cp 0.8788
rs 5.5893
cc 10
eloc 29
nc 20
nop 3
crap 10.178

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     MIT
13
 *
14
 * LICENSE:
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
 * THE SOFTWARE.
23
 */
24
25
class Parser {
26
27
    public $logger;
28
29 1
    public function __construct(LoggerInterface $logger) {
30
31 1
        $this->logger = $logger;
32
33 1
    }
34
35
    // This method read the route (folder by folder recursively) and build
36
    // the global regular expression against which all the request URI will be compared
37 1
    public function read($folders = array(), Route $value = null, $regex = '') {
38
39 1
        if (is_null($value)) {
40
41 1
            $value = new Route();
42
43 1
        }
44
45
        // if the first 'folder' is empty is removed
46 1
        while (!empty($folders) && empty($folders[0])) {
47
48
            array_shift($folders);
49
50
        }
51
52
        // if the 'folder' array is empty, the route has been fully analyzed
53
        // this is the exit condition from the recursive loop.
54 1
        if (empty($folders)) {
55
56 1
            return '^'.$regex.'[\/]?$';
57
58
        } else {
59
60
            // The first element of the array 'folders' is taken in order to be analyzed
61 1
            $folder  = array_shift($folders);
62
63
            // All the parameters of the route must be json strings
64 1
            $decoded = json_decode($folder, true);
65
66 1
            if (!is_null($decoded) && is_array($decoded)) {
67
68 1
                $param_regex = '';
69
70 1
                $param_required = false;
71
72
                /* All the folders can include more than one parameter
73
                 * Eg: /service_name/{'param1': 'regex1', 'param2': 'regex2'}/
74
                 *     /calendar/{'ux_timestamp*': '\d{10}', 'microseconds': '\d{4}'}/
75
                 *
76
                 * The '*' at the end of the paramerter name implies that the parameter is required
77
                 * This example can be read as a calendar service that accepts both
78
                 * timestamps in unix or javascript format.
79
                 *
80
                 * This is the reason of the following 'foreach'
81
                 */
82 1
                foreach ($decoded as $key => $string) {
83
84 1
                    $this->logger->debug("Route parser - parameter key: $key");
85
86 1
                    $this->logger->debug("Route parser - parameter string: $string");
87
88
                    /* The key and the regex of every paramater is passed to the 'param'
89
                     * method which will build an appropriate regular expression and will understand
90
                     * if the parameter is required and will build the Route query object
91
                     */
92 1
                    $param_regex .= $this->param($key, $string, $value);
93
94 1
                    if ($value->isQueryRequired($key)) {
95
                        $param_required = true;
96
                    }
97
98 1
                    $this->logger->debug("Route parser - parameter regex: $param_regex");
99
100 1
                }
101
                // Once the parameter is analyzed, the result is passed to the next iteration
102 1
                return $this->read(
103 1
                    $folders,
104 1
                    $value,
105 1
                    $regex.'(?:\/'.$param_regex.')'.(($param_required) ? '{1}' : '?')
106 1
                );
107
108
            } else {
109
                // if the element is not a json string, I assume it's the service name
110 1
                $value->addService($folder);
111
112 1
                return $this->read(
113 1
                    $folders,
114 1
                    $value,
115 1
                    $regex.'\/'.$folder
116 1
                );
117
118
            }
119
120
        }
121
122
    }
123
124
    // This method read a single parameter and build the regular expression
125 1
    private function param($key, $string, $value) {
126
127 1
        $field_required = false;
128
129
        // If the field name ends with a '*', the parameter is considered as required
130 1
        if (preg_match('/^(.+)\*$/', $key, $bits)) {
131
132 1
            $key = $bits[1];
133 1
            $field_required = true;
134
135 1
        }
136
137
        // The $value query object contains all regex which will be used by the collector to parse the route fields
138 1
        $value->setQuery($key, $string, $field_required);
139
140
        /* Every parameter can include it's own logic into the regular expression,
141
         * it can use backreferences and it's expected to be used against a single parameter.
142
         * This means that it can't be used as is to build the route regular expression,
143
         * Backreferences are not useful at this point and can make the regular expression more time consuming
144
         * and resource hungry. This is why they are replaced with the grouping parenthesis.
145
         * Eg: (value) changes in (?:value)
146
         *
147
         * Delimiting characters like '^' and '$' are also meaningless in the complete regular expression and
148
         * need to be removed. Contrariwise, wildcards must be delimited in order to keet the whole regular
149
         * expression consistent, hence a '?' is added to all the '.*' or '.+' that don't already have one.
150
         */
151 1
        $string = preg_replace("/(?<!\\\\)\\((?!\\?)/", '(?:', $string);
152 1
        $string = preg_replace("/\\.([\\*\\+])(?!\\?)/", '.${1}?', $string);
153 1
        $string = preg_replace("/^[\\^]/", '', $string);
154 1
        $string = preg_replace("/[\\$]$/", '', $string);
155
156
        /* The produced regular expression is grouped and associated with its key (this means that the 'preg_match'
157
         * function will generate an associative array where the key/value association is preserved).
158
         * If the field is required, the regular expression is completed with a '{1}' (which make it compulsory),
159
         * otherwise a '?' is added.
160
         */
161 1
        return '(?P<'.$key.'>'.$string.')'.(($field_required) ? '{1}' : '?');
162
163
    }
164
165
}
166