Passed
Push — master ( 48ebae...be64f6 )
by Alexander
11:25
created

ViewRenderer::renderPartial()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App;
6
7
use Psr\Http\Message\ResponseInterface;
8
use Yiisoft\Aliases\Aliases;
9
use Yiisoft\Router\UrlMatcherInterface;
10
use Yiisoft\Strings\Inflector;
11
use Yiisoft\View\ViewContextInterface;
12
use Yiisoft\View\WebView;
13
use Yiisoft\DataResponse\DataResponseFactoryInterface;
14
15
final class ViewRenderer implements ViewContextInterface
16
{
17
    protected ?string $name = null;
18
    protected DataResponseFactoryInterface $responseFactory;
19
    protected ApplicationParameters $applicationParameters;
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
        ApplicationParameters $applicationParameters,
33
        Aliases $aliases,
34
        WebView $view,
35
        UrlMatcherInterface $urlMatcher,
36
        string $viewBasePath,
37
        string $layout
38
    ) {
39
        $this->responseFactory = $responseFactory;
40
        $this->applicationParameters = $applicationParameters;
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 = null): self
112
    {
113
        $new = clone $this;
114
        $new->csrfTokenRequestAttribute = $requestAttribute ?? $this->applicationParameters->getCsrfAttribute();
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
        $layout = $this->findLayoutFile($this->aliases->get($this->layout));
134
135
        if ($layout === null) {
0 ignored issues
show
introduced by
The condition $layout === null is always false.
Loading history...
136
            return $content;
137
        }
138
139
        $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...
140
141
        return $this->view->renderFile(
142
            $layout,
143
            $layoutParameters,
144
            $this,
145
        );
146
    }
147
148
    private function findLayoutFile(?string $file): ?string
149
    {
150
        if ($file === null) {
151
            return null;
152
        }
153
154
        if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
155
            return $file;
156
        }
157
158
        return $file . '.' . $this->view->getDefaultExtension();
159
    }
160
161
    /**
162
     * Returns the controller name. Name should be converted to "id" case.
163
     * Method returns classname without `controller` on the ending.
164
     * If namespace is not contain `controller` or `controllers`
165
     * then returns only classname without `controller` on the ending
166
     * else returns all subnamespaces from `controller` (or `controllers`) to the end
167
     *
168
     * @return string
169
     * @example App\Controller\FooBar\BazController -> foo-bar/baz
170
     * @example App\Controllers\FooBar\BazController -> foo-bar/baz
171
     * @example Path\To\File\BlogController -> blog
172
     * @see Inflector::camel2id()
173
     */
174
    private function getName(object $controller): string
175
    {
176
        if ($this->name !== null) {
177
            return $this->name;
178
        }
179
180
        $regexp = '/((?<=controller\\\|s\\\)(?:[\w\\\]+)|(?:[a-z]+))controller/iuU';
181
        if (!preg_match($regexp, get_class($controller), $m) || empty($m[1])) {
182
            throw new \RuntimeException('Cannot detect controller name');
183
        }
184
185
        $inflector = new Inflector();
186
        $name = str_replace('\\', '/', $m[1]);
187
188
        return $this->name = $inflector->camel2id($name);
189
    }
190
191
    private function getCsrfToken(): string
192
    {
193
        return $this->urlMatcher->getLastMatchedRequest()->getAttribute($this->csrfTokenRequestAttribute);
194
    }
195
}
196