Complex classes like BaseFieldDescription 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 BaseFieldDescription, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 63 | abstract class BaseFieldDescription implements FieldDescriptionInterface |
||
| 64 | { |
||
| 65 | /** |
||
| 66 | * @var string the field name |
||
| 67 | */ |
||
| 68 | protected $name; |
||
| 69 | |||
| 70 | /** |
||
| 71 | * @var string|int the type |
||
| 72 | */ |
||
| 73 | protected $type; |
||
| 74 | |||
| 75 | /** |
||
| 76 | * @var string|int the original mapping type |
||
| 77 | */ |
||
| 78 | protected $mappingType; |
||
| 79 | |||
| 80 | /** |
||
| 81 | * @var string the field name (of the form) |
||
| 82 | */ |
||
| 83 | protected $fieldName; |
||
| 84 | |||
| 85 | /** |
||
| 86 | * @var array the ORM association mapping |
||
| 87 | */ |
||
| 88 | protected $associationMapping = []; |
||
| 89 | |||
| 90 | /** |
||
| 91 | * @var array the ORM field information |
||
| 92 | */ |
||
| 93 | protected $fieldMapping = []; |
||
| 94 | |||
| 95 | /** |
||
| 96 | * @var array the ORM parent mapping association |
||
| 97 | */ |
||
| 98 | protected $parentAssociationMappings = []; |
||
| 99 | |||
| 100 | /** |
||
| 101 | * @var string the template name |
||
| 102 | */ |
||
| 103 | protected $template; |
||
| 104 | |||
| 105 | /** |
||
| 106 | * @var array the option collection |
||
| 107 | */ |
||
| 108 | protected $options = []; |
||
| 109 | |||
| 110 | /** |
||
| 111 | * @var AdminInterface|null the parent Admin instance |
||
| 112 | */ |
||
| 113 | protected $parent; |
||
| 114 | |||
| 115 | /** |
||
| 116 | * @var AdminInterface|null the related admin instance |
||
| 117 | */ |
||
| 118 | protected $admin; |
||
| 119 | |||
| 120 | /** |
||
| 121 | * @var AdminInterface|null the associated admin class if the object is associated to another entity |
||
| 122 | */ |
||
| 123 | protected $associationAdmin; |
||
| 124 | |||
| 125 | /** |
||
| 126 | * @var string the help message to display |
||
| 127 | */ |
||
| 128 | protected $help; |
||
| 129 | |||
| 130 | /** |
||
| 131 | * @var array[] cached object field getters |
||
| 132 | */ |
||
| 133 | private static $fieldGetters = []; |
||
| 134 | |||
| 135 | public function setFieldName($fieldName) |
||
| 139 | |||
| 140 | public function getFieldName() |
||
| 144 | |||
| 145 | public function setName($name) |
||
| 153 | |||
| 154 | public function getName() |
||
| 158 | |||
| 159 | public function getOption($name, $default = null) |
||
| 163 | |||
| 164 | public function setOption($name, $value) |
||
| 168 | |||
| 169 | public function setOptions(array $options) |
||
| 200 | |||
| 201 | public function getOptions() |
||
| 205 | |||
| 206 | public function setTemplate($template) |
||
| 210 | |||
| 211 | public function getTemplate() |
||
| 212 | { |
||
| 213 | if (null !== $this->template && !\is_string($this->template) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) { |
||
| 214 | @trigger_error(sprintf( |
||
|
|
|||
| 215 | 'Returning other type than string or null in method %s() is deprecated since' |
||
| 216 | .' sonata-project/admin-bundle 3.65. It will return only those types in version 4.0.', |
||
| 217 | __METHOD__ |
||
| 218 | ), E_USER_DEPRECATED); |
||
| 219 | } |
||
| 220 | |||
| 221 | return $this->template; |
||
| 222 | } |
||
| 223 | |||
| 224 | public function setType($type) |
||
| 225 | { |
||
| 226 | $this->type = $type; |
||
| 227 | } |
||
| 228 | |||
| 229 | public function getType() |
||
| 230 | { |
||
| 231 | return $this->type; |
||
| 232 | } |
||
| 233 | |||
| 234 | public function setParent(AdminInterface $parent) |
||
| 235 | { |
||
| 236 | $this->parent = $parent; |
||
| 237 | } |
||
| 238 | |||
| 239 | public function getParent() |
||
| 240 | { |
||
| 241 | if (!$this->hasParent()) { |
||
| 242 | @trigger_error( |
||
| 243 | sprintf( |
||
| 244 | 'Calling %s() when there is no parent is deprecated since sonata-project/admin-bundle 3.69' |
||
| 245 | .' and will throw an exception in 4.0. Use %s::hasParent() to know if there is a parent.', |
||
| 246 | __METHOD__, |
||
| 247 | __CLASS__ |
||
| 248 | ), |
||
| 249 | E_USER_DEPRECATED |
||
| 250 | ); |
||
| 251 | // NEXT_MAJOR : remove the previous `trigger_error()` call, uncomment the following exception and declare AdminInterface as return type |
||
| 252 | // throw new \LogicException(sprintf('%s has no parent.', static::class)); |
||
| 253 | } |
||
| 254 | |||
| 255 | return $this->parent; |
||
| 256 | } |
||
| 257 | |||
| 258 | public function hasParent() |
||
| 259 | { |
||
| 260 | return null !== $this->parent; |
||
| 261 | } |
||
| 262 | |||
| 263 | public function getAssociationMapping() |
||
| 264 | { |
||
| 265 | return $this->associationMapping; |
||
| 266 | } |
||
| 267 | |||
| 268 | public function getFieldMapping() |
||
| 269 | { |
||
| 270 | return $this->fieldMapping; |
||
| 271 | } |
||
| 272 | |||
| 273 | public function getParentAssociationMappings() |
||
| 274 | { |
||
| 275 | return $this->parentAssociationMappings; |
||
| 276 | } |
||
| 277 | |||
| 278 | public function setAssociationAdmin(AdminInterface $associationAdmin) |
||
| 279 | { |
||
| 280 | $this->associationAdmin = $associationAdmin; |
||
| 281 | $this->associationAdmin->setParentFieldDescription($this); |
||
| 282 | } |
||
| 283 | |||
| 284 | public function getAssociationAdmin() |
||
| 285 | { |
||
| 286 | if (!$this->hasAssociationAdmin()) { |
||
| 287 | @trigger_error( |
||
| 288 | sprintf( |
||
| 289 | 'Calling %s() when there is no association admin is deprecated since' |
||
| 290 | .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.' |
||
| 291 | .' Use %s::hasAssociationAdmin() to know if there is an association admin.', |
||
| 292 | __METHOD__, |
||
| 293 | __CLASS__ |
||
| 294 | ), |
||
| 295 | E_USER_DEPRECATED |
||
| 296 | ); |
||
| 297 | // NEXT_MAJOR : remove the previous `trigger_error()` call, uncomment the following exception and declare AdminInterface as return type |
||
| 298 | // throw new \LogicException(sprintf('%s has no association admin.', static::class)); |
||
| 299 | } |
||
| 300 | |||
| 301 | return $this->associationAdmin; |
||
| 302 | } |
||
| 303 | |||
| 304 | public function hasAssociationAdmin() |
||
| 305 | { |
||
| 306 | return null !== $this->associationAdmin; |
||
| 307 | } |
||
| 308 | |||
| 309 | public function getFieldValue($object, $fieldName) |
||
| 310 | { |
||
| 311 | if ($this->isVirtual() || null === $object) { |
||
| 312 | return null; |
||
| 313 | } |
||
| 314 | |||
| 315 | $getters = []; |
||
| 316 | $parameters = []; |
||
| 317 | |||
| 318 | // prefer method name given in the code option |
||
| 319 | if ($this->getOption('code')) { |
||
| 320 | $getters[] = $this->getOption('code'); |
||
| 321 | } |
||
| 322 | // parameters for the method given in the code option |
||
| 323 | if ($this->getOption('parameters')) { |
||
| 324 | $parameters = $this->getOption('parameters'); |
||
| 325 | } |
||
| 326 | |||
| 327 | if (\is_string($fieldName) && '' !== $fieldName) { |
||
| 328 | if ($this->hasCachedFieldGetter($object, $fieldName)) { |
||
| 329 | return $this->callCachedGetter($object, $fieldName, $parameters); |
||
| 330 | } |
||
| 331 | |||
| 332 | $camelizedFieldName = InflectorFactory::create()->build()->classify($fieldName); |
||
| 333 | |||
| 334 | $getters[] = sprintf('get%s', $camelizedFieldName); |
||
| 335 | $getters[] = sprintf('is%s', $camelizedFieldName); |
||
| 336 | $getters[] = sprintf('has%s', $camelizedFieldName); |
||
| 337 | } |
||
| 338 | |||
| 339 | foreach ($getters as $getter) { |
||
| 340 | if (method_exists($object, $getter) && \is_callable([$object, $getter])) { |
||
| 341 | $this->cacheFieldGetter($object, $fieldName, 'getter', $getter); |
||
| 342 | |||
| 343 | return $object->{$getter}(...$parameters); |
||
| 344 | } |
||
| 345 | } |
||
| 346 | |||
| 347 | if (method_exists($object, '__call')) { |
||
| 348 | $this->cacheFieldGetter($object, $fieldName, 'call'); |
||
| 349 | |||
| 350 | return $object->{$fieldName}(...$parameters); |
||
| 351 | } |
||
| 352 | |||
| 353 | if (isset($object->{$fieldName})) { |
||
| 354 | $this->cacheFieldGetter($object, $fieldName, 'var'); |
||
| 355 | |||
| 356 | return $object->{$fieldName}; |
||
| 357 | } |
||
| 358 | |||
| 359 | throw new NoValueException(sprintf( |
||
| 360 | 'Neither the property "%s" nor one of the methods "%s()" exist and have public access in class "%s".', |
||
| 361 | $this->getName(), |
||
| 362 | implode('()", "', $getters), |
||
| 363 | \get_class($object) |
||
| 364 | )); |
||
| 365 | } |
||
| 366 | |||
| 367 | public function setAdmin(AdminInterface $admin) |
||
| 368 | { |
||
| 369 | $this->admin = $admin; |
||
| 370 | } |
||
| 371 | |||
| 372 | public function getAdmin() |
||
| 373 | { |
||
| 374 | if (!$this->hasAdmin()) { |
||
| 375 | @trigger_error( |
||
| 376 | sprintf( |
||
| 377 | 'Calling %s() when there is no admin is deprecated since sonata-project/admin-bundle 3.69' |
||
| 378 | .' and will throw an exception in 4.0. Use %s::hasAdmin() to know if there is an admin.', |
||
| 379 | __METHOD__, |
||
| 380 | __CLASS__ |
||
| 381 | ), |
||
| 382 | E_USER_DEPRECATED |
||
| 383 | ); |
||
| 384 | // NEXT_MAJOR : remove the previous `trigger_error()` call, uncomment the following exception and declare AdminInterface as return type |
||
| 385 | // throw new \LogicException(sprintf('%s has no admin.', static::class)); |
||
| 386 | } |
||
| 387 | |||
| 388 | return $this->admin; |
||
| 389 | } |
||
| 390 | |||
| 391 | public function hasAdmin() |
||
| 392 | { |
||
| 393 | return null !== $this->admin; |
||
| 394 | } |
||
| 395 | |||
| 396 | public function mergeOption($name, array $options = []) |
||
| 397 | { |
||
| 398 | if (!isset($this->options[$name])) { |
||
| 399 | $this->options[$name] = []; |
||
| 400 | } |
||
| 401 | |||
| 402 | if (!\is_array($this->options[$name])) { |
||
| 403 | throw new \RuntimeException(sprintf('The key `%s` does not point to an array value', $name)); |
||
| 404 | } |
||
| 405 | |||
| 406 | $this->options[$name] = array_merge($this->options[$name], $options); |
||
| 407 | } |
||
| 408 | |||
| 409 | public function mergeOptions(array $options = []) |
||
| 410 | { |
||
| 411 | $this->setOptions(array_merge_recursive($this->options, $options)); |
||
| 412 | } |
||
| 413 | |||
| 414 | public function setMappingType($mappingType) |
||
| 415 | { |
||
| 416 | $this->mappingType = $mappingType; |
||
| 417 | } |
||
| 418 | |||
| 419 | public function getMappingType() |
||
| 420 | { |
||
| 421 | return $this->mappingType; |
||
| 422 | } |
||
| 423 | |||
| 424 | /** |
||
| 425 | * Camelize a string. |
||
| 426 | * |
||
| 427 | * NEXT_MAJOR: remove this method. |
||
| 428 | * |
||
| 429 | * @static |
||
| 430 | * |
||
| 431 | * @param string $property |
||
| 432 | * |
||
| 433 | * @return string |
||
| 434 | * |
||
| 435 | * @deprecated since sonata-project/admin-bundle 3.1. Use \Doctrine\Inflector\Inflector::classify() instead |
||
| 436 | */ |
||
| 437 | public static function camelize($property) |
||
| 438 | { |
||
| 439 | @trigger_error(sprintf( |
||
| 440 | 'The %s method is deprecated since 3.1 and will be removed in 4.0. Use %s::classify() instead.', |
||
| 441 | __METHOD__, |
||
| 442 | Inflector::class |
||
| 443 | ), E_USER_DEPRECATED); |
||
| 444 | |||
| 445 | return InflectorFactory::create()->build()->classify($property); |
||
| 446 | } |
||
| 447 | |||
| 448 | /** |
||
| 449 | * Defines the help message. |
||
| 450 | * |
||
| 451 | * @param string $help |
||
| 452 | */ |
||
| 453 | public function setHelp($help) |
||
| 457 | |||
| 458 | public function getHelp() |
||
| 462 | |||
| 463 | public function getHelpTranslationParameters() |
||
| 464 | { |
||
| 465 | return $this->getOption('help_translation_parameters'); |
||
| 466 | } |
||
| 467 | |||
| 468 | public function getLabelTranslationParameters() |
||
| 469 | { |
||
| 470 | return $this->getOption('label_translation_parameters'); |
||
| 471 | } |
||
| 472 | |||
| 473 | public function getLabel() |
||
| 474 | { |
||
| 475 | $label = $this->getOption('label'); |
||
| 476 | if (null !== $label && false !== $label && !\is_string($label) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) { |
||
| 477 | @trigger_error(sprintf( |
||
| 478 | 'Returning other type than string, false or null in method %s() is deprecated since' |
||
| 479 | .' sonata-project/admin-bundle 3.65. It will return only those types in version 4.0.', |
||
| 480 | __METHOD__ |
||
| 481 | ), E_USER_DEPRECATED); |
||
| 482 | } |
||
| 483 | |||
| 484 | return $label; |
||
| 485 | } |
||
| 486 | |||
| 487 | public function isSortable() |
||
| 491 | |||
| 492 | public function getSortFieldMapping() |
||
| 496 | |||
| 497 | public function getSortParentAssociationMapping() |
||
| 501 | |||
| 502 | public function getTranslationDomain() |
||
| 506 | |||
| 507 | /** |
||
| 508 | * Return true if field is virtual. |
||
| 509 | * |
||
| 510 | * @return bool |
||
| 511 | */ |
||
| 512 | public function isVirtual() |
||
| 516 | |||
| 517 | private function getFieldGetterKey($object, ?string $fieldName): ?string |
||
| 533 | |||
| 534 | private function hasCachedFieldGetter($object, string $fieldName): bool |
||
| 540 | |||
| 541 | private function callCachedGetter($object, string $fieldName, array $parameters = []) |
||
| 552 | |||
| 553 | private function cacheFieldGetter($object, ?string $fieldName, string $method, ?string $getter = null): void |
||
| 565 | } |
||
| 566 |
If you suppress an error, we recommend checking for the error condition explicitly: