Completed
Push — fetcher_factories ( 951c99...ef2a05 )
by David
09:00
created

SplashDefaultRouter::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
c 8
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\Services\ParameterFetcher;
8
use Mouf\Mvc\Splash\Services\ParameterFetcherRegistry;
9
use Mouf\Mvc\Splash\Services\SplashRequestParameterFetcher;
10
use Mouf\Mvc\Splash\Services\UrlProviderInterface;
11
use Mouf\Mvc\Splash\Utils\SplashException;
12
use Psr\Cache\CacheItemPoolInterface;
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Http\Message\ServerRequestInterface;
15
use Mouf\Utils\Cache\CacheInterface;
16
use Mouf\MoufManager;
17
use Mouf\Mvc\Splash\Store\SplashUrlNode;
18
use Psr\Log\LoggerInterface;
19
use Mouf\Mvc\Splash\Controllers\WebServiceInterface;
20
use Mouf\Mvc\Splash\Services\SplashRequestContext;
21
use Mouf\Mvc\Splash\Services\SplashUtils;
22
use Psr\Log\NullLogger;
23
use Zend\Diactoros\Response\RedirectResponse;
24
use Zend\Stratigility\MiddlewareInterface;
25
26
class SplashDefaultRouter implements MiddlewareInterface
27
{
28
    /**
29
     * The container that will be used to fetch controllers.
30
     *
31
     * @var ContainerInterface
32
     */
33
    private $container;
34
35
    /**
36
     * List of objects that provide routes.
37
     *
38
     * @var UrlProviderInterface[]
39
     */
40
    private $routeProviders = [];
41
42
    /**
43
     * The logger used by Splash.
44
     *
45
     * @var LoggerInterface
46
     */
47
    private $log;
48
49
    /**
50
     * Splash uses the cache service to store the URL mapping (the mapping between a URL and its controller/action).
51
     *
52
     * @var CacheItemPoolInterface
53
     */
54
    private $cachePool;
55
56
    /**
57
     * The default mode for Splash. Can be one of 'weak' (controllers are allowed to output HTML), or 'strict' (controllers
58
     * are requested to return a ResponseInterface object).
59
     *
60
     * @var string
61
     */
62
    private $mode;
63
64
    /**
65
     * In debug mode, Splash will display more accurate messages if output starts (in strict mode)
66
     *
67
     * @var bool
68
     */
69
    private $debug;
70
71
    /**
72
     * @var ParameterFetcher[]
73
     */
74
    private $parameterFetcherRegistry;
75
76
    /**
77
     * The base URL of the application (from which the router will start routing).
78
     * @var string
79
     */
80
    private $rootUrl;
81
82
    /**
83
     * @Important
84
     *
85
     * @param ContainerInterface $container The container that will be used to fetch controllers.
86
     * @param UrlProviderInterface[] $routeProviders
87
     * @param ParameterFetcherRegistry $parameterFetcherRegistry
88
     * @param CacheItemPoolInterface $cachePool Splash uses the cache service to store the URL mapping (the mapping between a URL and its controller/action)
89
     * @param LoggerInterface $log The logger used by Splash
90
     * @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).
91
     * @param bool $debug In debug mode, Splash will display more accurate messages if output starts (in strict mode)
92
     * @param string $rootUrl
93
     */
94
    public function __construct(ContainerInterface $container, array $routeProviders, ParameterFetcherRegistry $parameterFetcherRegistry, CacheItemPoolInterface $cachePool = null, LoggerInterface $log = null, $mode = SplashUtils::MODE_STRICT, $debug = true, $rootUrl = '/')
95
    {
96
        $this->container = $container;
97
        $this->routeProviders = $routeProviders;
98
        $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...
99
        $this->cachePool = $cachePool === null ? new VoidCachePool() : $cachePool;
100
        $this->log = $log === null ? new NullLogger() : $log;
101
        $this->mode = $mode;
102
        $this->debug = $debug;
103
        $this->rootUrl = $rootUrl;
104
    }
105
106
    /**
107
     * Process an incoming request and/or response.
108
     *
109
     * Accepts a server-side request and a response instance, and does
110
     * something with them.
111
     *
112
     * If the response is not complete and/or further processing would not
113
     * interfere with the work done in the middleware, or if the middleware
114
     * wants to delegate to another process, it can use the `$out` callable
115
     * if present.
116
     *
117
     * If the middleware does not return a value, execution of the current
118
     * request is considered complete, and the response instance provided will
119
     * be considered the response to return.
120
     *
121
     * Alternately, the middleware may return a response instance.
122
     *
123
     * Often, middleware will `return $out();`, with the assumption that a
124
     * later middleware will return a response.
125
     *
126
     * @param ServerRequestInterface $request
127
     * @param ResponseInterface      $response
128
     * @param null|callable          $out
129
     *
130
     * @return null|ResponseInterface
131
     */
132
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $out = null)
133
    {
134
        $urlNodesCacheItem = $this->cachePool->getItem('splashUrlNodes');
135
        if (!$urlNodesCacheItem->isHit()) {
136
            // No value in cache, let's get the URL nodes
137
            $urlsList = $this->getSplashActionsList();
138
            $urlNodes = $this->generateUrlNode($urlsList);
139
            $urlNodesCacheItem->set($urlNodes);
140
            $this->cachePool->save($urlNodesCacheItem);
141
        }
142
143
        $urlNodes = $urlNodesCacheItem->get();
144
145
        $request_path = $request->getUri()->getPath();
146
147
        $pos = strpos($request_path, $this->rootUrl);
148
        if ($pos === false) {
149
            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.'"');
150
        }
151
152
        $tailing_url = substr($request_path, $pos + strlen($this->rootUrl));
153
154
        $context = new SplashRequestContext($request);
155
        $splashRoute = $urlNodes->walk($tailing_url, $request);
156
157
        if ($splashRoute === null) {
158
            // No route found. Let's try variants with or without trailing / if we are in a GET.
159
            if ($request->getMethod() === 'GET') {
160
                // If there is a trailing /, let's remove it and retry
161
                if (strrpos($tailing_url, '/') === strlen($tailing_url)-1) {
162
                    $url = substr($tailing_url, 0, -1);
163
                    $splashRoute = $urlNodes->walk($url, $request);
164
                } else {
165
                    $url = $tailing_url.'/';
166
                    $splashRoute = $urlNodes->walk($url, $request);
167
                }
168
                
169
                if ($splashRoute !== null) {
170
                    // If a route does match, let's make a redirect.
171
                    return new RedirectResponse($this->rootUrl.$url);
172
                }
173
            }
174
175
            $this->log->debug('Found no route for URL {url}.', [
176
                'url' => $request_path,
177
            ]);
178
179
            // No route found, let's pass control to the next middleware.
180
            return $out($request, $response);
181
        }
182
183
        $controller = $this->container->get($splashRoute->controllerInstanceName);
184
        $action = $splashRoute->methodName;
185
186
        $context->setUrlParameters($splashRoute->filledParameters);
187
188
        $this->log->debug('Routing URL {url} to controller instance {controller} and action {action}', [
189
            'url' => $request_path,
190
            'controller' => $splashRoute->controllerInstanceName,
191
            'action' => $action,
192
        ]);
193
194
        // Let's pass everything to the controller:
195
        $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...
196
197
        $filters = $splashRoute->filters;
198
199
        // Apply filters
200
        for ($i = count($filters) - 1; $i >= 0; --$i) {
201
            $filters[$i]->beforeAction();
202
        }
203
204
        $response = SplashUtils::buildControllerResponse(
205
            function () use ($controller, $action, $args) {
206
                return call_user_func_array(array($controller, $action), $args);
207
            },
208
            $this->mode,
209
            $this->debug
210
        );
211
212
        foreach ($filters as $filter) {
213
            $filter->afterAction();
214
        }
215
216
        return $response;
217
    }
218
219
    /**
220
     * Returns the list of all SplashActions.
221
     * This call is LONG and should be cached.
222
     *
223
     * @return array<SplashAction>
224
     */
225
    public function getSplashActionsList()
226
    {
227
        $urls = array();
228
229
        foreach ($this->routeProviders as $routeProvider) {
230
            /* @var $routeProvider UrlProviderInterface */
231
            $tmpUrlList = $routeProvider->getUrlsList(null);
232
            $urls = array_merge($urls, $tmpUrlList);
233
        }
234
235
        return $urls;
236
    }
237
238
    /**
239
     * Generates the URLNodes from the list of URLS.
240
     * URLNodes are a very efficient way to know whether we can access our page or not.
241
     *
242
     * @param array<SplashAction> $urlsList
243
     *
244
     * @return SplashUrlNode
245
     */
246
    private function generateUrlNode($urlsList)
247
    {
248
        $urlNode = new SplashUrlNode();
249
        foreach ($urlsList as $splashAction) {
250
            $urlNode->registerCallback($splashAction);
251
        }
252
253
        return $urlNode;
254
    }
255
256
    /**
257
     * Purges the urls cache.
258
     */
259
    public function purgeUrlsCache()
260
    {
261
        $this->cachePool->deleteItem('splashUrlNodes');
262
    }
263
}
264