Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like ModelStructureProperty 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 ModelStructureProperty, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 99 | class ModelStructureProperty extends StructureProperty |
||
| 100 | { |
||
| 101 | /** |
||
| 102 | * Track the state of loaded metadata for the structure. |
||
| 103 | * |
||
| 104 | * @var boolean |
||
| 105 | */ |
||
| 106 | private $isStructureFinalized = false; |
||
| 107 | |||
| 108 | /** |
||
| 109 | * The metadata interfaces to use as the structure. |
||
| 110 | * |
||
| 111 | * These are paths (PSR-4) to import. |
||
| 112 | * |
||
| 113 | * @var array |
||
| 114 | */ |
||
| 115 | private $structureInterfaces = []; |
||
| 116 | |||
| 117 | /** |
||
| 118 | * Store the property's structure. |
||
| 119 | * |
||
| 120 | * @var MetadataInterface|array|null |
||
| 121 | */ |
||
| 122 | private $structureMetadata; |
||
| 123 | |||
| 124 | /** |
||
| 125 | * Store the property's "terminal" structure. |
||
| 126 | * |
||
| 127 | * This represents the value of "structure_metadata" key on a property definition. |
||
| 128 | * This should always be merged last, after the interfaces are imported. |
||
| 129 | * |
||
| 130 | * @var MetadataInterface|array|null |
||
| 131 | */ |
||
| 132 | private $terminalStructureMetadata; |
||
| 133 | |||
| 134 | /** |
||
| 135 | * Store the property's model prototype. |
||
| 136 | * |
||
| 137 | * @var ArrayAccess|DescribableInterface|null |
||
| 138 | */ |
||
| 139 | private $structurePrototype; |
||
| 140 | |||
| 141 | /** |
||
| 142 | * The object type of the "structure" collection to use. |
||
| 143 | * |
||
| 144 | * @var string |
||
| 145 | */ |
||
| 146 | private $structureModelType; |
||
| 147 | |||
| 148 | /** |
||
| 149 | * The class name of the "structure" collection to use. |
||
| 150 | * |
||
| 151 | * Must be a fully-qualified PHP namespace and an implementation of {@see ArrayAccess}. |
||
| 152 | * |
||
| 153 | * @var string |
||
| 154 | */ |
||
| 155 | private $structureModelClass = StructureModel::class; |
||
| 156 | |||
| 157 | /** |
||
| 158 | * Store the factory instance. |
||
| 159 | * |
||
| 160 | * @var FactoryInterface |
||
| 161 | */ |
||
| 162 | protected $structureModelFactory; |
||
| 163 | |||
| 164 | /** |
||
| 165 | * Retrieve the property's type identifier. |
||
| 166 | * |
||
| 167 | * @return string |
||
| 168 | */ |
||
| 169 | public function type() |
||
| 173 | |||
| 174 | /** |
||
| 175 | * Retrieve the property's structure. |
||
| 176 | * |
||
| 177 | * @return MetadataInterface|null |
||
| 178 | */ |
||
| 179 | public function getStructureMetadata() |
||
| 187 | |||
| 188 | /** |
||
| 189 | * Set the property's structure. |
||
| 190 | * |
||
| 191 | * @param MetadataInterface|array|null $data The property's structure (fields, data). |
||
| 192 | * @throws InvalidArgumentException If the structure is invalid. |
||
| 193 | * @return self |
||
| 194 | */ |
||
| 195 | public function setStructureMetadata($data) |
||
| 221 | |||
| 222 | /** |
||
| 223 | * Retrieve the metadata interfaces used by the property as a structure. |
||
| 224 | * |
||
| 225 | * @return array |
||
| 226 | */ |
||
| 227 | public function getStructureInterfaces() |
||
| 235 | |||
| 236 | /** |
||
| 237 | * Determine if the property has any structure metadata interfaces. |
||
| 238 | * |
||
| 239 | * @return boolean |
||
| 240 | */ |
||
| 241 | public function hasStructureInterfaces() |
||
| 245 | |||
| 246 | /** |
||
| 247 | * Set the given metadata interfaces for the property to use as a structure. |
||
| 248 | * |
||
| 249 | * @param array $interfaces One or more metadata interfaces to use. |
||
| 250 | * @return self |
||
| 251 | */ |
||
| 252 | public function setStructureInterfaces(array $interfaces) |
||
| 260 | |||
| 261 | /** |
||
| 262 | * Add the given metadata interfaces for the property to use as a structure. |
||
| 263 | * |
||
| 264 | * @param array $interfaces One or more metadata interfaces to use. |
||
| 265 | * @return self |
||
| 266 | */ |
||
| 267 | public function addStructureInterfaces(array $interfaces) |
||
| 275 | |||
| 276 | /** |
||
| 277 | * Add the given metadata interfaces for the property to use as a structure. |
||
| 278 | * |
||
| 279 | * @param string $interface A metadata interface to use. |
||
| 280 | * @throws InvalidArgumentException If the interface is not a string. |
||
| 281 | * @return self |
||
| 282 | */ |
||
| 283 | public function addStructureInterface($interface) |
||
| 301 | |||
| 302 | /** |
||
| 303 | * Load the property's structure. |
||
| 304 | * |
||
| 305 | * @return MetadataInterface |
||
| 306 | */ |
||
| 307 | protected function loadStructureMetadata() |
||
| 346 | |||
| 347 | /** |
||
| 348 | * Retrieve a singleton of the structure model for prototyping. |
||
| 349 | * |
||
| 350 | * @return ArrayAccess|DescribableInterface |
||
| 351 | */ |
||
| 352 | public function structureProto() |
||
| 366 | |||
| 367 | /** |
||
| 368 | * Retrieve the default data-model structure class name. |
||
| 369 | * |
||
| 370 | * @return string |
||
| 371 | */ |
||
| 372 | public static function getDefaultStructureModelClass() |
||
| 376 | |||
| 377 | /** |
||
| 378 | * Set the class name of the data-model structure. |
||
| 379 | * |
||
| 380 | * A model type (kebab-case) is converted to a FQN. |
||
| 381 | * |
||
| 382 | * @param string $className The class name of the structure. |
||
| 383 | * @throws InvalidArgumentException If the class name is invalid. |
||
| 384 | * @return self |
||
| 385 | */ |
||
| 386 | protected function setStructureModelClass($className) |
||
| 418 | |||
| 419 | /** |
||
| 420 | * Determine if the property is using a custom data-model. |
||
| 421 | * |
||
| 422 | * @return boolean |
||
| 423 | */ |
||
| 424 | public function hasCustomStructureModelClass() |
||
| 428 | |||
| 429 | /** |
||
| 430 | * Retrieve the class name of the data-model structure. |
||
| 431 | * |
||
| 432 | * @return string |
||
| 433 | */ |
||
| 434 | public function getStructureModelClass() |
||
| 438 | |||
| 439 | /** |
||
| 440 | * Retrieve the class name of the data-model structure. |
||
| 441 | * |
||
| 442 | * @return string |
||
| 443 | */ |
||
| 444 | public function getStructureModelType() |
||
| 452 | |||
| 453 | /** |
||
| 454 | * Convert the given value into a structure. |
||
| 455 | * |
||
| 456 | * Options: |
||
| 457 | * - `default_data` (_boolean_|_array_) — If TRUE, the default data defined |
||
| 458 | * in the structure's metadata is merged. If an array, that is merged. |
||
| 459 | * |
||
| 460 | * @param mixed $val The value to "structurize". |
||
| 461 | * @param array|MetadataInterface $options Optional structure options. |
||
| 462 | * @throws InvalidArgumentException If the options are invalid. |
||
| 463 | * @return ModelInterface|ModelInterface[] |
||
| 464 | */ |
||
| 465 | public function structureVal($val, $options = []) |
||
| 515 | |||
| 516 | /** |
||
| 517 | * Retrieve the structure as a plain array. |
||
| 518 | * |
||
| 519 | * @return array |
||
| 520 | */ |
||
| 521 | public function toStructure() |
||
| 525 | |||
| 526 | /** |
||
| 527 | * @param null|string $model Model ident. |
||
| 528 | * @return ArrayAccess|DescribableInterface|mixed |
||
| 529 | * @throws UnexpectedValueException If the structure is invalid. |
||
| 530 | */ |
||
| 531 | View Code Duplication | public function toModel($model = null) |
|
| 549 | |||
| 550 | /** |
||
| 551 | * PropertyInterface::save(). |
||
| 552 | * @param mixed $val The value, at time of saving. |
||
| 553 | * @return mixed |
||
| 554 | */ |
||
| 555 | public function save($val) |
||
| 582 | |||
| 583 | /** |
||
| 584 | * Inject dependencies from a DI Container. |
||
| 585 | * |
||
| 586 | * @param Container $container A dependencies container instance. |
||
| 587 | * @return void |
||
| 588 | */ |
||
| 589 | protected function setDependencies(Container $container) |
||
| 595 | |||
| 596 | /** |
||
| 597 | * Retrieve the structure model factory. |
||
| 598 | * |
||
| 599 | * @throws RuntimeException If the model factory was not previously set. |
||
| 600 | * @return FactoryInterface |
||
| 601 | */ |
||
| 602 | protected function structureModelFactory() |
||
| 613 | |||
| 614 | /** |
||
| 615 | * Set an structure model factory. |
||
| 616 | * |
||
| 617 | * @param FactoryInterface $factory The model factory, to create objects. |
||
| 618 | * @return self |
||
| 619 | */ |
||
| 620 | private function setStructureModelFactory(FactoryInterface $factory) |
||
| 626 | |||
| 627 | /** |
||
| 628 | * Parse a metadata identifier from given interface. |
||
| 629 | * |
||
| 630 | * Change `\` and `.` to `/` and force lowercase |
||
| 631 | * |
||
| 632 | * @param string $interface A metadata interface to convert. |
||
| 633 | * @return string |
||
| 634 | */ |
||
| 635 | protected function parseStructureInterface($interface) |
||
| 642 | |||
| 643 | /** |
||
| 644 | * Create a new metadata object for structures. |
||
| 645 | * |
||
| 646 | * Similar to {@see \Charcoal\Model\DescribableTrait::createMetadata()}. |
||
| 647 | * |
||
| 648 | * @return MetadataInterface |
||
| 649 | */ |
||
| 650 | protected function createStructureMetadata() |
||
| 655 | |||
| 656 | /** |
||
| 657 | * Retrieve the class name of the metadata object. |
||
| 658 | * |
||
| 659 | * @return string |
||
| 660 | */ |
||
| 661 | protected function getStructureMetadataClass() |
||
| 665 | |||
| 666 | /** |
||
| 667 | * Create a data-model structure. |
||
| 668 | * |
||
| 669 | * @todo Add support for simple {@see ArrayAccess} models. |
||
| 670 | * @throws UnexpectedValueException If the structure is invalid. |
||
| 671 | * @return ArrayAccess |
||
| 672 | */ |
||
| 673 | View Code Duplication | private function createStructureModel() |
|
| 688 | |||
| 689 | /** |
||
| 690 | * Create a data-model structure. |
||
| 691 | * |
||
| 692 | * @param MetadataInterface $metadata The model's definition. |
||
| 693 | * @param array ...$datasets The dataset(s) to modelize. |
||
| 694 | * @throws UnexpectedValueException If the structure is invalid. |
||
| 695 | * @return DescribableInterface |
||
| 696 | */ |
||
| 697 | private function createStructureModelWith( |
||
| 720 | } |
||
| 721 |