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

ViewRenderer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
c 0
b 0
f 0
nc 1
nop 8
dl 0
loc 18
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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