Passed
Pull Request — master (#192)
by Alexander
06:02 queued 02:54
created

FieldFactory   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 292
Duplicated Lines 0 %

Test Coverage

Coverage 97.41%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 96
dl 0
loc 292
ccs 113
cts 116
cp 0.9741
rs 8.48
c 3
b 1
f 0
wmc 49

32 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 radioList() 0 5 1
A textarea() 0 3 1
A hidden() 0 3 1
A input() 0 13 2
A text() 0 3 1
A button() 0 3 1
A error() 0 7 1
A fieldset() 0 3 1
A makeLabelConfig() 0 9 2
A date() 0 3 1
A buttonGroup() 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 checkboxList() 0 5 1
A image() 0 3 1
A email() 0 3 1
A hint() 0 7 1
A makeFieldConfig() 0 13 4
A field() 0 21 3
A telephone() 0 3 1
A errorSummary() 0 5 1
A submitButton() 0 3 1
A select() 0 3 1
A dateTime() 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\AbstractField;
11
use Yiisoft\Form\Field\Base\AbstractInputField;
12
use Yiisoft\Form\Field\Base\PlaceholderTrait;
13
use Yiisoft\Form\Field\Button;
14
use Yiisoft\Form\Field\ButtonGroup;
15
use Yiisoft\Form\Field\Checkbox;
16
use Yiisoft\Form\Field\CheckboxList;
17
use Yiisoft\Form\Field\Date;
18
use Yiisoft\Form\Field\DateTime;
19
use Yiisoft\Form\Field\DateTimeLocal;
20
use Yiisoft\Form\Field\Email;
21
use Yiisoft\Form\Field\ErrorSummary;
22
use Yiisoft\Form\Field\Fieldset;
23
use Yiisoft\Form\Field\Hidden;
24
use Yiisoft\Form\Field\Image;
25
use Yiisoft\Form\Field\Number;
26
use Yiisoft\Form\Field\Part\Error;
27
use Yiisoft\Form\Field\Part\Hint;
28
use Yiisoft\Form\Field\Part\Label;
29
use Yiisoft\Form\Field\Password;
30
use Yiisoft\Form\Field\RadioList;
31
use Yiisoft\Form\Field\Range;
32
use Yiisoft\Form\Field\ResetButton;
33
use Yiisoft\Form\Field\Select;
34
use Yiisoft\Form\Field\SubmitButton;
35
use Yiisoft\Form\Field\Telephone;
36
use Yiisoft\Form\Field\Text;
37
use Yiisoft\Form\Field\Textarea;
38
use Yiisoft\Form\Field\Url;
39
use Yiisoft\Widget\WidgetFactory;
40
41
use function in_array;
42
43
final class FieldFactory
44
{
45
    private const SUPPORT_PLACEHOLDER = 1;
46
47
    private ?array $baseFieldConfig = null;
48
49
    /**
50
     * @param array[] $fieldConfigs
51
     */
52 17
    public function __construct(
53
        private ?string $containerTag = null,
54
        private array $containerTagAttributes = [],
55
        private ?bool $useContainer = null,
56
        private ?string $template = null,
57
        private ?bool $setInputIdAttribute = null,
58
        private array $inputTagAttributes = [],
59
        private array $labelConfig = [],
60
        private array $hintConfig = [],
61
        private array $errorConfig = [],
62
        private ?bool $usePlaceholder = null,
63
        private array $fieldConfigs = [],
64
    ) {
65
    }
66
67 1
    public function button(array $config = []): Button
68
    {
69 1
        return $this->field(Button::class, $config);
70
    }
71
72 1
    public function buttonGroup(array $config = []): ButtonGroup
73
    {
74 1
        return $this->field(ButtonGroup::class, $config);
75
    }
76
77 1
    public function checkbox(FormModelInterface $formModel, string $attribute, array $config = []): Checkbox
78
    {
79 1
        return $this->input(Checkbox::class, $formModel, $attribute, $config);
80
    }
81
82 1
    public function checkboxList(FormModelInterface $formModel, string $attribute, array $config = []): CheckboxList
83
    {
84
        return $this
85 1
            ->field(CheckboxList::class, $config)
86 1
            ->attribute($formModel, $attribute);
87
    }
88
89 1
    public function date(FormModelInterface $formModel, string $attribute, array $config = []): Date
90
    {
91 1
        return $this->input(Date::class, $formModel, $attribute, $config);
92
    }
93
94 1
    public function dateTime(FormModelInterface $formModel, string $attribute, array $config = []): DateTime
95
    {
96 1
        return $this->input(DateTime::class, $formModel, $attribute, $config);
97
    }
98
99 1
    public function dateTimeLocal(FormModelInterface $formModel, string $attribute, array $config = []): DateTimeLocal
100
    {
101 1
        return $this->input(DateTimeLocal::class, $formModel, $attribute, $config);
102
    }
103
104 1
    public function email(FormModelInterface $formModel, string $attribute, array $config = []): Email
105
    {
106 1
        return $this->input(Email::class, $formModel, $attribute, $config);
107
    }
108
109 1
    public function errorSummary(FormModelInterface $formModel, array $config = []): ErrorSummary
110
    {
111
        return $this
112 1
            ->field(ErrorSummary::class, $config)
113 1
            ->formModel($formModel);
114
    }
115
116 1
    public function fieldset(array $config = []): Fieldset
117
    {
118 1
        return $this->field(Fieldset::class, $config);
119
    }
120
121 1
    public function hidden(FormModelInterface $formModel, string $attribute, array $config = []): Hidden
122
    {
123 1
        return $this->input(Hidden::class, $formModel, $attribute, $config);
124
    }
125
126 1
    public function image(array $config = []): Image
127
    {
128 1
        return $this->field(Image::class, $config);
129
    }
130
131 1
    public function number(FormModelInterface $formModel, string $attribute, array $config = []): Number
132
    {
133 1
        return $this->input(Number::class, $formModel, $attribute, $config);
134
    }
135
136 1
    public function password(FormModelInterface $formModel, string $attribute, array $config = []): Password
137
    {
138 1
        return $this->input(Password::class, $formModel, $attribute, $config);
139
    }
140
141 1
    public function radioList(FormModelInterface $formModel, string $attribute, array $config = []): RadioList
142
    {
143
        return $this
144 1
            ->field(RadioList::class, $config)
145 1
            ->attribute($formModel, $attribute);
146
    }
147
148 1
    public function range(FormModelInterface $formModel, string $attribute, array $config = []): Range
149
    {
150 1
        return $this->input(Range::class, $formModel, $attribute, $config);
151
    }
152
153 1
    public function resetButton(array $config = []): ResetButton
154
    {
155 1
        return $this->field(ResetButton::class, $config);
156
    }
157
158 1
    public function select(FormModelInterface $formModel, string $attribute, array $config = []): Select
159
    {
160 1
        return $this->input(Select::class, $formModel, $attribute, $config);
161
    }
162
163 1
    public function submitButton(array $config = []): SubmitButton
164
    {
165 1
        return $this->field(SubmitButton::class, $config);
166
    }
167
168 1
    public function telephone(FormModelInterface $formModel, string $attribute, array $config = []): Telephone
169
    {
170 1
        return $this->input(Telephone::class, $formModel, $attribute, $config);
171
    }
172
173 12
    public function text(FormModelInterface $formModel, string $attribute, array $config = []): Text
174
    {
175 12
        return $this->input(Text::class, $formModel, $attribute, $config);
176
    }
177
178 1
    public function textarea(FormModelInterface $formModel, string $attribute, array $config = []): Textarea
179
    {
180 1
        return $this->input(Textarea::class, $formModel, $attribute, $config);
181
    }
182
183 1
    public function url(FormModelInterface $formModel, string $attribute, array $config = []): Url
184
    {
185 1
        return $this->input(Url::class, $formModel, $attribute, $config);
186
    }
187
188 4
    public function label(FormModelInterface $formModel, string $attribute, array $config = []): Label
189
    {
190 4
        $widgetConfig = array_merge(
191 4
            $this->makeLabelConfig(),
192
            $config,
193
        );
194 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\RadioList or Yiisoft\Form\Field\Base\AbstractInputField or Yiisoft\Form\Field\CheckboxList. ( Ignorable by Annotation )

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

194
        return Label::widget($widgetConfig)->/** @scrutinizer ignore-call */ attribute($formModel, $attribute);
Loading history...
195
    }
196
197 3
    public function hint(FormModelInterface $formModel, string $attribute, array $config = []): Hint
198
    {
199 3
        $widgetConfig = array_merge(
200 3
            $this->hintConfig,
201
            $config,
202
        );
203 3
        return Hint::widget($widgetConfig)->attribute($formModel, $attribute);
204
    }
205
206 3
    public function error(FormModelInterface $formModel, string $attribute, array $config = []): Error
207
    {
208 3
        $widgetConfig = array_merge(
209 3
            $this->errorConfig,
210
            $config,
211
        );
212 3
        return Error::widget($widgetConfig)->attribute($formModel, $attribute);
213
    }
214
215
    /**
216
     * @psalm-template T
217
     * @psalm-param class-string<T> $class
218
     * @psalm-return T
219
     */
220 25
    public function input(string $class, FormModelInterface $formModel, string $attribute, array $config = []): object
221
    {
222 25
        $widget = $this->field($class, $config);
223 25
        if (!$widget instanceof AbstractInputField) {
224
            throw new InvalidArgumentException(
225
                sprintf(
226
                    'Input widget must be instance of "%s".',
227
                    AbstractInputField::class
228
                )
229
            );
230
        }
231
232 25
        return $widget->attribute($formModel, $attribute);
233
    }
234
235
    /**
236
     * @psalm-template T
237
     * @psalm-param class-string<T> $class
238
     * @psalm-return T
239
     */
240 34
    public function field(string $class, array $config = []): object
241
    {
242 34
        $traits = class_uses($class);
243 34
        if ($traits === false) {
244
            throw new RuntimeException('Invalid field class.');
245
        }
246
247 34
        $supports = [];
248 34
        if (in_array(PlaceholderTrait::class, $traits, true)) {
249 18
            $supports[] = self::SUPPORT_PLACEHOLDER;
250
        }
251
252 34
        $config = array_merge(
253 34
            $this->makeFieldConfig($supports),
254 34
            $this->fieldConfigs[$class] ?? [],
255
            $config,
256 34
            ['class' => $class],
257
        );
258
259
        /** @psalm-var T&AbstractField */
260 34
        return WidgetFactory::createWidget($config);
261
    }
262
263
    /**
264
     * @param int[] $supports
265
     */
266 34
    private function makeFieldConfig(array $supports): array
267
    {
268 34
        $config = $this->makeBaseFieldConfig();
269 34
        foreach ($supports as $support) {
270
            switch ($support) {
271 18
                case self::SUPPORT_PLACEHOLDER:
272 18
                    if ($this->usePlaceholder !== null) {
273 2
                        $config['usePlaceholder()'] = [$this->usePlaceholder];
274
                    }
275 18
                    break;
276
            }
277
        }
278 34
        return $config;
279
    }
280
281 34
    private function makeBaseFieldConfig(): array
282
    {
283 34
        if ($this->baseFieldConfig === null) {
284 10
            $this->baseFieldConfig = [];
285
286 10
            if ($this->containerTag !== null) {
287 2
                $this->baseFieldConfig['containerTag()'] = [$this->containerTag];
288
            }
289
290 10
            if ($this->containerTagAttributes !== []) {
291 2
                $this->baseFieldConfig['containerTagAttributes()'] = [$this->containerTagAttributes];
292
            }
293
294 10
            if ($this->useContainer !== null) {
295 1
                $this->baseFieldConfig['useContainer()'] = [$this->useContainer];
296
            }
297
298 10
            if ($this->template !== null) {
299 1
                $this->baseFieldConfig['template()'] = [$this->template];
300
            }
301
302 10
            if ($this->setInputIdAttribute !== null) {
303 1
                $this->baseFieldConfig['setInputIdAttribute()'] = [$this->setInputIdAttribute];
304
            }
305
306 10
            if ($this->inputTagAttributes !== []) {
307 2
                $this->baseFieldConfig['inputTagAttributes()'] = [$this->inputTagAttributes];
308
            }
309
310 10
            $labelConfig = $this->makeLabelConfig();
311 10
            if ($labelConfig !== []) {
312 2
                $this->baseFieldConfig['labelConfig()'] = [$labelConfig];
313
            }
314
315 10
            if ($this->hintConfig !== []) {
316 1
                $this->baseFieldConfig['hintConfig()'] = [$this->hintConfig];
317
            }
318
319 10
            if ($this->errorConfig !== []) {
320 1
                $this->baseFieldConfig['errorConfig()'] = [$this->errorConfig];
321
            }
322
        }
323 34
        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...
324
    }
325
326 14
    private function makeLabelConfig(): array
327
    {
328 14
        $config = [];
329
330 14
        if ($this->setInputIdAttribute === false) {
331 2
            $config['useInputIdAttribute()'] = [false];
332
        }
333
334 14
        return array_merge($config, $this->labelConfig);
335
    }
336
}
337