Passed
Pull Request — master (#122)
by
unknown
14:34
created

ViewRenderer::getViewPath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 7
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\ViewRenderer;
6
7
use Psr\Container\ContainerInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Yiisoft\Aliases\Aliases;
10
use Yiisoft\Strings\Inflector;
11
use Yiisoft\View\ViewContextInterface;
12
use Yiisoft\View\WebView;
13
use Yiisoft\DataResponse\DataResponseFactoryInterface;
14
use Yiisoft\Yii\Web\Middleware\Csrf;
15
16
final class ViewRenderer implements ViewContextInterface
17
{
18
    protected ?string $name = null;
19
    protected DataResponseFactoryInterface $responseFactory;
20
21
    private Aliases $aliases;
22
    private ContainerInterface $container;
23
    private WebView $view;
24
    private string $layout;
25
    private ?string $viewBasePath;
26
    private ?string $viewPath = null;
27
28
    private array $contentInjections;
29
    private array $layoutInjections;
30
31
    public function __construct(
32
        DataResponseFactoryInterface $responseFactory,
33
        Aliases $aliases,
34
        ContainerInterface $container,
35
        WebView $view,
36
        string $viewBasePath,
37
        string $layout,
38
        array $contentInjections = [],
39
        array $layoutInjections = []
40
    ) {
41
        $this->responseFactory = $responseFactory;
42
        $this->aliases = $aliases;
43
        $this->container = $container;
44
        $this->view = $view;
45
        $this->viewBasePath = $viewBasePath;
46
        $this->layout = $layout;
47
        $this->contentInjections = $contentInjections;
48
        $this->layoutInjections = $layoutInjections;
49
    }
50
51
    public function getViewPath(): string
52
    {
53
        if ($this->viewPath !== null) {
54
            return $this->viewPath;
55
        }
56
57
        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

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