Completed
Push — fetcher_factories ( ec83ea...40fc6e )
by David
08:11
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
            $urlNodesCacheItem = $this->cachePool->getItem('splashUrlNodes');
189
            if (!$urlNodesCacheItem->isHit()) {
190
                // No value in cache, let's get the URL nodes
191
                $urlsList = $this->getSplashActionsList();
192
                $urlNodes = $this->generateUrlNode($urlsList);
193
                $urlNodesCacheItem->set($urlNodes);
194
                $this->cachePool->save($urlNodesCacheItem);
195
            }
196
197
            $urlNodes = $urlNodesCacheItem->get();
198
199
            $request_path = $request->getUri()->getPath();
200
201
            $pos = strpos($request_path, $this->rootUrl);
202
            if ($pos === false) {
203
                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.'"');
204
            }
205
206
            $tailing_url = substr($request_path, $pos + strlen($this->rootUrl));
207
208
            $context = new SplashRequestContext($request);
209
            $splashRoute = $urlNodes->walk($tailing_url, $request);
210
211
            if ($splashRoute === null) {
212
                // No route found. Let's try variants with or without trailing / if we are in a GET.
213
                if ($request->getMethod() === 'GET') {
214
                    // If there is a trailing /, let's remove it and retry
215
                    if (strrpos($tailing_url, '/') === strlen($tailing_url) - 1) {
216
                        $url = substr($tailing_url, 0, -1);
217
                        $splashRoute = $urlNodes->walk($url, $request);
218
                    } else {
219
                        $url = $tailing_url.'/';
220
                        $splashRoute = $urlNodes->walk($url, $request);
221
                    }
222
223
                    if ($splashRoute !== null) {
224
                        // If a route does match, let's make a redirect.
225
                        return new RedirectResponse($this->rootUrl.$url);
226
                    }
227
                }
228
229
                $this->log->debug('Found no route for URL {url}.', [
230
                    'url' => $request_path,
231
                ]);
232
233
                // No route found, let's pass control to the next middleware.
234
                if ($out !== null) {
235
                    return $out($request, $response);
236
                } else {
237
                    throw PageNotFoundException::create($tailing_url);
238
                }
239
            }
240
241
            $controller = $this->container->get($splashRoute->controllerInstanceName);
242
            $action = $splashRoute->methodName;
243
244
            $context->setUrlParameters($splashRoute->filledParameters);
245
246
            $this->log->debug('Routing URL {url} to controller instance {controller} and action {action}', [
247
                'url' => $request_path,
248
                'controller' => $splashRoute->controllerInstanceName,
249
                'action' => $action,
250
            ]);
251
252
            // Let's pass everything to the controller:
253
            $args = $this->parameterFetcherRegistry->toArguments($context, $splashRoute->parameters);
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...
254
255
            $filters = $splashRoute->filters;
256
257
            // Apply filters
258
            for ($i = count($filters) - 1; $i >= 0; --$i) {
259
                $filters[$i]->beforeAction();
260
            }
261
262
            $response = SplashUtils::buildControllerResponse(
263
                function () use ($controller, $action, $args) {
264
                    return call_user_func_array(array($controller, $action), $args);
265
                },
266
                $this->mode,
267
                $this->debug
268
            );
269
270
            foreach ($filters as $filter) {
271
                $filter->afterAction();
272
            }
273
274
            return $response;
275
        } catch (BadRequestException $e) {
276
            if ($this->http400Handler !== null) {
277
                return $this->http400Handler->badRequest($e, $request);
278
            } else {
279
                throw $e;
280
            }
281
        } catch (PageNotFoundException $e) {
282
            if ($this->http404Handler !== null) {
283
                return $this->http404Handler->pageNotFound($request);
284
            } else {
285
                throw $e;
286
            }
287
        } catch (\Throwable $t) {
288
            if ($this->http500Handler !== null) {
289
                return $this->http500Handler->serverError($t, $request);
290
            } else {
291
                throw $t;
292
            }
293
        }
294
    }
295
296
    /**
297
     * Returns the list of all SplashActions.
298
     * This call is LONG and should be cached.
299
     *
300
     * @return array<SplashAction>
301
     */
302
    public function getSplashActionsList()
303
    {
304
        $urls = array();
305
306
        foreach ($this->routeProviders as $routeProvider) {
307
            /* @var $routeProvider UrlProviderInterface */
308
            $tmpUrlList = $routeProvider->getUrlsList(null);
309
            $urls = array_merge($urls, $tmpUrlList);
310
        }
311
312
        return $urls;
313
    }
314
315
    /**
316
     * Generates the URLNodes from the list of URLS.
317
     * URLNodes are a very efficient way to know whether we can access our page or not.
318
     *
319
     * @param array<SplashAction> $urlsList
320
     *
321
     * @return SplashUrlNode
322
     */
323
    private function generateUrlNode($urlsList)
324
    {
325
        $urlNode = new SplashUrlNode();
326
        foreach ($urlsList as $splashAction) {
327
            $urlNode->registerCallback($splashAction);
328
        }
329
330
        return $urlNode;
331
    }
332
333
    /**
334
     * Purges the urls cache.
335
     */
336
    public function purgeUrlsCache()
337
    {
338
        $this->cachePool->deleteItem('splashUrlNodes');
339
    }
340
}
341