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

SplashDefaultRouter::purgeUrlsCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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\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