Passed
Pull Request — master (#122)
by Sergei
12:04
created

ViewRenderer   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Importance

Changes 13
Bugs 0 Features 0
Metric Value
wmc 38
eloc 95
dl 0
loc 246
rs 9.36
c 13
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getViewPath() 0 7 2
A render() 0 5 1
A __construct() 0 16 1
A injectLinkTags() 0 7 4
A getName() 0 17 4
A injectMetaTags() 0 7 4
A withAddedInjections() 0 5 1
A withCsrf() 0 6 2
A withLayout() 0 6 1
A withViewBasePath() 0 6 1
A withControllerName() 0 6 1
A withAddedInjection() 0 3 1
A renderPartial() 0 5 1
A withViewPath() 0 6 1
A withController() 0 6 1
A withInjections() 0 5 1
A renderProxy() 0 19 2
A findLayoutFile() 0 13 3
A getContentParameters() 0 8 3
A getLayoutParameters() 0 8 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\ViewRenderer;
6
7
use Psr\Http\Message\ResponseInterface;
8
use RuntimeException;
9
use Yiisoft\Aliases\Aliases;
10
use Yiisoft\Arrays\ArrayHelper;
11
use Yiisoft\Strings\Inflector;
12
use Yiisoft\View\ViewContextInterface;
13
use Yiisoft\View\WebView;
14
use Yiisoft\DataResponse\DataResponseFactoryInterface;
15
16
final class ViewRenderer implements ViewContextInterface
17
{
18
    protected ?string $name = null;
19
    protected DataResponseFactoryInterface $responseFactory;
20
21
    private Aliases $aliases;
22
    private WebView $view;
23
    private ?CsrfViewInjectionInterface $csrfViewInjection;
24
    private string $layout;
25
    private ?string $viewBasePath;
26
    private ?string $viewPath = null;
27
28
    private array $injections;
29
30
    public function __construct(
31
        DataResponseFactoryInterface $responseFactory,
32
        Aliases $aliases,
33
        WebView $view,
34
        ?CsrfViewInjectionInterface $csrfViewInjection,
35
        string $viewBasePath,
36
        string $layout,
37
        array $injections = []
38
    ) {
39
        $this->responseFactory = $responseFactory;
40
        $this->aliases = $aliases;
41
        $this->view = $view;
42
        $this->csrfViewInjection = $csrfViewInjection;
43
        $this->viewBasePath = $viewBasePath;
44
        $this->layout = $layout;
45
        $this->injections = $injections;
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
    /**
112
     * @param object[] $injections
113
     * @return self
114
     */
115
    public function withAddedInjections(array $injections): self
116
    {
117
        $new = clone $this;
118
        $new->injections = array_merge($this->injections, $injections);
119
        return $new;
120
    }
121
122
    /**
123
     * @param object $injection
124
     * @return self
125
     */
126
    public function withAddedInjection($injection): self
127
    {
128
        return $this->withAddedInjections([$injection]);
129
    }
130
131
    /**
132
     * @param object[] $injections
133
     * @return self
134
     */
135
    public function withInjections(array $injections): self
136
    {
137
        $new = clone $this;
138
        $new->injections = $injections;
139
        return $new;
140
    }
141
142
    public function withCsrf(string $requestAttribute = null): self
143
    {
144
        if ($this->csrfViewInjection === null) {
145
            throw new RuntimeException('No definition for CsrfViewInjectionInterface.');
146
        }
147
        return $this->withAddedInjection($this->csrfViewInjection->withRequestAttribute($requestAttribute));
148
    }
149
150
    private function renderProxy(string $view, array $parameters = []): string
151
    {
152
        $this->injectMetaTags();
153
        $this->injectLinkTags();
154
155
        $parameters = $this->getContentParameters($parameters);
156
        $content = $this->view->render($view, $parameters, $this);
157
158
        $layout = $this->findLayoutFile($this->layout);
159
        if ($layout === null) {
0 ignored issues
show
introduced by
The condition $layout === null is always false.
Loading history...
160
            return $content;
161
        }
162
163
        $layoutParameters = $this->getLayoutParameters(['content' => $content]);
164
165
        return $this->view->renderFile(
166
            $layout,
167
            $layoutParameters,
168
            $this,
169
        );
170
    }
171
172
    private function injectMetaTags(): void
173
    {
174
        foreach ($this->injections as $injection) {
175
            if ($injection instanceof MetaTagsInjectionInterface) {
176
                foreach ($injection->getMetaTags() as $options) {
177
                    $key = ArrayHelper::remove($options, '__key');
178
                    $this->view->registerMetaTag($options, $key);
179
                }
180
            }
181
        }
182
    }
183
184
    private function injectLinkTags(): void
185
    {
186
        foreach ($this->injections as $injection) {
187
            if ($injection instanceof LinkTagsInjectionInterface) {
188
                foreach ($injection->getLinkTags() as $options) {
189
                    $key = ArrayHelper::remove($options, '__key');
190
                    $this->view->registerLinkTag($options, $key);
191
                }
192
            }
193
        }
194
    }
195
196
    private function getContentParameters(array $parameters): array
197
    {
198
        foreach ($this->injections as $injection) {
199
            if ($injection instanceof ContentParamsInjectionInterface) {
200
                $parameters = array_merge($parameters, $injection->getContentParams());
201
            }
202
        }
203
        return $parameters;
204
    }
205
206
    private function getLayoutParameters(array $parameters): array
207
    {
208
        foreach ($this->injections as $injection) {
209
            if ($injection instanceof LayoutParamsInjectionInterface) {
210
                $parameters = array_merge($parameters, $injection->getLayoutParams());
211
            }
212
        }
213
        return $parameters;
214
    }
215
216
    private function findLayoutFile(?string $file): ?string
217
    {
218
        if ($file === null) {
219
            return null;
220
        }
221
222
        $file = $this->aliases->get($file);
223
224
        if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
225
            return $file;
226
        }
227
228
        return $file . '.' . $this->view->getDefaultExtension();
229
    }
230
231
    /**
232
     * Returns the controller name. Name should be converted to "id" case.
233
     * Method returns classname without `controller` on the ending.
234
     * If namespace is not contain `controller` or `controllers`
235
     * then returns only classname without `controller` on the ending
236
     * else returns all subnamespaces from `controller` (or `controllers`) to the end
237
     *
238
     * @param object $controller
239
     * @return string
240
     * @example App\Controller\FooBar\BazController -> foo-bar/baz
241
     * @example App\Controllers\FooBar\BazController -> foo-bar/baz
242
     * @example Path\To\File\BlogController -> blog
243
     * @see Inflector::pascalCaseToId()
244
     */
245
    private function getName(object $controller): string
246
    {
247
        static $cache = [];
248
249
        $class = get_class($controller);
250
        if (array_key_exists($class, $cache)) {
251
            return $cache[$class];
252
        }
253
254
        $regexp = '/((?<=controller\\\|s\\\)(?:[\w\\\]+)|(?:[a-z]+))controller/iuU';
255
        if (!preg_match($regexp, $class, $m) || empty($m[1])) {
256
            throw new \RuntimeException('Cannot detect controller name');
257
        }
258
259
        $inflector = new Inflector();
260
        $name = str_replace('\\', '/', $m[1]);
261
        return $cache[$class] = $inflector->pascalCaseToId($name);
262
    }
263
}
264