Passed
Pull Request — master (#118)
by Dmitriy
12:16
created

ViewRenderer   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 181
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 76
c 4
b 0
f 0
dl 0
loc 181
rs 10
wmc 22

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getCsrf() 0 3 1
A getName() 0 15 4
A findLayoutFile() 0 11 3
A getViewPath() 0 7 2
A withLayout() 0 6 1
A withViewBasePath() 0 6 1
A __construct() 0 16 1
A renderPartial() 0 5 1
A renderProxy() 0 27 3
A withViewPath() 0 6 1
A withCsrf() 0 7 1
A render() 0 5 1
A withControllerName() 0 6 1
A withController() 0 6 1
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\User\User;
13
14
final class ViewRenderer implements ViewContextInterface
15
{
16
    protected ?string $name = null;
17
    protected DataResponseFactoryInterface $responseFactory;
18
    protected User $user;
19
20
    private UrlMatcherInterface $urlMatcher;
21
    private Aliases $aliases;
22
    private WebView $view;
23
    private string $layout;
24
    private ?string $viewBasePath;
25
    private ?string $viewPath = null;
26
    private ?string $csrf = null;
27
    private string $csrfRequestName;
28
29
    public function __construct(
30
        DataResponseFactoryInterface $responseFactory,
31
        User $user,
32
        Aliases $aliases,
33
        WebView $view,
34
        UrlMatcherInterface $urlMatcher,
35
        string $viewBasePath,
36
        string $layout
37
    ) {
38
        $this->responseFactory = $responseFactory;
39
        $this->user = $user;
40
        $this->aliases = $aliases;
41
        $this->view = $view;
42
        $this->urlMatcher = $urlMatcher;
43
        $this->viewBasePath = $viewBasePath;
44
        $this->layout = $layout;
45
    }
46
47
    public function getViewPath(): string
48
    {
49
        if ($this->viewPath !== null) {
50
            return $this->viewPath;
51
        }
52
53
        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

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