Completed
Push — fetcher_factories ( 10a56f...fd93ea )
by David
13:44
created

SplashDefaultRouter::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 9
Bugs 0 Features 0
Metric Value
c 9
b 0
f 0
dl 0
loc 11
rs 9.4285
cc 3
eloc 9
nc 4
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Mouf\Mvc\Splash\Routers;
4
5
use Cache\Adapter\Void\VoidCachePool;
6
use Interop\Container\ContainerInterface;
7
use Mouf\Mvc\Splash\Controllers\Http400HandlerInterface;
8
use Mouf\Mvc\Splash\Controllers\Http404HandlerInterface;
9
use Mouf\Mvc\Splash\Controllers\Http500HandlerInterface;
10
use Mouf\Mvc\Splash\Exception\BadRequestException;
11
use Mouf\Mvc\Splash\Exception\PageNotFoundException;
12
use Mouf\Mvc\Splash\Services\ParameterFetcher;
13
use Mouf\Mvc\Splash\Services\ParameterFetcherRegistry;
14
use Mouf\Mvc\Splash\Services\UrlProviderInterface;
15
use Mouf\Mvc\Splash\Utils\SplashException;
16
use Psr\Cache\CacheItemPoolInterface;
17
use Psr\Http\Message\ResponseInterface;
18
use Psr\Http\Message\ServerRequestInterface;
19
use Mouf\Mvc\Splash\Store\SplashUrlNode;
20
use Psr\Log\LoggerInterface;
21
use Mouf\Mvc\Splash\Services\SplashRequestContext;
22
use Mouf\Mvc\Splash\Services\SplashUtils;
23
use Psr\Log\NullLogger;
24
use Zend\Diactoros\Response\RedirectResponse;
25
use Zend\Stratigility\MiddlewareInterface;
26
27
class SplashDefaultRouter implements MiddlewareInterface
28
{
29
    /**
30
     * The container that will be used to fetch controllers.
31
     *
32
     * @var ContainerInterface
33
     */
34
    private $container;
35
36
    /**
37
     * List of objects that provide routes.
38
     *
39
     * @var UrlProviderInterface[]
40
     */
41
    private $routeProviders = [];
42
43
    /**
44
     * The logger used by Splash.
45
     *
46
     * @var LoggerInterface
47
     */
48
    private $log;
49
50
    /**
51
     * Splash uses the cache service to store the URL mapping (the mapping between a URL and its controller/action).
52
     *
53
     * @var CacheItemPoolInterface
54
     */
55
    private $cachePool;
56
57
    /**
58
     * The default mode for Splash. Can be one of 'weak' (controllers are allowed to output HTML), or 'strict' (controllers
59
     * are requested to return a ResponseInterface object).
60
     *
61
     * @var string
62
     */
63
    private $mode;
64
65
    /**
66
     * In debug mode, Splash will display more accurate messages if output starts (in strict mode).
67
     *
68
     * @var bool
69
     */
70
    private $debug;
71
72
    /**
73
     * @var ParameterFetcher[]
74
     */
75
    private $parameterFetcherRegistry;
76
77
    /**
78
     * The base URL of the application (from which the router will start routing).
79
     *
80
     * @var string
81
     */
82
    private $rootUrl;
83
84
    /**
85
     * (optional) Handles HTTP 400 status code.
86
     *
87
     * @var Http400HandlerInterface
88
     */
89
    private $http400Handler;
90
91
    /**
92
     * (optional) Handles HTTP 404 status code (if no $out provided).
93
     *
94
     * @var Http404HandlerInterface
95
     */
96
    private $http404Handler;
97
98
    /**
99
     * (optional) Handles HTTP 500 status code.
100
     *
101
     * @var Http500HandlerInterface
102
     */
103
    private $http500Handler;
104
105
    /**
106
     * @Important
107
     *
108
     * @param ContainerInterface       $container                The container that will be used to fetch controllers.
109
     * @param UrlProviderInterface[]   $routeProviders
110
     * @param ParameterFetcherRegistry $parameterFetcherRegistry
111
     * @param CacheItemPoolInterface   $cachePool                Splash uses the cache service to store the URL mapping (the mapping between a URL and its controller/action)
112
     * @param LoggerInterface          $log                      The logger used by Splash
113
     * @param string                   $mode                     The default mode for Splash. Can be one of 'weak' (controllers are allowed to output HTML), or 'strict' (controllers are requested to return a ResponseInterface object).
114
     * @param bool                     $debug                    In debug mode, Splash will display more accurate messages if output starts (in strict mode)
115
     * @param string                   $rootUrl
116
     */
117
    public function __construct(ContainerInterface $container, array $routeProviders, ParameterFetcherRegistry $parameterFetcherRegistry, CacheItemPoolInterface $cachePool = null, LoggerInterface $log = null, $mode = SplashUtils::MODE_STRICT, $debug = true, $rootUrl = '/')
118
    {
119
        $this->container = $container;
120
        $this->routeProviders = $routeProviders;
121
        $this->parameterFetcherRegistry = $parameterFetcherRegistry;
0 ignored issues
show
Documentation Bug introduced by
It seems like $parameterFetcherRegistry of type object<Mouf\Mvc\Splash\S...rameterFetcherRegistry> is incompatible with the declared type array<integer,object<Mou...ices\ParameterFetcher>> of property $parameterFetcherRegistry.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
122
        $this->cachePool = $cachePool === null ? new VoidCachePool() : $cachePool;
123
        $this->log = $log === null ? new NullLogger() : $log;
124
        $this->mode = $mode;
125
        $this->debug = $debug;
126
        $this->rootUrl = $rootUrl;
127
    }
128
129
    /**
130
     * @param Http400HandlerInterface $http400Handler
131
     */
132
    public function setHttp400Handler($http400Handler)
133
    {
134
        $this->http400Handler = $http400Handler;
135
136
        return $this;
137
    }
138
139
    /**
140
     * @param Http404HandlerInterface $http404Handler
141
     */
142
    public function setHttp404Handler($http404Handler)
143
    {
144
        $this->http404Handler = $http404Handler;
145
146
        return $this;
147
    }
148
149
    /**
150
     * @param Http500HandlerInterface $http500Handler
151
     */
152
    public function setHttp500Handler($http500Handler)
153
    {
154
        $this->http500Handler = $http500Handler;
155
156
        return $this;
157
    }
158
159
    /**
160
     * Process an incoming request and/or response.
161
     *
162
     * Accepts a server-side request and a response instance, and does
163
     * something with them.
164
     *
165
     * If the response is not complete and/or further processing would not
166
     * interfere with the work done in the middleware, or if the middleware
167
     * wants to delegate to another process, it can use the `$out` callable
168
     * if present.
169
     *
170
     * If the middleware does not return a value, execution of the current
171
     * request is considered complete, and the response instance provided will
172
     * be considered the response to return.
173
     *
174
     * Alternately, the middleware may return a response instance.
175
     *
176
     * Often, middleware will `return $out();`, with the assumption that a
177
     * later middleware will return a response.
178
     *
179
     * @param ServerRequestInterface $request
180
     * @param ResponseInterface      $response
181
     * @param null|callable          $out
182
     *
183
     * @return null|ResponseInterface
184
     */
185
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $out = null)
186
    {
187
        try {
188
            $this->purgeExpiredRoutes();
189
190
            $urlNodesCacheItem = $this->cachePool->getItem('splashUrlNodes');
191
            if (!$urlNodesCacheItem->isHit()) {
192
                // No value in cache, let's get the URL nodes
193
                $urlsList = $this->getSplashActionsList();
194
                $urlNodes = $this->generateUrlNode($urlsList);
195
                $urlNodesCacheItem->set($urlNodes);
196
                $this->cachePool->save($urlNodesCacheItem);
197
            }
198
199
            $urlNodes = $urlNodesCacheItem->get();
200
            /* @var $urlNodes SplashUrlNode */
201
202
            $request_path = $request->getUri()->getPath();
203
204
            $pos = strpos($request_path, $this->rootUrl);
205
            if ($pos === false) {
206
                throw new SplashException('Error: the prefix of the web application "'.$this->rootUrl.'" was not found in the URL. The application must be misconfigured. Check the ROOT_URL parameter in your config.php file at the root of your project. It should have the same value as the RewriteBase parameter in your .htaccess file. Requested URL : "'.$request_path.'"');
207
            }
208
209
            $tailing_url = substr($request_path, $pos + strlen($this->rootUrl));
210
211
            $splashRoute = $urlNodes->walk($tailing_url, $request);
212
213
            if ($splashRoute === null) {
214
                // No route found. Let's try variants with or without trailing / if we are in a GET.
215
                if ($request->getMethod() === 'GET') {
216
                    // If there is a trailing /, let's remove it and retry
217
                    if (strrpos($tailing_url, '/') === strlen($tailing_url) - 1) {
218
                        $url = substr($tailing_url, 0, -1);
219
                        $splashRoute = $urlNodes->walk($url, $request);
220
                    } else {
221
                        $url = $tailing_url.'/';
222
                        $splashRoute = $urlNodes->walk($url, $request);
223
                    }
224
225
                    if ($splashRoute !== null) {
226
                        // If a route does match, let's make a redirect.
227
                        return new RedirectResponse($this->rootUrl.$url);
228
                    }
229
                }
230
231
                $this->log->debug('Found no route for URL {url}.', [
232
                    'url' => $request_path,
233
                ]);
234
235
                // No route found, let's pass control to the next middleware.
236
                if ($out !== null) {
237
                    return $out($request, $response);
238
                } else {
239
                    throw PageNotFoundException::create($tailing_url);
240
                }
241
            }
242
243
            // Is the route still valid according to the cache?
244
            if (!$splashRoute->isCacheValid()) {
245
                // The route is invalid! Let's purge the cache and retry!
246
                $this->purgeUrlsCache();
247
                return $this($request, $response, $out);
248
            }
249
250
251
            $controller = $this->container->get($splashRoute->getControllerInstanceName());
252
            $action = $splashRoute->getMethodName();
253
254
            $this->log->debug('Routing URL {url} to controller instance {controller} and action {action}', [
255
                'url' => $request_path,
256
                'controller' => $splashRoute->getControllerInstanceName(),
257
                'action' => $action,
258
            ]);
259
260
261
            $filters = $splashRoute->getFilters();
262
263
264
265
            $middlewareCaller = function(ServerRequestInterface $request, ResponseInterface $response) use ($controller, $action, $splashRoute, $splashRoute) {
0 ignored issues
show
Unused Code introduced by
The parameter $response is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
266
                // Let's recreate a new context object (because request can be modified by the filters)
267
                $context = new SplashRequestContext($request);
268
                $context->setUrlParameters($splashRoute->getFilledParameters());
269
                // Let's pass everything to the controller:
270
                $args = $this->parameterFetcherRegistry->toArguments($context, $splashRoute->getParameters());
0 ignored issues
show
Bug introduced by
The method toArguments cannot be called on $this->parameterFetcherRegistry (of type array<integer,object<Mou...ices\ParameterFetcher>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
271
272
273
274
                $response = SplashUtils::buildControllerResponse(
275
                    function () use ($controller, $action, $args) {
276
                        return $controller->$action(...$args);
277
                    },
278
                    $this->mode,
279
                    $this->debug
280
                );
281
282
                return $response;
283
            };
284
285
            // Apply filters
286
            for ($i = count($filters) - 1; $i >= 0; --$i) {
287
                $filter = $filters[$i];
288
                $middlewareCaller = function(ServerRequestInterface $request, ResponseInterface $response) use ($middlewareCaller, $filter) {
289
                    return $filter($request, $response, $middlewareCaller, $this->container);
290
                };
291
            }
292
293
294
            $response = $middlewareCaller($request, $response);
295
296
            return $response;
297
        } catch (BadRequestException $e) {
298
            if ($this->http400Handler !== null) {
299
                return $this->http400Handler->badRequest($e, $request);
300
            } else {
301
                throw $e;
302
            }
303
        } catch (PageNotFoundException $e) {
304
            if ($this->http404Handler !== null) {
305
                return $this->http404Handler->pageNotFound($request);
306
            } else {
307
                throw $e;
308
            }
309
        } catch (\Throwable $t) {
310
            if ($this->http500Handler !== null) {
311
                return $this->http500Handler->serverError($t, $request);
312
            } else {
313
                throw $t;
314
            }
315
        }
316
    }
317
318
    /**
319
     * Purges the cache if one of the url providers tells us to.
320
     */
321
    private function purgeExpiredRoutes()
322
    {
323
        $expireTag = '';
324
        foreach ($this->routeProviders as $routeProvider) {
325
            /* @var $routeProvider UrlProviderInterface */
326
            $expireTag .= $routeProvider->getExpirationTag();
327
        }
328
329
        $value = md5($expireTag);
330
331
        $urlNodesCacheItem = $this->cachePool->getItem('splashExpireTag');
332
333
        if ($urlNodesCacheItem->isHit() && $urlNodesCacheItem->get() === $value) {
334
            return;
335
        }
336
337
        $this->purgeUrlsCache();
338
339
        $urlNodesCacheItem->set($value);
340
        $this->cachePool->save($urlNodesCacheItem);
341
    }
342
343
344
    /**
345
     * Returns the list of all SplashActions.
346
     * This call is LONG and should be cached.
347
     *
348
     * @return array<SplashAction>
349
     */
350
    public function getSplashActionsList()
351
    {
352
        $urls = array();
353
354
        foreach ($this->routeProviders as $routeProvider) {
355
            /* @var $routeProvider UrlProviderInterface */
356
            $tmpUrlList = $routeProvider->getUrlsList(null);
357
            $urls = array_merge($urls, $tmpUrlList);
358
        }
359
360
        return $urls;
361
    }
362
363
    /**
364
     * Generates the URLNodes from the list of URLS.
365
     * URLNodes are a very efficient way to know whether we can access our page or not.
366
     *
367
     * @param array<SplashAction> $urlsList
368
     *
369
     * @return SplashUrlNode
370
     */
371
    private function generateUrlNode($urlsList)
372
    {
373
        $urlNode = new SplashUrlNode();
374
        foreach ($urlsList as $splashAction) {
375
            $urlNode->registerCallback($splashAction);
376
        }
377
378
        return $urlNode;
379
    }
380
381
    /**
382
     * Purges the urls cache.
383
     */
384
    public function purgeUrlsCache()
385
    {
386
        $this->cachePool->deleteItem('splashUrlNodes');
387
    }
388
}
389