Passed
Pull Request — master (#192)
by Sergei
02:14
created

FieldFactory   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Test Coverage

Coverage 96.84%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 81
dl 0
loc 241
ccs 92
cts 95
cp 0.9684
rs 9.2
c 3
b 1
f 0
wmc 40

23 Methods

Rating   Name   Duplication   Size   Complexity  
A resetButton() 0 3 1
A label() 0 7 1
A range() 0 3 1
A __construct() 0 13 1
A textarea() 0 3 1
A hidden() 0 3 1
A input() 0 13 2
A text() 0 3 1
A error() 0 7 1
A makeLabelConfig() 0 9 2
A date() 0 3 1
A password() 0 3 1
A dateTimeLocal() 0 3 1
F makeBaseFieldConfig() 0 43 11
A url() 0 3 1
A number() 0 3 1
A email() 0 3 1
A makeFieldConfig() 0 13 4
A hint() 0 7 1
A field() 0 21 3
A telephone() 0 3 1
A submitButton() 0 3 1
A checkbox() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like FieldFactory often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FieldFactory, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form;
6
7
use InvalidArgumentException;
8
use RuntimeException;
9
10
use Yiisoft\Form\Field\Base\AbstractInputField;
11
use Yiisoft\Form\Field\Base\AbstractField;
12
use Yiisoft\Form\Field\Base\PlaceholderTrait;
13
use Yiisoft\Form\Field\Checkbox;
14
use Yiisoft\Form\Field\Date;
15
use Yiisoft\Form\Field\DateTimeLocal;
16
use Yiisoft\Form\Field\Email;
17
use Yiisoft\Form\Field\Hidden;
18
use Yiisoft\Form\Field\Number;
19
use Yiisoft\Form\Field\Part\Error;
20
use Yiisoft\Form\Field\Part\Hint;
21
use Yiisoft\Form\Field\Part\Label;
22
use Yiisoft\Form\Field\Password;
23
use Yiisoft\Form\Field\Range;
24
use Yiisoft\Form\Field\ResetButton;
25
use Yiisoft\Form\Field\SubmitButton;
26
use Yiisoft\Form\Field\Telephone;
27
use Yiisoft\Form\Field\Text;
28
use Yiisoft\Form\Field\Textarea;
29
use Yiisoft\Form\Field\Url;
30
use Yiisoft\Widget\WidgetFactory;
31
32
use function in_array;
33
34
final class FieldFactory
35
{
36
    private const SUPPORT_PLACEHOLDER = 1;
37
38
    private ?array $baseFieldConfig = null;
39
40
    /**
41
     * @param array[] $fieldConfigs
42
     */
43 17
    public function __construct(
44
        private ?string $containerTag = null,
45
        private array $containerTagAttributes = [],
46
        private ?bool $useContainer = null,
47
        private ?string $template = null,
48
        private ?bool $setInputIdAttribute = null,
49
        private array $inputTagAttributes = [],
50
        private array $labelConfig = [],
51
        private array $hintConfig = [],
52
        private array $errorConfig = [],
53
        private ?bool $usePlaceholder = null,
54
        private array $fieldConfigs = [],
55
    ) {
56
    }
57
58 1
    public function checkbox(FormModelInterface $formModel, string $attribute, array $config = []): Checkbox
59
    {
60 1
        return $this->input(Checkbox::class, $formModel, $attribute, $config);
61
    }
62
63 1
    public function date(FormModelInterface $formModel, string $attribute, array $config = []): Date
64
    {
65 1
        return $this->input(Date::class, $formModel, $attribute, $config);
66
    }
67
68 1
    public function dateTimeLocal(FormModelInterface $formModel, string $attribute, array $config = []): DateTimeLocal
69
    {
70 1
        return $this->input(DateTimeLocal::class, $formModel, $attribute, $config);
71
    }
72
73 1
    public function email(FormModelInterface $formModel, string $attribute, array $config = []): Email
74
    {
75 1
        return $this->input(Email::class, $formModel, $attribute, $config);
76
    }
77
78 1
    public function hidden(FormModelInterface $formModel, string $attribute, array $config = []): Hidden
79
    {
80 1
        return $this->input(Hidden::class, $formModel, $attribute, $config);
81
    }
82
83 1
    public function number(FormModelInterface $formModel, string $attribute, array $config = []): Number
84
    {
85 1
        return $this->input(Number::class, $formModel, $attribute, $config);
86
    }
87
88 1
    public function password(FormModelInterface $formModel, string $attribute, array $config = []): Password
89
    {
90 1
        return $this->input(Password::class, $formModel, $attribute, $config);
91
    }
92
93 1
    public function range(FormModelInterface $formModel, string $attribute, array $config = []): Range
94
    {
95 1
        return $this->input(Range::class, $formModel, $attribute, $config);
96
    }
97
98 1
    public function resetButton(array $config = []): ResetButton
99
    {
100 1
        return $this->field(ResetButton::class, $config);
101
    }
102
103 1
    public function submitButton(array $config = []): SubmitButton
104
    {
105 1
        return $this->field(SubmitButton::class, $config);
106
    }
107
108 1
    public function telephone(FormModelInterface $formModel, string $attribute, array $config = []): Telephone
109
    {
110 1
        return $this->input(Telephone::class, $formModel, $attribute, $config);
111
    }
112
113 10
    public function text(FormModelInterface $formModel, string $attribute, array $config = []): Text
114
    {
115 10
        return $this->input(Text::class, $formModel, $attribute, $config);
116
    }
117
118 1
    public function textarea(FormModelInterface $formModel, string $attribute, array $config = []): Textarea
119
    {
120 1
        return $this->input(Textarea::class, $formModel, $attribute, $config);
121
    }
122
123 1
    public function url(FormModelInterface $formModel, string $attribute, array $config = []): Url
124
    {
125 1
        return $this->input(Url::class, $formModel, $attribute, $config);
126
    }
127
128 4
    public function label(FormModelInterface $formModel, string $attribute, array $config = []): Label
129
    {
130 4
        $widgetConfig = array_merge(
131 4
            $this->makeLabelConfig(),
132
            $config,
133
        );
134 4
        return Label::widget($widgetConfig)->attribute($formModel, $attribute);
0 ignored issues
show
Bug introduced by
The method attribute() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Form\Field\Part\Label or Yiisoft\Form\Field\Part\Error or Yiisoft\Form\Field\Part\Hint or Yiisoft\Form\Field\Base\AbstractInputField. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

134
        return Label::widget($widgetConfig)->/** @scrutinizer ignore-call */ attribute($formModel, $attribute);
Loading history...
135
    }
136
137 3
    public function hint(FormModelInterface $formModel, string $attribute, array $config = []): Hint
138
    {
139 3
        $widgetConfig = array_merge(
140 3
            $this->hintConfig,
141
            $config,
142
        );
143 3
        return Hint::widget($widgetConfig)->attribute($formModel, $attribute);
144
    }
145
146 3
    public function error(FormModelInterface $formModel, string $attribute, array $config = []): Error
147
    {
148 3
        $widgetConfig = array_merge(
149 3
            $this->errorConfig,
150
            $config,
151
        );
152 3
        return Error::widget($widgetConfig)->attribute($formModel, $attribute);
153
    }
154
155
    /**
156
     * @psalm-template T
157
     * @psalm-param class-string<T> $class
158
     * @psalm-return T
159
     */
160 21
    public function input(string $class, FormModelInterface $formModel, string $attribute, array $config = []): object
161
    {
162 21
        $widget = $this->field($class, $config);
163 21
        if (!$widget instanceof AbstractInputField) {
164
            throw new InvalidArgumentException(
165
                sprintf(
166
                    'Input widget must be instance of "%s".',
167
                    AbstractInputField::class
168
                )
169
            );
170
        }
171
172 21
        return $widget->attribute($formModel, $attribute);
173
    }
174
175
    /**
176
     * @psalm-template T
177
     * @psalm-param class-string<T> $class
178
     * @psalm-return T
179
     */
180 23
    public function field(string $class, array $config = []): object
181
    {
182 23
        $traits = class_uses($class);
183 23
        if ($traits === false) {
184
            throw new RuntimeException('Invalid field class.');
185
        }
186
187 23
        $supports = [];
188 23
        if (in_array(PlaceholderTrait::class, $traits, true)) {
189 16
            $supports[] = self::SUPPORT_PLACEHOLDER;
190
        }
191
192 23
        $config = array_merge(
193 23
            $this->makeFieldConfig($supports),
194 23
            $this->fieldConfigs[$class] ?? [],
195
            $config,
196 23
            ['class' => $class],
197
        );
198
199
        /** @psalm-var T&AbstractField */
200 23
        return WidgetFactory::createWidget($config);
201
    }
202
203
    /**
204
     * @param int[] $supports
205
     */
206 23
    private function makeFieldConfig(array $supports): array
207
    {
208 23
        $config = $this->makeBaseFieldConfig();
209 23
        foreach ($supports as $support) {
210
            switch ($support) {
211 16
                case self::SUPPORT_PLACEHOLDER:
212 16
                    if ($this->usePlaceholder !== null) {
213 2
                        $config['usePlaceholder()'] = [$this->usePlaceholder];
214
                    }
215 16
                    break;
216
            }
217
        }
218 23
        return $config;
219
    }
220
221 23
    private function makeBaseFieldConfig(): array
222
    {
223 23
        if ($this->baseFieldConfig === null) {
224 10
            $this->baseFieldConfig = [];
225
226 10
            if ($this->containerTag !== null) {
227 2
                $this->baseFieldConfig['containerTag()'] = [$this->containerTag];
228
            }
229
230 10
            if ($this->containerTagAttributes !== []) {
231 2
                $this->baseFieldConfig['containerTagAttributes()'] = [$this->containerTagAttributes];
232
            }
233
234 10
            if ($this->useContainer !== null) {
235 1
                $this->baseFieldConfig['useContainer()'] = [$this->useContainer];
236
            }
237
238 10
            if ($this->template !== null) {
239 1
                $this->baseFieldConfig['template()'] = [$this->template];
240
            }
241
242 10
            if ($this->setInputIdAttribute !== null) {
243 1
                $this->baseFieldConfig['setInputIdAttribute()'] = [$this->setInputIdAttribute];
244
            }
245
246 10
            if ($this->inputTagAttributes !== []) {
247 2
                $this->baseFieldConfig['inputTagAttributes()'] = [$this->inputTagAttributes];
248
            }
249
250 10
            $labelConfig = $this->makeLabelConfig();
251 10
            if ($labelConfig !== []) {
252 2
                $this->baseFieldConfig['labelConfig()'] = [$labelConfig];
253
            }
254
255 10
            if ($this->hintConfig !== []) {
256 1
                $this->baseFieldConfig['hintConfig()'] = [$this->hintConfig];
257
            }
258
259 10
            if ($this->errorConfig !== []) {
260 1
                $this->baseFieldConfig['errorConfig()'] = [$this->errorConfig];
261
            }
262
        }
263 23
        return $this->baseFieldConfig;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->baseFieldConfig could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
264
    }
265
266 14
    private function makeLabelConfig(): array
267
    {
268 14
        $config = [];
269
270 14
        if ($this->setInputIdAttribute === false) {
271 2
            $config['useInputIdAttribute()'] = [false];
272
        }
273
274 14
        return array_merge($config, $this->labelConfig);
275
    }
276
}
277