Completed
Push — 4.0 ( 0161d2...25fc97 )
by Marco
12:04
created

Collector::loadRoutes()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 3
eloc 5
nc 2
nop 1
1
<?php namespace Comodojo\Dispatcher\Router;
2
3
use \Comodojo\Dispatcher\Components\Model as DispatcherClassModel;
4
use \Comodojo\Dispatcher\Router\RoutingTable;
5
use \Comodojo\Dispatcher\Components\Timestamp as TimestampTrait;
6
use \Comodojo\Dispatcher\Request\Model as Request;
7
use \Comodojo\Dispatcher\Response\Model as Response;
8
use \Comodojo\Dispatcher\Extra\Model as Extra;
9
use \Comodojo\Dispatcher\Components\Configuration;
10
use \Comodojo\Cache\CacheManager;
11
use \Monolog\Logger;
12
use \Comodojo\Exception\DispatcherException;
13
use \Exception;
14
15
/**
16
 * @package     Comodojo Dispatcher
17
 * @author      Marco Giovinazzi <[email protected]>
18
 * @author      Marco Castiello <[email protected]>
19
 * @license     GPL-3.0+
20
 *
21
 * LICENSE:
22
 *
23
 * This program is free software: you can redistribute it and/or modify
24
 * it under the terms of the GNU Affero General Public License as
25
 * published by the Free Software Foundation, either version 3 of the
26
 * License, or (at your option) any later version.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31
 * GNU Affero General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Affero General Public License
34
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
35
 */
36
37
class Collector extends DispatcherClassModel {
38
39
    use TimestampTrait;
40
41
    private $bypass = false;
42
43
    private $classname = "";
44
45
    private $type = "";
46
47
    private $service = "";
48
49
    private $cache;
50
51
    private $request;
52
53
    private $response;
54
55
    private $table;
56
57
    public function __construct(
58
        Configuration $configuration,
59
        Logger $logger,
60
        CacheManager $cache,
61
        Extra $extra = null
62
    ) {
63
64
        parent::__construct($configuration, $logger);
65
66
        $this->table = new RoutingTable($logger);
67
68
        $this->cache = $cache;
69
70
        $this->extra = $extra;
0 ignored issues
show
Bug introduced by
The property extra does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
71
72
        $this->setTimestamp();
73
74
    }
75
76
    public function getType() {
77
78
        return $this->type;
79
80
    }
81
82
    public function getService() {
83
84
        return $this->service;
85
86
    }
87
88
    public function getParameters() {
89
90
        return $this->parameters;
0 ignored issues
show
Bug introduced by
The property parameters does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
91
92
    }
93
94
    public function getClassName() {
95
96
        return $this->classname;
97
98
    }
99
100
    public function getInstance() {
101
102
        $class = $this->classname;
103
104
        if (class_exists($class)) {
105
106
            return new $class(
107
                $this->configuration,
108
                $this->logger,
109
                $this->request,
110
                $this,
111
                $this->response,
112
                $this->extra
113
            );
114
115
        }
116
        else return null;
117
118
    }
119
120
    public function add($route, $type, $class, $parameters = array()) {
121
122
        $routeData = $this->get($route);
123
124
        if (is_null($routeData)) {
125
126
            $this->table->put($route, $type, $class, $parameters);
127
128
        } else {
129
130
            $this->table->set($route, $type, $class, $parameters);
131
132
        }
133
134
    }
135
136
    public function get($route) {
137
138
        return $this->table->get($route);
139
140
    }
141
142
    public function remove($route) {
143
144
        return $this->table->remove($route);
145
146
    }
147
148
    public function bypass($mode = true) {
149
150
        $this->bypass = filter_var($mode, FILTER_VALIDATE_BOOLEAN);
151
152
        return $this;
153
154
    }
155
156
    public function route(Request $request) {
157
158
        $method = $request->method()->get();
159
160
        $methods = $this->configuration->get('allowed-http-methods');
161
162
        if ( ( $methods != null || !empty($methods) ) && in_array($method, $methods) === false ) {
163
164
            throw new DispatcherException("Method not allowed", 0, null, 405, array(
0 ignored issues
show
Unused Code introduced by
The call to DispatcherException::__construct() has too many arguments starting with array('Allow' => implode(',', $methods)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
165
                "Allow" => implode(",",$methods)
166
            ));
167
168
        }
169
170
        $this->request = $request;
171
172
        if (!$this->parse()) {
173
174
            throw new DispatcherException("Unable to find a valid route for the specified uri", 0, null, 404);
175
176
        }
177
178
    }
179
180
    public function compose(Response $response) {
181
182
        $this->response = $response;
183
184
        $service = $this->getInstance();
185
186
        if (!is_null($service)) {
187
188
            $result;
0 ignored issues
show
Bug introduced by
The variable $result seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
189
190
            $method = $this->request->method()->get();
191
192
            $methods = $service->getImplementedMethods();
193
194
            if ( in_array($method, $methods) ) {
195
196
                $callable = $service->getMethod($method);
197
198
                try {
199
200
                    $result = call_user_func(array($service, $callable));
201
202
                } catch (DispatcherException $de) {
203
204
                    throw new DispatcherException(sprintf("Service '%s' exception for method '%s': %s", $this->service, $method, $de->getMessage()), 0, $de, 500);
205
206
                } catch (Exception $e) {
207
208
                    throw new DispatcherException(sprintf("Service '%s' execution failed for method '%s': %s", $this->service, $method, $e->getMessage()), 0, $e, 500);
209
210
                }
211
212
            } else {
213
214
                throw new DispatcherException(sprintf("Service '%s' doesn't implement method '%s'", $this->service, $method), 0, null, 501, array(
0 ignored issues
show
Unused Code introduced by
The call to DispatcherException::__construct() has too many arguments starting with array('Allow' => implode(',', $methods)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
215
                    "Allow" => implode(",", $methods)
216
                ));
217
218
            }
219
220
            $this->response->content()->set($result);
221
222
        } else {
223
224
            throw new DispatcherException(sprintf("Unable to execute service '%s'", $this->service), 0, null, 500);
225
226
        }
227
228
    }
229
230
    public function loadRoutes($routes) {
231
232
        if ( !empty($routes) ) {
233
234
            foreach( $routes as $name => $route ) {
235
236
                $this->add($route['route'], $route['type'], $route['class'], $route['parameters']);
237
238
            }
239
240
        }
241
242
        return $this->dumpCache();
243
244
    }
245
    
246
    public function loadFromCache() {
247
        
248
        $routes = $this->cache->get("dispatcher_routes");
249
        
250
        if (is_null($routes)) return null;
251
        
252
        $this->table->routes($routes);
253
        
254
        return $this;
255
        
256
    }
257
    
258
    private function dumpCache() {
259
        
260
        $routes = $this->table->routes();
261
        
262
        $this->cache->set("dispatcher_routes", $routes, 24 * 60 * 60);
263
        
264
        return $this;
265
        
266
    }
267
268
    private function parse() {
269
270
        $path = $this->request->route();
271
        
272
        foreach ($this->table->routes() as $regex => $value) {
0 ignored issues
show
Bug introduced by
The expression $this->table->routes() of type array|object<Comodojo\Di...er\Router\RoutingTable> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
273
            
274
            // The current uri is checked against all the global regular expressions associated with the routes
275
            if (preg_match("/" . $regex . "/", $path, $matches)) {
276
277
                /* If a route is matched, all the bits of the route string are evalued in order to create
278
                 * new query parameters which will be available for the service class
279
                 */
280
                $this->evalUri($value['query'], $matches);
281
282
                // All the route parameters are also added to the query parameters
283
                foreach ($value['parameters'] as $parameter => $value) {
284
285
                    $this->request->query()->set($parameter, $value);
286
287
                }
288
289
                $this->classname  = $value['class'];
290
                $this->type       = $value['type'];
291
                $this->service    = implode('.', $value['service']);
292
                $this->service    = empty($this->service)?"default":$this->service;
293
294
                return true;
295
296
            }
297
298
        }
299
300
        return false;
301
302
    }
303
304
    private function evalUri($parameters, $bits) {
305
306
        $count  = 0;
0 ignored issues
show
Unused Code introduced by
$count 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...
307
308
        // Because of the nature of the global regular expression, all the bits of the matched route are associated with a parameter key
309
        foreach ($parameters as $key => $value) {
310
311
            if (isset($bits[$key])) {
312
                /* if it's available a bit associated with the parameter name, it is compared against
313
                 * it's regular expression in order to extrect backreferences
314
                 */
315
                if (preg_match('/^' . $value['regex'] . '$/', $bits[$key], $matches)) {
316
                    
317
                    if (count($matches) == 1) $matches = $matches[0]; // This is the case where no backreferences are present or available.
318
                    
319
                    // The extracted value (with any backreference available) is added to the query parameters.
320
                    $this->request->query()->set($key, $matches);
321
322
                }
323
324
            } elseif ($value['required']) {
325
326
                throw new DispatcherException(sprintf("Required parameter '%s' not specified.", $key), 1, null, 500);
327
328
            }
329
330
        }
331
332
    }
333
334
}
335