Passed
Pull Request — master (#122)
by
unknown
11:55
created

ViewRenderer::withViewBasePath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 6
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 WebView $view;
22
    private string $layout;
23
    private ?string $viewBasePath;
24
    private ?string $viewPath = null;
25
26
    private array $injections;
27
28
    public function __construct(
29
        DataResponseFactoryInterface $responseFactory,
30
        Aliases $aliases,
31
        WebView $view,
32
        string $viewBasePath,
33
        string $layout,
34
        array $injections = []
35
    ) {
36
        $this->responseFactory = $responseFactory;
37
        $this->aliases = $aliases;
38
        $this->view = $view;
39
        $this->viewBasePath = $viewBasePath;
40
        $this->layout = $layout;
41
        $this->injections = $injections;
42
    }
43
44
    public function getViewPath(): string
45
    {
46
        if ($this->viewPath !== null) {
47
            return $this->viewPath;
48
        }
49
50
        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

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