Completed
Push — master ( 413e31...6028d7 )
by Changwan
06:35
created

Dispatcher::setRoutes()   C

Complexity

Conditions 8
Paths 16

Size

Total Lines 47
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 30
nc 16
nop 1
dl 0
loc 47
ccs 30
cts 30
cp 1
crap 8
rs 5.7377
c 0
b 0
f 0
1
<?php
2
namespace Wandu\Router;
3
4
use Closure;
5
use FastRoute\DataGenerator\GroupCountBased as GCBGenerator;
6
use FastRoute\Dispatcher as FastDispatcher;
7
use FastRoute\Dispatcher\GroupCountBased as GCBDispatcher;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Wandu\Router\Contracts\LoaderInterface;
10
use Wandu\Router\Contracts\ResponsifierInterface;
11
use Wandu\Router\Exception\MethodNotAllowedException;
12
use Wandu\Router\Exception\RouteNotFoundException;
13
use Wandu\Router\Path\Pattern;
14
15
class Dispatcher
16
{
17
    /** @var \Wandu\Router\Contracts\LoaderInterface */
18
    protected $loader;
19
20
    /** @var \Wandu\Router\Responsifier\NullResponsifier */
21
    protected $responsifier;
22
    
23
    /** @var \Wandu\Router\Configuration */
24
    protected $config;
25
    
26
    /** @var \Wandu\Router\Route[] */
27
    protected $routes = [];
28
29
    /** @var \Wandu\Router\Route[] */
30
    protected $namedPattern;
31
    
32
    /** @var array */
33
    protected $compiledRoutes = [];
34
35 22
    public function __construct(
36
        LoaderInterface $loader = null,
37
        ResponsifierInterface $responsifier = null,
38
        Configuration $config = null
39
    ) {
40 22
        $this->loader = $loader;
41 22
        $this->responsifier = $responsifier;
0 ignored issues
show
Documentation Bug introduced by
It seems like $responsifier can also be of type object<Wandu\Router\Cont...\ResponsifierInterface>. However, the property $responsifier is declared as type object<Wandu\Router\Resp...ifier\NullResponsifier>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
42 22
        $this->config = $config ?: new Configuration([]);
43 22
    }
44
45 1
    public function flush()
46
    {
47 1
        if ($this->config->isCacheEnabled()) {
48 1
            @unlink($this->config->getCacheFile());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
49
        }
50 1
    }
51
52
    /**
53
     * @deprecated use setRoutes
54
     * @param \Closure $routes
55
     * @return \Wandu\Router\Dispatcher
56
     */
57
    public function withRoutes(Closure $routes)
58
    {
59
        $inst = clone $this;
60
        $inst->setRoutes($routes);
61
        return $inst;
62
    }
63
64
    /**
65
     * @param string $name
66
     * @param array $attributes
67
     * @return string
68
     */
69 1
    public function getPath($name, array $attributes = [])
70
    {
71 1
        if (!isset($this->namedPattern[$name])) {
72 1
            throw new RouteNotFoundException("Route \"{$name}\" not found.");
73
        }
74 1
        $pattern = new Pattern($this->namedPattern[$name]);
0 ignored issues
show
Documentation introduced by
$this->namedPattern[$name] is of type object<Wandu\Router\Route>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
75 1
        return $pattern->path($attributes);
76
    }
77
78
    /**
79
     * @param \Closure $routes
80
     */
81 22
    public function setRoutes(Closure $routes)
82
    {
83 22
        $cacheEnabled = $this->config->isCacheEnabled();
84 22
        if ($this->isCached()) {
85 2
            $cache = $this->restoreCache();
86 2
            $this->compiledRoutes = $cache['dispatch_data'];
87 2
            $this->routes = $cache['routes'];
88 2
            $this->namedPattern = isset($cache['named_routes']) ? $cache['named_routes'] : [];
89
        } else {
90 22
            $resultRoutes = [];
91 22
            $resultNamedPath = [];
92
            
93 22
            $router = new Router;
94 22
            $router->middleware($this->config->getMiddleware(), $routes);
95
            
96 22
            $generator = new GCBGenerator();
97
            /**
98
             * @var array|string[] $methods
99
             * @var string $path
100
             * @var \Wandu\Router\Route $route
101
             */
102 22
            foreach ($router as list($methods, $path, $route)) {
103 22
                $pathPattern = new Pattern($path);
104 22
                if ($routeName = $route->getName()) {
105 1
                    $resultNamedPath[$routeName] = $path;
106
                }
107 22
                foreach ($pathPattern->parse() as $parsedPath) {
108 22
                    foreach ($methods as $method) {
109 22
                        $handleId = uniqid('HANDLER');
110 22
                        $generator->addRoute($method, $parsedPath, $handleId);
111 22
                        $resultRoutes[$handleId] = $route;
112
                    }
113
                }
114
            }
115 22
            $this->routes = $resultRoutes;
116 22
            $this->namedPattern = $resultNamedPath;
117 22
            $this->compiledRoutes = $generator->getData();
118
            
119 22
            if ($cacheEnabled) {
120 2
                $this->storeCache([
121 2
                    'dispatch_data' => $this->compiledRoutes,
122 2
                    'routes' => $this->routes,
123 2
                    'named_routes' => $this->namedPattern,
124
                ]);
125
            }
126
        }
127 22
    }
128
129
    /**
130
     * @param \Psr\Http\Message\ServerRequestInterface $request
131
     * @return \Psr\Http\Message\ResponseInterface
132
     */
133 18
    public function dispatch(ServerRequestInterface $request)
134
    {
135 18
        $request = $this->applyVirtualMethod($request);
136 18
        $routeInfo = $this->runDispatcher(
137 18
            new GCBDispatcher($this->compiledRoutes),
138 18
            $request->getMethod(),
139 18
            $request->getUri()->getPath()
140
        );
141 17
        foreach ($routeInfo[2] as $key => $value) {
142 4
            $request = $request->withAttribute($key, $value);
143
        }
144 17
        return $this->routes[$routeInfo[1]]->execute($request, $this->loader, $this->responsifier);
145
    }
146
147
    /**
148
     * @param \Psr\Http\Message\ServerRequestInterface $request
149
     * @return \Psr\Http\Message\ServerRequestInterface
150
     */
151 18
    protected function applyVirtualMethod(ServerRequestInterface $request)
152
    {
153 18
        if (!$this->config->isVirtualMethodEnabled()) {
154 16
            return $request;
155
        }
156 2
        $parsedBody = $request->getParsedBody();
157 2
        if (isset($parsedBody['_method'])) {
158 1
            return $request->withMethod(strtoupper($parsedBody['_method']));
159
        }
160 1
        if ($request->hasHeader('X-Http-Method-Override')) {
161 1
            return $request->withMethod(strtoupper($request->getHeaderLine('X-Http-Method-Override')));
162
        }
163
        return $request;
164
    }
165
166
    /**
167
     * @param \FastRoute\Dispatcher $dispatcher
168
     * @param string $method
169
     * @param string $path
170
     * @return array
171
     */
172 18
    protected function runDispatcher(FastDispatcher $dispatcher, $method, $path)
173
    {
174 18
        $routeInfo = $dispatcher->dispatch($method, $path);
175 18
        switch ($routeInfo[0]) {
176 18
            case FastDispatcher::NOT_FOUND:
177
                throw new RouteNotFoundException();
178 18
            case FastDispatcher::METHOD_NOT_ALLOWED:
179 8
                throw new MethodNotAllowedException();
180 17
            case FastDispatcher::FOUND:
181 17
                return $routeInfo;
182
        }
183
    }
184
185
    /**
186
     * @return bool
187
     */
188 22
    public function isCached(): bool
189
    {
190 22
        $cacheEnabled = $this->config->isCacheEnabled();
191 22
        $cacheFile = $this->config->getCacheFile();
192 22
        return $cacheEnabled && file_exists($cacheFile);
193
    }
194
    
195
    /**
196
     * @param array $attributes
197
     */
198 2
    private function storeCache(array $attributes = [])
199
    {
200 2
        file_put_contents($this->config->getCacheFile(), '<?php return ' . var_export($attributes, true) .';');
201 2
    }
202
203
    /**
204
     * @return array
205
     */
206 2
    private function restoreCache()
207
    {
208 2
        return require $this->config->getCacheFile();
209
    }
210
}
211