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

ViewRenderer::render()   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
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 2
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 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 addInjections(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 addInjection($injection): self
123
    {
124
        return $this->addInjections([$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
        $parameters = $this->injectContent($parameters, $this->injections);
141
        $content = $this->view->render($view, $parameters, $this);
142
143
        $layout = $this->findLayoutFile($this->layout);
144
        if ($layout === null) {
0 ignored issues
show
introduced by
The condition $layout === null is always false.
Loading history...
145
            return $content;
146
        }
147
148
        $layoutParameters = $this->injectLayout(['content' => $content], $this->injections);
149
150
        return $this->view->renderFile(
151
            $layout,
152
            $layoutParameters,
153
            $this,
154
        );
155
    }
156
157
    /**
158
     * @param array $parameters
159
     * @param ContentParamsInjectionInterface[]|LayoutParamsInjectionInterface[]|LinkTagsInjectionInterface[]|MetaTagsInjectionInterface[] $injections
160
     * @return array
161
     */
162
    private function injectContent(array $parameters, array $injections): array
163
    {
164
        foreach ($injections as $injection) {
165
            if ($injection instanceof ContentParamsInjectionInterface) {
166
                $parameters = array_merge($parameters, $injection->getContentParams());
167
            }
168
            if ($injection instanceof MetaTagsInjectionInterface) {
169
                foreach ($injection->getMetaTags() as $options) {
170
                    $key = ArrayHelper::remove($options, '__key');
171
                    $this->view->registerMetaTag($options, $key);
172
                }
173
            }
174
            if ($injection instanceof LinkTagsInjectionInterface) {
175
                foreach ($injection->getLinkTags() as $options) {
176
                    $key = ArrayHelper::remove($options, '__key');
177
                    $this->view->registerLinkTag($options, $key);
178
                }
179
            }
180
        }
181
        return $parameters;
182
    }
183
184
    /**
185
     * @param array $parameters
186
     * @param ContentParamsInjectionInterface[]|LayoutParamsInjectionInterface[]|LinkTagsInjectionInterface[]|MetaTagsInjectionInterface[] $injections
187
     * @return array
188
     */
189
    private function injectLayout(array $parameters, array $injections): array
190
    {
191
        foreach ($injections as $injection) {
192
            if ($injection instanceof LayoutParamsInjectionInterface) {
193
                $parameters = array_merge($parameters, $injection->getLayoutParams());
194
            }
195
        }
196
        return $parameters;
197
    }
198
199
    private function findLayoutFile(?string $file): ?string
200
    {
201
        if ($file === null) {
202
            return null;
203
        }
204
205
        $file = $this->aliases->get($file);
206
207
        if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
208
            return $file;
209
        }
210
211
        return $file . '.' . $this->view->getDefaultExtension();
212
    }
213
214
    /**
215
     * Returns the controller name. Name should be converted to "id" case.
216
     * Method returns classname without `controller` on the ending.
217
     * If namespace is not contain `controller` or `controllers`
218
     * then returns only classname without `controller` on the ending
219
     * else returns all subnamespaces from `controller` (or `controllers`) to the end
220
     *
221
     * @param object $controller
222
     * @return string
223
     * @example App\Controller\FooBar\BazController -> foo-bar/baz
224
     * @example App\Controllers\FooBar\BazController -> foo-bar/baz
225
     * @example Path\To\File\BlogController -> blog
226
     * @see Inflector::camel2id()
227
     */
228
    private static function getName(object $controller): string
229
    {
230
        static $cache = [];
231
232
        $class = get_class($controller);
233
        if (isset($cache[$class])) {
234
            return $cache[$class];
235
        }
236
237
        $regexp = '/((?<=controller\\\|s\\\)(?:[\w\\\]+)|(?:[a-z]+))controller/iuU';
238
        if (!preg_match($regexp, $class, $m) || empty($m[1])) {
239
            throw new \RuntimeException('Cannot detect controller name');
240
        }
241
242
        $inflector = new Inflector();
243
        $name = str_replace('\\', '/', $m[1]);
244
        return $cache[$class] = $inflector->camel2id($name);
245
    }
246
}
247