Passed
Pull Request — master (#120)
by Alexander
16:51 queued 01:51
created

ViewRenderer::withCsrf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 3
b 0
f 0
nc 1
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace App;
4
5
use Psr\Http\Message\ResponseInterface;
6
use Yiisoft\Aliases\Aliases;
7
use Yiisoft\Router\UrlMatcherInterface;
8
use Yiisoft\Strings\Inflector;
9
use Yiisoft\View\ViewContextInterface;
10
use Yiisoft\View\WebView;
11
use Yiisoft\DataResponse\DataResponseFactoryInterface;
12
use Yiisoft\Yii\Web\Middleware\Csrf;
13
use Yiisoft\Yii\Web\User\User;
14
15
final class ViewRenderer implements ViewContextInterface
16
{
17
    protected ?string $name = null;
18
    protected DataResponseFactoryInterface $responseFactory;
19
    protected User $user;
20
21
    private UrlMatcherInterface $urlMatcher;
22
    private Aliases $aliases;
23
    private WebView $view;
24
    private string $layout;
25
    private ?string $viewBasePath;
26
    private ?string $viewPath = null;
27
    private ?string $csrfToken = null;
28
    private string $csrfTokenRequestAttribute;
29
30
    public function __construct(
31
        DataResponseFactoryInterface $responseFactory,
32
        User $user,
33
        Aliases $aliases,
34
        WebView $view,
35
        UrlMatcherInterface $urlMatcher,
36
        string $viewBasePath,
37
        string $layout
38
    ) {
39
        $this->responseFactory = $responseFactory;
40
        $this->user = $user;
41
        $this->aliases = $aliases;
42
        $this->view = $view;
43
        $this->urlMatcher = $urlMatcher;
44
        $this->viewBasePath = $viewBasePath;
45
        $this->layout = $layout;
46
    }
47
48
    public function getViewPath(): string
49
    {
50
        if ($this->viewPath !== null) {
51
            return $this->viewPath;
52
        }
53
54
        return $this->aliases->get($this->viewBasePath) . '/' . $this->name;
0 ignored issues
show
Bug introduced by
It seems like $this->viewBasePath can also be of type null; however, parameter $alias of Yiisoft\Aliases\Aliases::get() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

54
        return $this->aliases->get(/** @scrutinizer ignore-type */ $this->viewBasePath) . '/' . $this->name;
Loading history...
55
    }
56
57
    public function render(string $view, array $parameters = []): ResponseInterface
58
    {
59
        $contentRenderer = fn () => $this->renderProxy($view, $parameters);
60
61
        return $this->responseFactory->createResponse($contentRenderer);
62
    }
63
64
    public function renderPartial(string $view, array $parameters = []): ResponseInterface
65
    {
66
        $content = $this->view->render($view, $parameters, $this);
67
68
        return $this->responseFactory->createResponse($content);
69
    }
70
71
    public function withController(object $controller): self
72
    {
73
        $new = clone $this;
74
        $new->name = $this->getName($controller);
75
76
        return $new;
77
    }
78
79
    public function withControllerName(string $name): self
80
    {
81
        $new = clone $this;
82
        $new->name = $name;
83
84
        return $new;
85
    }
86
87
    public function withViewPath(string $viewPath): self
88
    {
89
        $new = clone $this;
90
        $new->viewPath = $viewPath;
91
92
        return $new;
93
    }
94
95
    public function withViewBasePath(string $viewBasePath): self
96
    {
97
        $new = clone $this;
98
        $new->viewBasePath = $viewBasePath;
99
100
        return $new;
101
    }
102
103
    public function withLayout(string $layout): self
104
    {
105
        $new = clone $this;
106
        $new->layout = $layout;
107
108
        return $new;
109
    }
110
111
    public function withCsrf(string $requestAttribute = Csrf::REQUEST_NAME): self
112
    {
113
        $new = clone $this;
114
        $new->csrfTokenRequestAttribute = $requestAttribute;
115
        $new->csrfToken = $new->getCsrfToken();
116
117
        return $new;
118
    }
119
120
    private function renderProxy(string $view, array $parameters = []): string
121
    {
122
        if ($this->csrfToken !== null) {
123
            $parameters['csrf'] = $this->csrfToken;
124
            $this->view->registerMetaTag(
125
                [
126
                    'name' => 'csrf',
127
                    'content' => $this->csrfToken,
128
                ],
129
                'csrf_meta_tags'
130
            );
131
        }
132
        $content = $this->view->render($view, $parameters, $this);
133
        $user = $this->user->getIdentity();
134
        $layout = $this->findLayoutFile($this->aliases->get($this->layout));
135
136
        if ($layout === null) {
0 ignored issues
show
introduced by
The condition $layout === null is always false.
Loading history...
137
            return $content;
138
        }
139
140
        $layoutParameters['content'] = $content;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$layoutParameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $layoutParameters = array(); before regardless.
Loading history...
141
        $layoutParameters['user'] = $user;
142
143
        return $this->view->renderFile(
144
            $layout,
145
            $layoutParameters,
146
            $this,
147
        );
148
    }
149
150
    private function findLayoutFile(?string $file): ?string
151
    {
152
        if ($file === null) {
153
            return null;
154
        }
155
156
        if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
157
            return $file;
158
        }
159
160
        return $file . '.' . $this->view->getDefaultExtension();
161
    }
162
163
    /**
164
     * Returns the controller name. Name should be converted to "id" case.
165
     * Method returns classname without `controller` on the ending.
166
     * If namespace is not contain `controller` or `controllers`
167
     * then returns only classname without `controller` on the ending
168
     * else returns all subnamespaces from `controller` (or `controllers`) to the end
169
     *
170
     * @return string
171
     * @example App\Controller\FooBar\BazController -> foo-bar/baz
172
     * @example App\Controllers\FooBar\BazController -> foo-bar/baz
173
     * @example Path\To\File\BlogController -> blog
174
     * @see Inflector::camel2id()
175
     */
176
    private function getName(object $controller): string
177
    {
178
        if ($this->name !== null) {
179
            return $this->name;
180
        }
181
182
        $regexp = '/((?<=controller\\\|s\\\)(?:[\w\\\]+)|(?:[a-z]+))controller/iuU';
183
        if (!preg_match($regexp, get_class($controller), $m) || empty($m[1])) {
184
            throw new \RuntimeException('Cannot detect controller name');
185
        }
186
187
        $inflector = new Inflector();
188
        $name = str_replace('\\', '/', $m[1]);
189
190
        return $this->name = $inflector->camel2id($name);
191
    }
192
193
    private function getCsrfToken(): string
194
    {
195
        return $this->urlMatcher->getLastMatchedRequest()->getAttribute($this->csrfTokenRequestAttribute);
196
    }
197
}
198