Completed
Push — fetcher_factories ( ec83ea...40fc6e )
by David
08:11
created

SplashDefaultRouter::getSplashActionsList()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 2
eloc 6
nc 2
nop 0
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