FormMapper   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 230
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 5
dl 0
loc 230
rs 9.1199
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 6 1
A has() 0 6 1
A keys() 0 4 1
A remove() 0 9 1
B removeGroup() 0 31 7
A getFormBuilder() 0 4 1
A create() 0 4 1
A __construct() 0 8 1
A reorder() 0 6 1
F add() 0 84 20
A sanitizeFieldName() 0 4 1
A getGroups() 0 4 1
A setGroups() 0 4 1
A getTabs() 0 4 1
A setTabs() 0 4 1
A getName() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like FormMapper 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 FormMapper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Form;
15
16
use Sonata\AdminBundle\Admin\AdminInterface;
17
use Sonata\AdminBundle\Builder\FormContractorInterface;
18
use Sonata\AdminBundle\Form\Type\CollectionType;
19
use Sonata\AdminBundle\Mapper\BaseGroupedMapper;
20
use Symfony\Component\Form\Extension\Core\Type\CollectionType as SymfonyCollectionType;
21
use Symfony\Component\Form\FormBuilderInterface;
22
23
/**
24
 * This class is use to simulate the Form API.
25
 *
26
 * @final since sonata-project/admin-bundle 3.52
27
 *
28
 * @author Thomas Rabaix <[email protected]>
29
 */
30
class FormMapper extends BaseGroupedMapper
31
{
32
    /**
33
     * @var FormBuilderInterface
34
     */
35
    protected $formBuilder;
36
37
    /**
38
     * @var FormContractorInterface
39
     */
40
    protected $builder;
41
42
    public function __construct(
43
        FormContractorInterface $formContractor,
44
        FormBuilderInterface $formBuilder,
45
        AdminInterface $admin
46
    ) {
47
        parent::__construct($formContractor, $admin);
48
        $this->formBuilder = $formBuilder;
49
    }
50
51
    public function reorder(array $keys): self
52
    {
53
        $this->admin->reorderFormGroup($this->getCurrentGroupName(), $keys);
54
55
        return $this;
56
    }
57
58
    /**
59
     * @param FormBuilderInterface|string $name
60
     */
61
    public function add($name, ?string $type = null, array $options = [], array $fieldDescriptionOptions = []): self
62
    {
63
        if (!$this->shouldApply()) {
64
            return $this;
65
        }
66
67
        if ($name instanceof FormBuilderInterface) {
68
            $fieldName = $name->getName();
69
        } else {
70
            $fieldName = $name;
71
        }
72
73
        // "Dot" notation is not allowed as form name, but can be used as property path to access nested data.
74
        if (!$name instanceof FormBuilderInterface && !isset($options['property_path'])) {
75
            $options['property_path'] = $fieldName;
76
77
            // fix the form name
78
            $fieldName = $this->sanitizeFieldName($fieldName);
79
        }
80
81
        // change `collection` to `sonata_type_native_collection` form type to
82
        // avoid BC break problems
83
        if ('collection' === $type || SymfonyCollectionType::class === $type) {
84
            $type = CollectionType::class;
85
        }
86
87
        $label = $fieldName;
88
89
        $group = $this->addFieldToCurrentGroup($label);
90
91
        // Try to autodetect type
92
        if ($name instanceof FormBuilderInterface && null === $type) {
93
            $fieldDescriptionOptions['type'] = \get_class($name->getType()->getInnerType());
94
        }
95
96
        if (!isset($fieldDescriptionOptions['type']) && \is_string($type)) {
97
            $fieldDescriptionOptions['type'] = $type;
98
        }
99
100
        if ($group['translation_domain'] && !isset($fieldDescriptionOptions['translation_domain'])) {
101
            $fieldDescriptionOptions['translation_domain'] = $group['translation_domain'];
102
        }
103
104
        $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance(
105
            $this->admin->getClass(),
106
            $name instanceof FormBuilderInterface ? $name->getName() : $name,
107
            $fieldDescriptionOptions
108
        );
109
110
        // Note that the builder var is actually the formContractor:
111
        $this->builder->fixFieldDescription($this->admin, $fieldDescription);
112
113
        if ($fieldName !== $name) {
114
            $fieldDescription->setName($fieldName);
115
        }
116
117
        if ($name instanceof FormBuilderInterface) {
118
            $type = null;
119
            $options = [];
120
        } else {
121
            $name = $fieldDescription->getName();
122
123
            // Note that the builder var is actually the formContractor:
124
            $options = array_replace_recursive($this->builder->getDefaultOptions($type, $fieldDescription) ?? [], $options);
125
126
            // be compatible with mopa if not installed, avoid generating an exception for invalid option
127
            // force the default to false ...
128
            if (!isset($options['label_render'])) {
129
                $options['label_render'] = false;
130
            }
131
132
            if (!isset($options['label'])) {
133
                $options['label'] = $this->admin->getLabelTranslatorStrategy()->getLabel($name, 'form', 'label');
134
            }
135
        }
136
137
        $this->admin->addFormFieldDescription($fieldName, $fieldDescription);
138
139
        if (!isset($fieldDescriptionOptions['role']) || $this->admin->isGranted($fieldDescriptionOptions['role'])) {
140
            $this->formBuilder->add($name, $type, $options);
141
        }
142
143
        return $this;
144
    }
145
146
    public function get(string $name): FormBuilderInterface
147
    {
148
        $name = $this->sanitizeFieldName($name);
149
150
        return $this->formBuilder->get($name);
151
    }
152
153
    public function has(string $key): bool
154
    {
155
        $key = $this->sanitizeFieldName($key);
156
157
        return $this->formBuilder->has($key);
158
    }
159
160
    final public function keys(): array
161
    {
162
        return array_keys($this->formBuilder->all());
163
    }
164
165
    public function remove(string $key): self
166
    {
167
        $key = $this->sanitizeFieldName($key);
168
        $this->admin->removeFormFieldDescription($key);
169
        $this->admin->removeFieldFromFormGroup($key);
170
        $this->formBuilder->remove($key);
171
172
        return $this;
173
    }
174
175
    /**
176
     * Removes a group.
177
     *
178
     * @param string $group          The group to delete
179
     * @param string $tab            The tab the group belongs to, defaults to 'default'
180
     * @param bool   $deleteEmptyTab Whether or not the Tab should be deleted, when the deleted group leaves the tab empty after deletion
181
     */
182
    public function removeGroup(string $group, string $tab = 'default', bool $deleteEmptyTab = false): self
183
    {
184
        $groups = $this->getGroups();
185
186
        // When the default tab is used, the tabname is not prepended to the index in the group array
187
        if ('default' !== $tab) {
188
            $group = sprintf('%s.%s', $tab, $group);
189
        }
190
191
        if (isset($groups[$group])) {
192
            foreach ($groups[$group]['fields'] as $field) {
193
                $this->remove($field);
194
            }
195
        }
196
        unset($groups[$group]);
197
198
        $tabs = $this->getTabs();
199
        $key = array_search($group, $tabs[$tab]['groups'], true);
200
201
        if (false !== $key) {
202
            unset($tabs[$tab]['groups'][$key]);
203
        }
204
        if ($deleteEmptyTab && 0 === \count($tabs[$tab]['groups'])) {
205
            unset($tabs[$tab]);
206
        }
207
208
        $this->setTabs($tabs);
209
        $this->setGroups($groups);
210
211
        return $this;
212
    }
213
214
    public function getFormBuilder(): FormBuilderInterface
215
    {
216
        return $this->formBuilder;
217
    }
218
219
    public function create(string $name, ?string $type = null, array $options = []): FormBuilderInterface
220
    {
221
        return $this->formBuilder->create($name, $type, $options);
222
    }
223
224
    /**
225
     * Symfony default form class sadly can't handle
226
     * form element with dots in its name (when data
227
     * get bound, the default dataMapper is a PropertyPathMapper).
228
     * So use this trick to avoid any issue.
229
     */
230
    protected function sanitizeFieldName(string $fieldName): string
231
    {
232
        return str_replace(['__', '.'], ['____', '__'], $fieldName);
233
    }
234
235
    protected function getGroups(): array
236
    {
237
        return $this->admin->getFormGroups();
238
    }
239
240
    protected function setGroups(array $groups): void
241
    {
242
        $this->admin->setFormGroups($groups);
243
    }
244
245
    protected function getTabs(): array
246
    {
247
        return $this->admin->getFormTabs();
248
    }
249
250
    protected function setTabs(array $tabs): void
251
    {
252
        $this->admin->setFormTabs($tabs);
253
    }
254
255
    protected function getName(): string
256
    {
257
        return 'form';
258
    }
259
}
260