Complex classes like AdminHelper 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 AdminHelper, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class AdminHelper |
||
33 | { |
||
34 | /** |
||
35 | * @var string |
||
36 | */ |
||
37 | private const FORM_FIELD_DELETE = '_delete'; |
||
38 | |||
39 | /** |
||
40 | * @var Pool |
||
41 | */ |
||
42 | protected $pool; |
||
43 | |||
44 | public function __construct(Pool $pool) |
||
48 | |||
49 | /** |
||
50 | * @param string $elementId |
||
51 | * |
||
52 | * @throws \RuntimeException |
||
53 | * |
||
54 | * @return FormBuilderInterface|null |
||
55 | */ |
||
56 | public function getChildFormBuilder(FormBuilderInterface $formBuilder, $elementId) |
||
66 | |||
67 | /** |
||
68 | * @param string $elementId |
||
69 | * |
||
70 | * @return FormView|null |
||
71 | */ |
||
72 | public function getChildFormView(FormView $formView, $elementId) |
||
82 | |||
83 | /** |
||
84 | * NEXT_MAJOR: remove this method. |
||
85 | * |
||
86 | * @deprecated |
||
87 | * |
||
88 | * @param string $code |
||
89 | * |
||
90 | * @return AdminInterface |
||
91 | */ |
||
92 | public function getAdmin($code) |
||
96 | |||
97 | /** |
||
98 | * Note: |
||
99 | * This code is ugly, but there is no better way of doing it. |
||
100 | * |
||
101 | * @param object $subject |
||
102 | * @param string $elementId |
||
103 | * |
||
104 | * @throws \RuntimeException |
||
105 | * @throws \Exception |
||
106 | * |
||
107 | * @return array |
||
108 | */ |
||
109 | public function appendFormFieldElement(AdminInterface $admin, $subject, $elementId) |
||
110 | { |
||
111 | // child rows marked as toDelete |
||
112 | $toDelete = []; |
||
113 | |||
114 | $formBuilder = $admin->getFormBuilder(); |
||
115 | |||
116 | // get the field element |
||
117 | $childFormBuilder = $this->getChildFormBuilder($formBuilder, $elementId); |
||
118 | |||
119 | if ($childFormBuilder) { |
||
120 | $formData = $admin->getRequest()->get($formBuilder->getName(), []); |
||
121 | if (\array_key_exists($childFormBuilder->getName(), $formData)) { |
||
122 | $formData = $admin->getRequest()->get($formBuilder->getName(), []); |
||
123 | $i = 0; |
||
124 | foreach ($formData[$childFormBuilder->getName()] as $name => &$field) { |
||
125 | $toDelete[$i] = false; |
||
126 | if (\array_key_exists(self::FORM_FIELD_DELETE, $field)) { |
||
127 | $toDelete[$i] = true; |
||
128 | unset($field[self::FORM_FIELD_DELETE]); |
||
129 | } |
||
130 | ++$i; |
||
131 | } |
||
132 | } |
||
133 | $admin->getRequest()->request->set($formBuilder->getName(), $formData); |
||
134 | } |
||
135 | |||
136 | $form = $formBuilder->getForm(); |
||
137 | $form->setData($subject); |
||
138 | $form->handleRequest($admin->getRequest()); |
||
139 | |||
140 | //Child form not found (probably nested one) |
||
141 | //if childFormBuilder was not found resulted in fatal error getName() method call on non object |
||
142 | if (!$childFormBuilder) { |
||
143 | $propertyAccessor = $this->pool->getPropertyAccessor(); |
||
144 | |||
145 | $path = $this->getElementAccessPath($elementId, $subject); |
||
146 | |||
147 | $collection = $propertyAccessor->getValue($subject, $path); |
||
148 | |||
149 | if ($collection instanceof DoctrinePersistentCollection || $collection instanceof PersistentCollection) { |
||
150 | //since doctrine 2.4 |
||
151 | $modelClassName = $collection->getTypeClass()->getName(); |
||
152 | } elseif ($collection instanceof Collection) { |
||
153 | $modelClassName = $this->getEntityClassName($admin, explode('.', preg_replace('#\[\d*?\]#', '', $path))); |
||
154 | } else { |
||
155 | throw new \Exception('unknown collection class'); |
||
156 | } |
||
157 | |||
158 | $collection->add(new $modelClassName()); |
||
159 | $propertyAccessor->setValue($subject, $path, $collection); |
||
160 | |||
161 | $fieldDescription = null; |
||
162 | } else { |
||
163 | // retrieve the FieldDescription |
||
164 | $fieldDescription = $admin->getFormFieldDescription($childFormBuilder->getName()); |
||
165 | |||
166 | try { |
||
167 | $value = $fieldDescription->getValue($form->getData()); |
||
168 | } catch (NoValueException $e) { |
||
169 | $value = null; |
||
170 | } |
||
171 | |||
172 | // retrieve the posted data |
||
173 | $data = $admin->getRequest()->get($formBuilder->getName()); |
||
174 | |||
175 | if (!isset($data[$childFormBuilder->getName()])) { |
||
176 | $data[$childFormBuilder->getName()] = []; |
||
177 | } |
||
178 | |||
179 | $objectCount = null === $value ? 0 : \count($value); |
||
180 | $postCount = \count($data[$childFormBuilder->getName()]); |
||
181 | |||
182 | // add new elements to the subject |
||
183 | while ($objectCount < $postCount) { |
||
184 | // append a new instance into the object |
||
185 | $this->addNewInstance($form->getData(), $fieldDescription); |
||
186 | ++$objectCount; |
||
187 | } |
||
188 | |||
189 | $newInstance = $this->addNewInstance($form->getData(), $fieldDescription); |
||
190 | |||
191 | $associationAdmin = $fieldDescription->getAssociationAdmin(); |
||
192 | $associationAdmin->setSubject($newInstance); |
||
193 | } |
||
194 | |||
195 | $finalForm = $admin->getFormBuilder()->getForm(); |
||
196 | $finalForm->setData($subject); |
||
197 | |||
198 | // bind the data |
||
199 | $finalForm->setData($form->getData()); |
||
200 | |||
201 | // back up delete field |
||
202 | if (\count($toDelete) > 0) { |
||
203 | $i = 0; |
||
204 | foreach ($finalForm->get($childFormBuilder->getName()) as $childField) { |
||
205 | if ($childField->has(self::FORM_FIELD_DELETE)) { |
||
206 | $childField->get(self::FORM_FIELD_DELETE)->setData($toDelete[$i] ?? false); |
||
207 | } |
||
208 | ++$i; |
||
209 | } |
||
210 | } |
||
211 | |||
212 | return [$fieldDescription, $finalForm]; |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * Add a new instance to the related FieldDescriptionInterface value. |
||
217 | * |
||
218 | * @param object $object |
||
219 | * |
||
220 | * @throws \RuntimeException |
||
221 | * |
||
222 | * @return object |
||
223 | */ |
||
224 | public function addNewInstance($object, FieldDescriptionInterface $fieldDescription) |
||
225 | { |
||
226 | $instance = $fieldDescription->getAssociationAdmin()->getNewInstance(); |
||
227 | $mapping = $fieldDescription->getAssociationMapping(); |
||
228 | $parentMappings = $fieldDescription->getParentAssociationMappings(); |
||
229 | |||
230 | $inflector = InflectorFactory::create()->build(); |
||
231 | |||
232 | foreach ($parentMappings as $parentMapping) { |
||
233 | $method = sprintf('get%s', $inflector->classify($parentMapping['fieldName'])); |
||
234 | |||
235 | if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { |
||
236 | /* |
||
237 | * NEXT_MAJOR: Use BadMethodCallException instead |
||
238 | */ |
||
239 | throw new \RuntimeException( |
||
240 | sprintf('Method %s::%s() does not exist.', ClassUtils::getClass($object), $method) |
||
241 | ); |
||
242 | } |
||
243 | |||
244 | $object = $object->$method(); |
||
245 | } |
||
246 | |||
247 | $method = sprintf('add%s', $inflector->classify($mapping['fieldName'])); |
||
248 | |||
249 | if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { |
||
250 | $method = rtrim($method, 's'); |
||
251 | |||
252 | if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { |
||
253 | $method = sprintf('add%s', $inflector->classify($inflector->singularize($mapping['fieldName']))); |
||
254 | |||
255 | if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { |
||
256 | /* |
||
257 | * NEXT_MAJOR: Use BadMethodCallException instead |
||
258 | */ |
||
259 | throw new \RuntimeException( |
||
260 | sprintf('Method %s::%s() does not exist.', ClassUtils::getClass($object), $method) |
||
261 | ); |
||
262 | } |
||
263 | } |
||
264 | } |
||
265 | |||
266 | $object->$method($instance); |
||
267 | |||
268 | return $instance; |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Camelize a string. |
||
273 | * |
||
274 | * NEXT_MAJOR: remove this method. |
||
275 | * |
||
276 | * @static |
||
277 | * |
||
278 | * @param string $property |
||
279 | * |
||
280 | * @return string |
||
281 | * |
||
282 | * @deprecated since sonata-project/admin-bundle 3.1. Use \Doctrine\Inflector\Inflector::classify() instead |
||
283 | */ |
||
284 | public function camelize($property) |
||
297 | |||
298 | /** |
||
299 | * Get access path to element which works with PropertyAccessor. |
||
300 | * |
||
301 | * @param string $elementId expects string in format used in form id field. |
||
302 | * (uniqueIdentifier_model_sub_model or uniqueIdentifier_model_1_sub_model etc.) |
||
303 | * @param mixed $model |
||
304 | * |
||
305 | * @throws \Exception |
||
306 | * |
||
307 | * @return string |
||
308 | */ |
||
309 | public function getElementAccessPath($elementId, $model) |
||
338 | |||
339 | /** |
||
340 | * Recursively find the class name of the admin responsible for the element at the end of an association chain. |
||
341 | */ |
||
342 | protected function getModelClassName(AdminInterface $admin, array $elements): string |
||
346 | |||
347 | /** |
||
348 | * NEXT_MAJOR: Remove this method and move its body to `getModelClassName()`. |
||
349 | * |
||
350 | * @deprecated since sonata-project/admin-bundle 3.x. Use `getModelClassName()` instead. |
||
351 | * |
||
352 | * @param array $elements |
||
353 | * |
||
354 | * @return string |
||
355 | */ |
||
356 | protected function getEntityClassName(AdminInterface $admin, $elements) |
||
375 | } |
||
376 |
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.