These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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 | public function __construct( |
||
38 | FormContractorInterface $formContractor, |
||
39 | FormBuilderInterface $formBuilder, |
||
40 | AdminInterface $admin |
||
41 | ) { |
||
42 | parent::__construct($formContractor, $admin); |
||
43 | $this->formBuilder = $formBuilder; |
||
44 | } |
||
45 | |||
46 | public function reorder(array $keys) |
||
47 | { |
||
48 | $this->admin->reorderFormGroup($this->getCurrentGroupName(), $keys); |
||
49 | |||
50 | return $this; |
||
51 | } |
||
52 | |||
53 | /** |
||
54 | * @param FormBuilderInterface|string $name |
||
55 | * @param string|null $type |
||
56 | * |
||
57 | * @return $this |
||
58 | */ |
||
59 | public function add($name, $type = null, array $options = [], array $fieldDescriptionOptions = []) |
||
60 | { |
||
61 | if (!$this->shouldApply()) { |
||
62 | return $this; |
||
63 | } |
||
64 | |||
65 | if ($name instanceof FormBuilderInterface) { |
||
66 | $fieldName = $name->getName(); |
||
67 | } else { |
||
68 | $fieldName = $name; |
||
69 | } |
||
70 | |||
71 | // "Dot" notation is not allowed as form name, but can be used as property path to access nested data. |
||
72 | if (!$name instanceof FormBuilderInterface && !isset($options['property_path'])) { |
||
73 | $options['property_path'] = $fieldName; |
||
74 | |||
75 | // fix the form name |
||
76 | $fieldName = $this->sanitizeFieldName($fieldName); |
||
77 | } |
||
78 | |||
79 | // change `collection` to `sonata_type_native_collection` form type to |
||
80 | // avoid BC break problems |
||
81 | if ('collection' === $type || SymfonyCollectionType::class === $type) { |
||
82 | $type = CollectionType::class; |
||
83 | } |
||
84 | |||
85 | $label = $fieldName; |
||
86 | |||
87 | $group = $this->addFieldToCurrentGroup($label); |
||
88 | |||
89 | // Try to autodetect type |
||
90 | if ($name instanceof FormBuilderInterface && null === $type) { |
||
91 | $fieldDescriptionOptions['type'] = \get_class($name->getType()->getInnerType()); |
||
92 | } |
||
93 | |||
94 | if (!isset($fieldDescriptionOptions['type']) && \is_string($type)) { |
||
95 | $fieldDescriptionOptions['type'] = $type; |
||
96 | } |
||
97 | |||
98 | if ($group['translation_domain'] && !isset($fieldDescriptionOptions['translation_domain'])) { |
||
99 | $fieldDescriptionOptions['translation_domain'] = $group['translation_domain']; |
||
100 | } |
||
101 | |||
102 | $fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance( |
||
103 | $this->admin->getClass(), |
||
104 | $name instanceof FormBuilderInterface ? $name->getName() : $name, |
||
105 | $fieldDescriptionOptions |
||
106 | ); |
||
107 | |||
108 | // Note that the builder var is actually the formContractor: |
||
109 | $this->builder->fixFieldDescription($this->admin, $fieldDescription, $fieldDescriptionOptions); |
||
0 ignored issues
–
show
|
|||
110 | |||
111 | if ($fieldName !== $name) { |
||
112 | $fieldDescription->setName($fieldName); |
||
113 | } |
||
114 | |||
115 | if ($name instanceof FormBuilderInterface) { |
||
116 | $type = null; |
||
117 | $options = []; |
||
118 | } else { |
||
119 | $name = $fieldDescription->getName(); |
||
120 | |||
121 | // Note that the builder var is actually the formContractor: |
||
122 | $options = array_replace_recursive($this->builder->getDefaultOptions($type, $fieldDescription) ?? [], $options); |
||
0 ignored issues
–
show
It seems like you code against a concrete implementation and not the interface
Sonata\AdminBundle\Builder\BuilderInterface as the method getDefaultOptions() does only exist in the following implementations of said interface: Sonata\AdminBundle\Tests...\Builder\FormContractor .
Let’s take a look at an example: interface User
{
/** @return string */
public function getPassword();
}
class MyUser implements User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
123 | |||
124 | // be compatible with mopa if not installed, avoid generating an exception for invalid option |
||
125 | // force the default to false ... |
||
126 | if (!isset($options['label_render'])) { |
||
127 | $options['label_render'] = false; |
||
128 | } |
||
129 | |||
130 | if (!isset($options['label'])) { |
||
131 | $options['label'] = $this->admin->getLabelTranslatorStrategy()->getLabel($name, 'form', 'label'); |
||
132 | } |
||
133 | |||
134 | if (isset($options['help'])) { |
||
135 | $fieldDescription->setHelp($options['help']); |
||
136 | unset($options['help']); |
||
137 | } |
||
138 | } |
||
139 | |||
140 | $this->admin->addFormFieldDescription($fieldName, $fieldDescription); |
||
141 | |||
142 | if (!isset($fieldDescriptionOptions['role']) || $this->admin->isGranted($fieldDescriptionOptions['role'])) { |
||
143 | $this->formBuilder->add($name, $type, $options); |
||
144 | } |
||
145 | |||
146 | return $this; |
||
147 | } |
||
148 | |||
149 | public function get($name) |
||
150 | { |
||
151 | $name = $this->sanitizeFieldName($name); |
||
152 | |||
153 | return $this->formBuilder->get($name); |
||
154 | } |
||
155 | |||
156 | public function has($key) |
||
157 | { |
||
158 | $key = $this->sanitizeFieldName($key); |
||
159 | |||
160 | return $this->formBuilder->has($key); |
||
161 | } |
||
162 | |||
163 | final public function keys() |
||
164 | { |
||
165 | return array_keys($this->formBuilder->all()); |
||
166 | } |
||
167 | |||
168 | public function remove($key) |
||
169 | { |
||
170 | $key = $this->sanitizeFieldName($key); |
||
171 | $this->admin->removeFormFieldDescription($key); |
||
172 | $this->admin->removeFieldFromFormGroup($key); |
||
173 | $this->formBuilder->remove($key); |
||
174 | |||
175 | return $this; |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * Removes a group. |
||
180 | * |
||
181 | * @param string $group The group to delete |
||
182 | * @param string $tab The tab the group belongs to, defaults to 'default' |
||
183 | * @param bool $deleteEmptyTab Whether or not the Tab should be deleted, when the deleted group leaves the tab empty after deletion |
||
184 | * |
||
185 | * @return $this |
||
186 | */ |
||
187 | public function removeGroup($group, $tab = 'default', $deleteEmptyTab = false) |
||
188 | { |
||
189 | $groups = $this->getGroups(); |
||
190 | |||
191 | // When the default tab is used, the tabname is not prepended to the index in the group array |
||
192 | if ('default' !== $tab) { |
||
193 | $group = $tab.'.'.$group; |
||
194 | } |
||
195 | |||
196 | if (isset($groups[$group])) { |
||
197 | foreach ($groups[$group]['fields'] as $field) { |
||
198 | $this->remove($field); |
||
199 | } |
||
200 | } |
||
201 | unset($groups[$group]); |
||
202 | |||
203 | $tabs = $this->getTabs(); |
||
204 | $key = array_search($group, $tabs[$tab]['groups'], true); |
||
205 | |||
206 | if (false !== $key) { |
||
207 | unset($tabs[$tab]['groups'][$key]); |
||
208 | } |
||
209 | if ($deleteEmptyTab && 0 === \count($tabs[$tab]['groups'])) { |
||
210 | unset($tabs[$tab]); |
||
211 | } |
||
212 | |||
213 | $this->setTabs($tabs); |
||
214 | $this->setGroups($groups); |
||
215 | |||
216 | return $this; |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * @return FormBuilderInterface |
||
221 | */ |
||
222 | public function getFormBuilder() |
||
223 | { |
||
224 | return $this->formBuilder; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * @param string $name |
||
229 | * @param mixed $type |
||
230 | * |
||
231 | * @return FormBuilderInterface |
||
232 | */ |
||
233 | public function create($name, $type = null, array $options = []) |
||
234 | { |
||
235 | return $this->formBuilder->create($name, $type, $options); |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * @return FormMapper |
||
240 | */ |
||
241 | public function setHelps(array $helps = []) |
||
242 | { |
||
243 | foreach ($helps as $name => $help) { |
||
244 | $this->addHelp($name, $help); |
||
245 | } |
||
246 | |||
247 | return $this; |
||
248 | } |
||
249 | |||
250 | /** |
||
251 | * @return FormMapper |
||
252 | */ |
||
253 | public function addHelp($name, $help) |
||
254 | { |
||
255 | if ($this->admin->hasFormFieldDescription($name)) { |
||
256 | $this->admin->getFormFieldDescription($name)->setHelp($help); |
||
257 | } |
||
258 | |||
259 | return $this; |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * Symfony default form class sadly can't handle |
||
264 | * form element with dots in its name (when data |
||
265 | * get bound, the default dataMapper is a PropertyPathMapper). |
||
266 | * So use this trick to avoid any issue. |
||
267 | * |
||
268 | * @param string $fieldName |
||
269 | * |
||
270 | * @return string |
||
271 | */ |
||
272 | protected function sanitizeFieldName($fieldName) |
||
273 | { |
||
274 | return str_replace(['__', '.'], ['____', '__'], $fieldName); |
||
275 | } |
||
276 | |||
277 | protected function getGroups() |
||
278 | { |
||
279 | // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call. |
||
280 | |||
281 | return $this->admin->getFormGroups('sonata_deprecation_mute'); |
||
282 | } |
||
283 | |||
284 | protected function setGroups(array $groups) |
||
285 | { |
||
286 | $this->admin->setFormGroups($groups); |
||
287 | } |
||
288 | |||
289 | protected function getTabs() |
||
290 | { |
||
291 | // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call. |
||
292 | |||
293 | return $this->admin->getFormTabs('sonata_deprecation_mute'); |
||
294 | } |
||
295 | |||
296 | protected function setTabs(array $tabs) |
||
297 | { |
||
298 | $this->admin->setFormTabs($tabs); |
||
299 | } |
||
300 | |||
301 | protected function getName() |
||
302 | { |
||
303 | return 'form'; |
||
304 | } |
||
305 | } |
||
306 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.