Passed
Pull Request — master (#122)
by
unknown
13:01
created

ViewRenderer::withLayoutInjections()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\ViewRenderer;
6
7
use Psr\Http\Message\ResponseInterface;
8
use Yiisoft\Aliases\Aliases;
9
use Yiisoft\Arrays\ArrayHelper;
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
20
    private Aliases $aliases;
21
    private CsrfInjection $csrfInjection;
22
    private WebView $view;
23
    private string $layout;
24
    private ?string $viewBasePath;
25
    private ?string $viewPath = null;
26
27
    private array $contentInjections;
28
    private array $layoutInjections;
29
30
    public function __construct(
31
        DataResponseFactoryInterface $responseFactory,
32
        Aliases $aliases,
33
        CsrfInjection $csrfInjection,
34
        WebView $view,
35
        string $viewBasePath,
36
        string $layout,
37
        array $contentInjections = [],
38
        array $layoutInjections = []
39
    ) {
40
        $this->responseFactory = $responseFactory;
41
        $this->aliases = $aliases;
42
        $this->csrfInjection = $csrfInjection;
43
        $this->view = $view;
44
        $this->viewBasePath = $viewBasePath;
45
        $this->layout = $layout;
46
        $this->contentInjections = $contentInjections;
47
        $this->layoutInjections = $layoutInjections;
48
    }
49
50
    public function getViewPath(): string
51
    {
52
        if ($this->viewPath !== null) {
53
            return $this->viewPath;
54
        }
55
56
        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

56
        return $this->aliases->get(/** @scrutinizer ignore-type */ $this->viewBasePath) . '/' . $this->name;
Loading history...
57
    }
58
59
    public function render(string $view, array $parameters = []): ResponseInterface
60
    {
61
        $contentRenderer = fn() => $this->renderProxy($view, $parameters);
62
63
        return $this->responseFactory->createResponse($contentRenderer);
64
    }
65
66
    public function renderPartial(string $view, array $parameters = []): ResponseInterface
67
    {
68
        $content = $this->view->render($view, $parameters, $this);
69
70
        return $this->responseFactory->createResponse($content);
71
    }
72
73
    public function withController(object $controller): self
74
    {
75
        $new = clone $this;
76
        $new->name = static::getName($controller);
77
78
        return $new;
79
    }
80
81
    public function withControllerName(string $name): self
82
    {
83
        $new = clone $this;
84
        $new->name = $name;
85
86
        return $new;
87
    }
88
89
    public function withViewPath(string $viewPath): self
90
    {
91
        $new = clone $this;
92
        $new->viewPath = $viewPath;
93
94
        return $new;
95
    }
96
97
    public function withViewBasePath(string $viewBasePath): self
98
    {
99
        $new = clone $this;
100
        $new->viewBasePath = $viewBasePath;
101
102
        return $new;
103
    }
104
105
    public function withLayout(string $layout): self
106
    {
107
        $new = clone $this;
108
        $new->layout = $layout;
109
110
        return $new;
111
    }
112
113
    public function addContentInjections(array $injections): self
114
    {
115
        $new = clone $this;
116
        $new->contentInjections = array_merge($this->contentInjections, $injections);
117
        return $new;
118
    }
119
120
    public function withContentInjections(array $injections): self
121
    {
122
        $new = clone $this;
123
        $new->contentInjections = $injections;
124
        return $new;
125
    }
126
127
    public function addLayoutInjections(array $injections): self
128
    {
129
        $new = clone $this;
130
        $new->layoutInjections = array_merge($this->layoutInjections, $injections);
131
        return $new;
132
    }
133
134
    public function withLayoutInjections(array $injections): self
135
    {
136
        $new = clone $this;
137
        $new->layoutInjections = $injections;
138
        return $new;
139
    }
140
141
    public function withCsrf(?string $requestAttribute = null): self
142
    {
143
        $injecton = $requestAttribute === null ? $this->csrfInjection : $this->csrfInjection->withRequestAttribute($requestAttribute);
144
        return $this->addContentInjections([$injecton]);
145
    }
146
147
    private function renderProxy(string $view, array $parameters = []): string
148
    {
149
        $parameters = $this->inject($parameters, $this->contentInjections);
150
        $content = $this->view->render($view, $parameters, $this);
151
152
        $layout = $this->findLayoutFile($this->layout);
153
        if ($layout === null) {
0 ignored issues
show
introduced by
The condition $layout === null is always false.
Loading history...
154
            return $content;
155
        }
156
157
        $layoutParameters = $this->inject(['content' => $content], $this->layoutInjections);
158
159
        return $this->view->renderFile(
160
            $layout,
161
            $layoutParameters,
162
            $this,
163
        );
164
    }
165
166
    /**
167
     * @param array $parameters
168
     * @param InjectionInterface[] $injections
169
     * @return array
170
     */
171
    private function inject(array $parameters, array $injections): array
172
    {
173
        foreach ($injections as $injection) {
174
            $parameters = array_merge($parameters, $injection->getParams());
175
            foreach ($injection->getMetaTags() as $options) {
176
                $key = ArrayHelper::remove($options, '__key');
177
                $this->view->registerMetaTag($options, $key);
178
            }
179
            foreach ($injection->getLinkTags() as $options) {
180
                $key = ArrayHelper::remove($options, '__key');
181
                $this->view->registerLinkTag($options, $key);
182
            }
183
        }
184
        return $parameters;
185
    }
186
187
    private function findLayoutFile(?string $file): ?string
188
    {
189
        if ($file === null) {
190
            return null;
191
        }
192
193
        $file = $this->aliases->get($file);
194
195
        if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
196
            return $file;
197
        }
198
199
        return $file . '.' . $this->view->getDefaultExtension();
200
    }
201
202
    /**
203
     * Returns the controller name. Name should be converted to "id" case.
204
     * Method returns classname without `controller` on the ending.
205
     * If namespace is not contain `controller` or `controllers`
206
     * then returns only classname without `controller` on the ending
207
     * else returns all subnamespaces from `controller` (or `controllers`) to the end
208
     *
209
     * @param object $controller
210
     * @return string
211
     * @example App\Controller\FooBar\BazController -> foo-bar/baz
212
     * @example App\Controllers\FooBar\BazController -> foo-bar/baz
213
     * @example Path\To\File\BlogController -> blog
214
     * @see Inflector::camel2id()
215
     */
216
    private static function getName(object $controller): string
217
    {
218
        static $cache = [];
219
220
        $class = get_class($controller);
221
        if (isset($cache[$class])) {
222
            return $cache[$class];
223
        }
224
225
        $regexp = '/((?<=controller\\\|s\\\)(?:[\w\\\]+)|(?:[a-z]+))controller/iuU';
226
        if (!preg_match($regexp, $class, $m) || empty($m[1])) {
227
            throw new \RuntimeException('Cannot detect controller name');
228
        }
229
230
        $inflector = new Inflector();
231
        $name = str_replace('\\', '/', $m[1]);
232
        $name = $inflector->camel2id($name);
233
234
        $cache[$class] = $name;
235
        return $name;
236
    }
237
}
238