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 AbstractProperty 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 AbstractProperty, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 35 | abstract class AbstractProperty implements |
||
| 36 | JsonSerializable, |
||
| 37 | Serializable, |
||
| 38 | PropertyInterface, |
||
| 39 | DescribableInterface, |
||
| 40 | LoggerAwareInterface, |
||
| 41 | StorablePropertyInterface, |
||
| 42 | ValidatableInterface, |
||
| 43 | ViewableInterface |
||
| 44 | { |
||
| 45 | use LoggerAwareTrait; |
||
| 46 | use DescribableTrait; |
||
| 47 | use StorablePropertyTrait; |
||
| 48 | use ValidatableTrait; |
||
| 49 | use ViewableTrait; |
||
| 50 | |||
| 51 | /** |
||
| 52 | * @var string $ident |
||
| 53 | */ |
||
| 54 | private $ident = ''; |
||
| 55 | |||
| 56 | /** |
||
| 57 | * @var mixed $Val |
||
| 58 | */ |
||
| 59 | protected $val; |
||
| 60 | |||
| 61 | /** |
||
| 62 | * @var TranslationString $label |
||
| 63 | */ |
||
| 64 | private $label; |
||
| 65 | |||
| 66 | /** |
||
| 67 | * @var boolean $l10n |
||
| 68 | */ |
||
| 69 | private $l10n = false; |
||
| 70 | |||
| 71 | /** |
||
| 72 | * @var boolean $hidden; |
||
| 73 | */ |
||
| 74 | private $hidden = false; |
||
| 75 | |||
| 76 | /** |
||
| 77 | * @var boolean $multiple |
||
| 78 | */ |
||
| 79 | private $multiple = false; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Array of options for multiple properties |
||
| 83 | * - `separator` (default=",") How the values will be separated in the storage (sql). |
||
| 84 | * - `min` (default=null) The min number of values. If null, <0 or NaN, then this is not taken into consideration. |
||
| 85 | * - `max` (default=null) The max number of values. If null, <0 or NaN, then there is not limit. |
||
| 86 | * @var mixed $multipleOptions |
||
| 87 | */ |
||
| 88 | private $multipleOptions; |
||
| 89 | |||
| 90 | /** |
||
| 91 | * If true, this property *must* have a value |
||
| 92 | * @var boolean $required |
||
| 93 | */ |
||
| 94 | private $required = false; |
||
| 95 | |||
| 96 | /** |
||
| 97 | * Unique properties should not share he same value across 2 objects |
||
| 98 | * @var boolean $unique |
||
| 99 | */ |
||
| 100 | private $unique = false; |
||
| 101 | |||
| 102 | /** |
||
| 103 | * @var boolean $allowNull |
||
| 104 | */ |
||
| 105 | private $allowNull = true; |
||
| 106 | |||
| 107 | /** |
||
| 108 | * Only the storable properties should be saved in storage. |
||
| 109 | * @var boolean $storable |
||
| 110 | */ |
||
| 111 | private $storable = true; |
||
| 112 | |||
| 113 | /** |
||
| 114 | * Inactive properties should be hidden everywhere / unused |
||
| 115 | * @var boolean $active |
||
| 116 | */ |
||
| 117 | private $active = true; |
||
| 118 | |||
| 119 | /** |
||
| 120 | * @var TranslationString $description |
||
| 121 | */ |
||
| 122 | private $description = ''; |
||
| 123 | |||
| 124 | /** |
||
| 125 | * @var TranslationString $_notes |
||
| 126 | */ |
||
| 127 | private $notes = ''; |
||
| 128 | |||
| 129 | /** |
||
| 130 | * Required dependencies: |
||
| 131 | * - `logger` a PSR3-compliant logger. |
||
| 132 | * |
||
| 133 | * @param array $data Optional. Class Dependencies. |
||
| 134 | */ |
||
| 135 | public function __construct(array $data = null) |
||
| 141 | |||
| 142 | /** |
||
| 143 | * |
||
| 144 | * |
||
| 145 | * @return string |
||
| 146 | */ |
||
| 147 | public function __toString() |
||
| 160 | |||
| 161 | /** |
||
| 162 | * Get the "property type" string. |
||
| 163 | * |
||
| 164 | * ## Notes |
||
| 165 | * - Type can not be set, so it must be explicitely provided by each implementing property classes. |
||
| 166 | * |
||
| 167 | * @return string |
||
| 168 | */ |
||
| 169 | abstract public function type(); |
||
| 170 | |||
| 171 | /** |
||
| 172 | * This function takes an array and fill the property with its value. |
||
| 173 | * |
||
| 174 | * This method either calls a setter for each key (`set_{$key}()`) or sets a public member. |
||
| 175 | * |
||
| 176 | * For example, calling with `set_data(['ident'=>$ident])` would call `setIdent($ident)` |
||
| 177 | * becasue `setIdent()` exists. |
||
| 178 | * |
||
| 179 | * But calling with `set_data(['foobar'=>$foo])` would set the `$foobar` member |
||
| 180 | * on the metadata object, because the method `set_foobar()` does not exist. |
||
| 181 | * |
||
| 182 | * @param array $data The property data. |
||
| 183 | * @return AbstractProperty Chainable |
||
| 184 | */ |
||
| 185 | public function setData(array $data) |
||
| 199 | |||
| 200 | /** |
||
| 201 | * @param string $ident The property identifier. |
||
| 202 | * @throws InvalidArgumentException If the ident parameter is not a string. |
||
| 203 | * @return AbstractProperty Chainable |
||
| 204 | */ |
||
| 205 | public function setIdent($ident) |
||
| 215 | |||
| 216 | /** |
||
| 217 | * @throws Exception If trying to access getter before setter. |
||
| 218 | * @return string |
||
| 219 | */ |
||
| 220 | public function ident() |
||
| 229 | |||
| 230 | /** |
||
| 231 | * @param mixed $val The property (raw) value. |
||
| 232 | * @throws InvalidArgumentException If the value is invalid (null or not multiple when supposed to). |
||
| 233 | * @return PropertyInterface Chainable |
||
| 234 | */ |
||
| 235 | public function setVal($val) |
||
| 260 | |||
| 261 | /** |
||
| 262 | * @return mixed |
||
| 263 | */ |
||
| 264 | public function val() |
||
| 268 | |||
| 269 | |||
| 270 | |||
| 271 | /** |
||
| 272 | * @param mixed $val Optional. The value to to convert to display. |
||
| 273 | * @return string |
||
| 274 | */ |
||
| 275 | public function displayVal($val = null) |
||
| 300 | |||
| 301 | /** |
||
| 302 | * @param mixed $label The property label. |
||
| 303 | * @return PropertyInterface Chainable |
||
| 304 | */ |
||
| 305 | public function setLabel($label) |
||
| 310 | |||
| 311 | /** |
||
| 312 | * @return string |
||
| 313 | */ |
||
| 314 | public function label() |
||
| 321 | |||
| 322 | /** |
||
| 323 | * @param boolean $l10n The l10n, or "translatable" flag. |
||
| 324 | * @return PropertyInterface Chainable |
||
| 325 | */ |
||
| 326 | public function setL10n($l10n) |
||
| 331 | |||
| 332 | /** |
||
| 333 | * The l10n flag sets the property as being translatable, meaning the data is held for multple languages. |
||
| 334 | * |
||
| 335 | * @return boolean |
||
| 336 | */ |
||
| 337 | public function l10n() |
||
| 341 | |||
| 342 | /** |
||
| 343 | * @param boolean $hidden The hidden flag. |
||
| 344 | * @return PropertyInterface Chainable |
||
| 345 | */ |
||
| 346 | public function setHidden($hidden) |
||
| 351 | |||
| 352 | /** |
||
| 353 | * @return boolean |
||
| 354 | */ |
||
| 355 | public function hidden() |
||
| 359 | |||
| 360 | /** |
||
| 361 | * @param boolean $multiple The multiple flag. |
||
| 362 | * @return PropertyInterface Chainable |
||
| 363 | */ |
||
| 364 | public function setMultiple($multiple) |
||
| 369 | |||
| 370 | /** |
||
| 371 | * The multiple flags sets the property as being "repeatable", or allow to represent an array of multiple values. |
||
| 372 | * |
||
| 373 | * ## Notes |
||
| 374 | * - The multiple flag can be forced to false (or true) in implementing property class. |
||
| 375 | * - How a multiple behaves also depend on `multipleOptions`. |
||
| 376 | * |
||
| 377 | * @return boolean |
||
| 378 | */ |
||
| 379 | public function multiple() |
||
| 383 | |||
| 384 | /** |
||
| 385 | * Set the multiple options / configuration, when property is `multiple`. |
||
| 386 | * |
||
| 387 | * ## Options structure |
||
| 388 | * - `separator` (string) The separator charactor. |
||
| 389 | * - `min` (integer) The minimum number of values. (0 = no limit). |
||
| 390 | * - `max` (integer) The maximum number of values. (0 = no limit). |
||
| 391 | * |
||
| 392 | * @param array $multipleOptions The property multiple options. |
||
| 393 | * @return PropertyInterface Chainable |
||
| 394 | */ |
||
| 395 | public function setMultipleOptions(array $multipleOptions) |
||
| 402 | |||
| 403 | /** |
||
| 404 | * The options defining the property behavior when the multiple flag is set to true. |
||
| 405 | * |
||
| 406 | * @return array |
||
| 407 | * @see self::defaultMultipleOptions |
||
| 408 | */ |
||
| 409 | public function multipleOptions() |
||
| 416 | |||
| 417 | /** |
||
| 418 | * @return array |
||
| 419 | */ |
||
| 420 | public function defaultMultipleOptions() |
||
| 428 | |||
| 429 | /** |
||
| 430 | * @return string |
||
| 431 | */ |
||
| 432 | public function multipleSeparator() |
||
| 437 | |||
| 438 | /** |
||
| 439 | * @param boolean $allow The property allow null flag. |
||
| 440 | * @return PropertyInterface Chainable |
||
| 441 | */ |
||
| 442 | public function setAllowNull($allow) |
||
| 447 | |||
| 448 | /** |
||
| 449 | * The allow null flag sets the property as being able to be of a "null" value. |
||
| 450 | * |
||
| 451 | * ## Notes |
||
| 452 | * - This flag typically modifies the storage database to also allow null values. |
||
| 453 | * |
||
| 454 | * @return boolean |
||
| 455 | */ |
||
| 456 | public function allowNull() |
||
| 460 | |||
| 461 | /** |
||
| 462 | * @param boolean $required The property required flag. |
||
| 463 | * @return PropertyInterface Chainable |
||
| 464 | */ |
||
| 465 | public function setRequired($required) |
||
| 470 | |||
| 471 | /** |
||
| 472 | * Required flag sets the property as being required, meaning not allowed to be null / empty. |
||
| 473 | * |
||
| 474 | * ## Notes |
||
| 475 | * - The actual meaning of "required" might be different for implementing property class. |
||
| 476 | * |
||
| 477 | * @return boolean |
||
| 478 | */ |
||
| 479 | public function required() |
||
| 483 | |||
| 484 | /** |
||
| 485 | * @param boolean $unique The property unique flag. |
||
| 486 | * @return PropertyInterface Chainable |
||
| 487 | */ |
||
| 488 | public function setUnique($unique) |
||
| 493 | |||
| 494 | /** |
||
| 495 | * @return boolean |
||
| 496 | */ |
||
| 497 | public function unique() |
||
| 501 | |||
| 502 | /** |
||
| 503 | * @param boolean $active The property active flag. Inactive properties should have no effects. |
||
| 504 | * @return PropertyInterface Chainable |
||
| 505 | */ |
||
| 506 | public function setActive($active) |
||
| 511 | |||
| 512 | /** |
||
| 513 | * @return boolean |
||
| 514 | */ |
||
| 515 | public function active() |
||
| 519 | |||
| 520 | /** |
||
| 521 | * @param boolean $storable The storable flag. |
||
| 522 | * @return PropertyInterface Chainable |
||
| 523 | */ |
||
| 524 | public function setStorable($storable) |
||
| 529 | |||
| 530 | /** |
||
| 531 | * @return boolean |
||
| 532 | */ |
||
| 533 | public function storable() |
||
| 537 | |||
| 538 | /** |
||
| 539 | * @param mixed $description The property description. |
||
| 540 | * @return PropertyInterface Chainable |
||
| 541 | */ |
||
| 542 | public function setDescription($description) |
||
| 547 | |||
| 548 | /** |
||
| 549 | * @return string |
||
| 550 | */ |
||
| 551 | public function description() |
||
| 555 | |||
| 556 | /** |
||
| 557 | * @param mixed $notes The property notes. |
||
| 558 | * @return PropertyInterface Chainable |
||
| 559 | */ |
||
| 560 | public function setNotes($notes) |
||
| 565 | |||
| 566 | /** |
||
| 567 | * @return string |
||
| 568 | */ |
||
| 569 | public function notes() |
||
| 573 | |||
| 574 | |||
| 575 | |||
| 576 | /** |
||
| 577 | * The property's default validation methods/ |
||
| 578 | * |
||
| 579 | * - `required` |
||
| 580 | * - `unique` |
||
| 581 | * - `allowNull` |
||
| 582 | * |
||
| 583 | * ## Notes |
||
| 584 | * - Those 3 base validation methods should always be merged, in implementing factory class. |
||
| 585 | * |
||
| 586 | * @return array |
||
| 587 | */ |
||
| 588 | public function validationMethods() |
||
| 596 | |||
| 597 | /** |
||
| 598 | * @return boolean |
||
| 599 | */ |
||
| 600 | public function validateRequired() |
||
| 609 | |||
| 610 | /** |
||
| 611 | * @return boolean |
||
| 612 | */ |
||
| 613 | public function validateUnique() |
||
| 622 | |||
| 623 | /** |
||
| 624 | * @return boolean |
||
| 625 | */ |
||
| 626 | public function validateAllowNull() |
||
| 634 | |||
| 635 | /** |
||
| 636 | * @param string $propertyIdent The ident of the property to retrieve. |
||
| 637 | * @return mixed |
||
| 638 | */ |
||
| 639 | protected function propertyValue($propertyIdent) |
||
| 647 | |||
| 648 | /** |
||
| 649 | * @param array $data Optional. Metadata data. |
||
| 650 | * @return PropertyMetadata |
||
| 651 | */ |
||
| 652 | protected function createMetadata(array $data = null) |
||
| 660 | |||
| 661 | /** |
||
| 662 | * ValidatableTrait > createValidator(). Create a Validator object |
||
| 663 | * |
||
| 664 | * @return ValidatorInterface |
||
| 665 | */ |
||
| 666 | protected function createValidator() |
||
| 671 | |||
| 672 | /** |
||
| 673 | * @param array $data Optional. View data. |
||
| 674 | * @return ViewInterface |
||
| 675 | */ |
||
| 676 | public function createView(array $data = null) |
||
| 686 | |||
| 687 | /** |
||
| 688 | * @return mixed |
||
| 689 | */ |
||
| 690 | abstract public function save(); |
||
| 691 | |||
| 692 | /** |
||
| 693 | * Serializable > serialize() |
||
| 694 | * |
||
| 695 | * @return string |
||
| 696 | */ |
||
| 697 | public function serialize() |
||
| 702 | /** |
||
| 703 | * Serializable > unsierialize() |
||
| 704 | * |
||
| 705 | * @param string $data Serialized data. |
||
| 706 | * @return void |
||
| 707 | */ |
||
| 708 | public function unserialize($data) |
||
| 713 | |||
| 714 | /** |
||
| 715 | * JsonSerializable > jsonSerialize() |
||
| 716 | * |
||
| 717 | * @return mixed |
||
| 718 | */ |
||
| 719 | public function jsonSerialize() |
||
| 723 | |||
| 724 | /** |
||
| 725 | * Allow an object to define how the key getter are called. |
||
| 726 | * |
||
| 727 | * @param string $key The key to get the getter from. |
||
| 728 | * @return string The getter method name, for a given key. |
||
| 729 | */ |
||
| 730 | protected function getter($key) |
||
| 735 | |||
| 736 | /** |
||
| 737 | * Allow an object to define how the key setter are called. |
||
| 738 | * |
||
| 739 | * @param string $key The key to get the setter from. |
||
| 740 | * @return string The setter method name, for a given key. |
||
| 741 | */ |
||
| 742 | protected function setter($key) |
||
| 747 | |||
| 748 | /** |
||
| 749 | * Transform a snake_case string to camelCase. |
||
| 750 | * |
||
| 751 | * @param string $str The snake_case string to camelize. |
||
| 752 | * @return string The camelCase string. |
||
| 753 | */ |
||
| 754 | private function camelize($str) |
||
| 758 | } |
||
| 759 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.