Completed
Push — master ( 7e0874...14a89c )
by Vladimir
04:01 queued 01:49
created

RouteDispatcher::normalizeUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
/**
4
 * @copyright 2018 Vladimir Jimenez
5
 * @license   https://github.com/stakx-io/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx\Server;
9
10
use allejo\stakx\Compiler;
11
use allejo\stakx\Document\BasePageView;
12
use allejo\stakx\Document\CollectableItem;
13
use allejo\stakx\Document\DynamicPageView;
14
use allejo\stakx\Document\ReadableDocument;
15
use allejo\stakx\Document\RepeaterPageView;
16
use allejo\stakx\Document\StaticPageView;
17
use allejo\stakx\Document\TemplateReadyDocument;
18
use allejo\stakx\Filesystem\FilesystemLoader as fs;
19
use allejo\stakx\FrontMatter\ExpandedValue;
20
use allejo\stakx\Service;
21
use FastRoute\RouteCollector;
22
use Psr\Http\Message\ServerRequestInterface;
23
use React\Http\Response;
24
25
class RouteDispatcher
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
26
{
27
    private static $baseUrl = '';
28
    private $lastModified = [];
29
30
    /**
31
     * @internal
32
     */
33
    private function __construct()
34
    {
35
    }
36
37
    /**
38
     * Build a controller for handling a Static PageView's URL.
39
     *
40
     * @param StaticPageView $pageView
41
     * @param Compiler       $compiler
42
     *
43
     * @return \Closure
44
     */
45
    private function staticPageViewController(StaticPageView $pageView, Compiler $compiler)
46
    {
47
        return function () use ($pageView, $compiler) {
48
            Service::setOption('currentTemplate', $pageView->getAbsoluteFilePath());
49
50
            if ($this->hasBeenTouched($pageView))
51
            {
52
                $pageView->readContent();
53
            }
54
55
            $mimeType = MimeDetector::getMimeType(fs::getExtension($pageView->getTargetFile()));
0 ignored issues
show
Documentation introduced by
$pageView->getTargetFile() is of type string, but the function expects a object<string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
56
57
            return new Response(
58
                200,
59
                ['Content-Type' => $mimeType],
60
                $compiler->renderStaticPageView($pageView)
61
            );
62
        };
63
    }
64
65
    /**
66
     * Build a controller for handling a Dynamic PageView's URL.
67
     *
68
     * @param DynamicPageView $pageView
69
     * @param Compiler        $compiler
70
     *
71
     * @return \Closure
72
     */
73
    private function dynamicPageViewController(DynamicPageView $pageView, Compiler $compiler)
74
    {
75
        return function (ServerRequestInterface $request) use ($pageView, $compiler) {
76
            Service::setOption('currentTemplate', $pageView->getAbsoluteFilePath());
77
78
            $contentItem = self::getContentItem($pageView, $request->getUri()->getPath());
79
80
            if ($contentItem === null)
81
            {
82
                return DevServer::return404();
83
            }
84
85
            if ($this->hasBeenTouched($pageView))
86
            {
87
                $pageView->readContent();
88
            }
89
90
            if ($this->hasBeenTouched($contentItem))
0 ignored issues
show
Documentation introduced by
$contentItem is of type object<allejo\stakx\Document\CollectableItem>, but the function expects a object<allejo\stakx\Document\ReadableDocument>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
91
            {
92
                $contentItem->readContent();
93
            }
94
95
            return DevServer::return200($compiler->renderDynamicPageView($pageView, $contentItem));
0 ignored issues
show
Documentation introduced by
$contentItem is of type object<allejo\stakx\Document\CollectableItem>, but the function expects a object<allejo\stakx\Docu...\TemplateReadyDocument>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
96
        };
97
    }
98
99
    /**
100
     * Build a controller for handling a Repeater PageView's URL.
101
     *
102
     * @param RepeaterPageView $pageView
103
     * @param Compiler         $compiler
104
     *
105
     * @return \Closure
106
     */
107
    private function repeaterPageViewController(RepeaterPageView $pageView, Compiler $compiler)
108
    {
109
        return function (ServerRequestInterface $request) use ($pageView, $compiler) {
110
            Service::setOption('currentTemplate', $pageView->getAbsoluteFilePath());
111
112
            $expandedValue = self::getExpandedValue($pageView, $request->getUri()->getPath());
113
114
            if ($expandedValue === null)
115
            {
116
                return DevServer::return404();
117
            }
118
119
            if ($this->hasBeenTouched($pageView))
120
            {
121
                $pageView->readContent();
122
            }
123
124
            return DevServer::return200($compiler->renderRepeaterPageView($pageView, $expandedValue));
125
        };
126
    }
127
128
    /**
129
     * Return the appropriate controller based on a PageView's type.
130
     *
131
     * @param BasePageView|DynamicPageView|RepeaterPageView|StaticPageView $pageView
132
     * @param Compiler                                                     $compiler
133
     *
134
     * @return \Closure
135
     */
136
    private function createController(BasePageView $pageView, Compiler $compiler)
137
    {
138
        switch ($pageView->getType())
139
        {
140
            case BasePageView::STATIC_TYPE:
141
                return $this->staticPageViewController($pageView, $compiler);
0 ignored issues
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Document\BasePageView> is not a sub-type of object<allejo\stakx\Document\StaticPageView>. It seems like you assume a child class of the class allejo\stakx\Document\BasePageView to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
142
143
            case BasePageView::DYNAMIC_TYPE:
144
                return $this->dynamicPageViewController($pageView, $compiler);
0 ignored issues
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Document\BasePageView> is not a sub-type of object<allejo\stakx\Document\DynamicPageView>. It seems like you assume a child class of the class allejo\stakx\Document\BasePageView to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
145
146
            case BasePageView::REPEATER_TYPE:
147
                return $this->repeaterPageViewController($pageView, $compiler);
0 ignored issues
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Document\BasePageView> is not a sub-type of object<allejo\stakx\Document\RepeaterPageView>. It seems like you assume a child class of the class allejo\stakx\Document\BasePageView to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
148
149
            default:
150
                return function () {
151
                    $errMsg = 'This URL type has not yet been implemented.';
152
153
                    return new Response(501, ['Content-Type' => 'text/plain'], $errMsg);
154
                };
155
        }
156
    }
157
158
    /**
159
     * Check to see if a file has been touched since we last read it.
160
     *
161
     * @param ReadableDocument $document
162
     *
163
     * @return bool True if the file has been modified since it was last accessed
164
     */
165
    private function hasBeenTouched(ReadableDocument $document)
166
    {
167
        $rPath = $document->getRelativeFilePath();
168
169
        if (!isset($this->lastModified[$rPath]))
170
        {
171
            $this->lastModified[$rPath] = $document->getLastModified();
172
173
            return true;
174
        }
175
176
        return $document->getLastModified() > $this->lastModified[$rPath];
177
    }
178
179
    /**
180
     * Create a FastRoute Dispatcher.
181
     *
182
     * @param PageViewRouter $router
183
     * @param Compiler       $compiler
184
     *
185
     * @return \FastRoute\Dispatcher
186
     */
187
    public static function create(PageViewRouter $router, Compiler $compiler)
188
    {
189
        self::$baseUrl = $router->getBaseUrl();
190
191
        return \FastRoute\simpleDispatcher(function (RouteCollector $r) use ($router, $compiler) {
192
            $dispatcher = new RouteDispatcher();
193
194
            foreach ($router->getRouteMapping() as $route => $pageView)
195
            {
196
                $r->get($route, $dispatcher->createController($pageView, $compiler));
197
            }
198
        });
199
    }
200
201
    /**
202
     * Normalize a given URL.
203
     *
204
     * A normalized URL is one with `baseurl` stripped away from it. This is necessary because all permalinks in stakx
205
     * are handled without the base so it's necessary to be able to reference correct correct permalinks.
206
     *
207
     * @param string $url
208
     *
209
     * @return mixed
210
     */
211
    public static function normalizeUrl($url)
212
    {
213
        return str_replace(self::$baseUrl, '/', $url);
214
    }
215
216
    /**
217
     * Find a ContentItem from a Dynamic PageView or null if it doesn't exist.
218
     *
219
     * @param DynamicPageView $pageView
220
     * @param                 $permalink
221
     *
222
     * @return CollectableItem|ReadableDocument|TemplateReadyDocument|null
223
     */
224
    private static function getContentItem(DynamicPageView $pageView, $permalink)
225
    {
226
        $permalink = self::normalizeUrl($permalink);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $permalink. This often makes code more readable.
Loading history...
227
228
        foreach ($pageView->getCollectableItems() as $collectableItem)
229
        {
230
            if ($collectableItem['permalink'] === $permalink)
231
            {
232
                return $collectableItem;
233
            }
234
        }
235
236
        return null;
237
    }
238
239
    /**
240
     * Find the ExpandedValue from a Repeater PageView or null if it doesn't exist.
241
     *
242
     * @param RepeaterPageView $pageView
243
     * @param                  $permalink
244
     *
245
     * @return ExpandedValue|null
246
     */
247
    private static function getExpandedValue(RepeaterPageView $pageView, $permalink)
248
    {
249
        $url = self::normalizeUrl($permalink);
250
        $repeaterPermalinks = $pageView->getRepeaterPermalinks();
251
252
        foreach ($repeaterPermalinks as $expandedValue)
253
        {
254
            if ($expandedValue->getEvaluated() === $url)
255
            {
256
                return $expandedValue;
257
            }
258
        }
259
260
        return null;
261
    }
262
}
263