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 |
||
44 | abstract class AbstractProperty extends AbstractEntity implements |
||
45 | PropertyInterface, |
||
46 | DescribableInterface, |
||
47 | DescribablePropertyInterface, |
||
48 | LoggerAwareInterface, |
||
49 | StorablePropertyInterface, |
||
50 | ValidatableInterface |
||
51 | { |
||
52 | use LoggerAwareTrait; |
||
53 | use DescribableTrait; |
||
54 | use DescribablePropertyTrait; |
||
55 | use StorablePropertyTrait; |
||
56 | use TranslatorAwareTrait; |
||
57 | use ValidatableTrait; |
||
58 | |||
59 | const DEFAULT_L10N = false; |
||
60 | const DEFAULT_MULTIPLE = false; |
||
61 | const DEFAULT_HIDDEN = false; |
||
62 | const DEFAULT_UNIQUE = false; |
||
63 | const DEFAULT_REQUIRED = false; |
||
64 | const DEFAULT_ALLOW_NULL = true; |
||
65 | const DEFAULT_STORABLE = true; |
||
66 | const DEFAULT_ACTIVE = true; |
||
67 | |||
68 | /** |
||
69 | * @var string |
||
70 | */ |
||
71 | private $ident = ''; |
||
72 | |||
73 | /** |
||
74 | * @var mixed |
||
75 | */ |
||
76 | protected $val; |
||
77 | |||
78 | /** |
||
79 | * @var Translation|null |
||
80 | */ |
||
81 | private $label; |
||
82 | |||
83 | /** |
||
84 | * @var boolean |
||
85 | */ |
||
86 | private $l10n = self::DEFAULT_L10N; |
||
87 | |||
88 | /** |
||
89 | * @var boolean |
||
90 | */ |
||
91 | private $multiple = self::DEFAULT_MULTIPLE; |
||
92 | |||
93 | /** |
||
94 | * Array of options for multiple properties |
||
95 | * - `separator` (default=",") How the values will be separated in the storage (sql). |
||
96 | * - `min` (default=null) The min number of values. If null, <0 or NaN, then this is not taken into consideration. |
||
97 | * - `max` (default=null) The max number of values. If null, <0 or NaN, then there is not limit. |
||
98 | * @var array|null |
||
99 | */ |
||
100 | private $multipleOptions; |
||
101 | |||
102 | /** |
||
103 | * @var boolean |
||
104 | */ |
||
105 | private $hidden = self::DEFAULT_HIDDEN; |
||
106 | |||
107 | /** |
||
108 | * If true, this property *must* have a value |
||
109 | * @var boolean |
||
110 | */ |
||
111 | private $required = self::DEFAULT_REQUIRED; |
||
112 | |||
113 | /** |
||
114 | * Unique properties should not share he same value across 2 objects |
||
115 | * @var boolean |
||
116 | */ |
||
117 | private $unique = self::DEFAULT_UNIQUE; |
||
118 | |||
119 | /** |
||
120 | * @var boolean $allowNull |
||
121 | */ |
||
122 | private $allowNull = self::DEFAULT_ALLOW_NULL; |
||
123 | |||
124 | /** |
||
125 | * Only the storable properties should be saved in storage. |
||
126 | * @var boolean |
||
127 | */ |
||
128 | private $storable = self::DEFAULT_STORABLE; |
||
129 | |||
130 | /** |
||
131 | * Inactive properties should be hidden everywhere / unused |
||
132 | * @var boolean |
||
133 | */ |
||
134 | private $active = self::DEFAULT_ACTIVE; |
||
135 | |||
136 | /** |
||
137 | * @var Translation|null |
||
138 | */ |
||
139 | private $description; |
||
140 | |||
141 | /** |
||
142 | * @var Translation|null |
||
143 | */ |
||
144 | private $notes; |
||
145 | |||
146 | /** |
||
147 | * @var array|null |
||
148 | */ |
||
149 | protected $viewOptions; |
||
150 | |||
151 | /** |
||
152 | * @var string |
||
153 | */ |
||
154 | protected $displayType; |
||
155 | |||
156 | /** |
||
157 | * @var PDO |
||
158 | */ |
||
159 | protected $pdo; |
||
160 | |||
161 | /** |
||
162 | * Required dependencies: |
||
163 | * - `logger` a PSR3-compliant logger. |
||
164 | * - `pdo` a PDO database. |
||
165 | * - `translator` a Charcoal Translator (based on Symfony's). |
||
166 | * |
||
167 | * @param array $data Optional. Class Dependencies. |
||
168 | */ |
||
169 | public function __construct(array $data = null) |
||
170 | { |
||
171 | $this->setLogger($data['logger']); |
||
172 | $this->setPdo($data['database']); |
||
173 | $this->setTranslator($data['translator']); |
||
174 | |||
175 | // Optional DescribableInterface dependencies |
||
176 | if (isset($data['property_factory'])) { |
||
177 | $this->setPropertyFactory($data['property_factory']); |
||
178 | } |
||
179 | |||
180 | if (isset($data['metadata_loader'])) { |
||
181 | $this->setMetadataLoader($data['metadata_loader']); |
||
182 | } |
||
183 | |||
184 | // DI Container can optionally be set in property constructor. |
||
185 | if (isset($data['container'])) { |
||
186 | $this->setDependencies($data['container']); |
||
187 | } |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * @return string |
||
192 | * @deprecated |
||
193 | */ |
||
194 | public function __toString() |
||
195 | { |
||
196 | $val = $this->val(); |
||
|
|||
197 | if (is_string($val)) { |
||
198 | return $val; |
||
199 | } else { |
||
200 | if (is_object($val)) { |
||
201 | return (string)$val; |
||
202 | } else { |
||
203 | return ''; |
||
204 | } |
||
205 | } |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * Get the "property type" string. |
||
210 | * |
||
211 | * ## Notes |
||
212 | * - Type can not be set, so it must be explicitely provided by each implementing property classes. |
||
213 | * |
||
214 | * @return string |
||
215 | */ |
||
216 | abstract public function type(); |
||
217 | |||
218 | /** |
||
219 | * Set the property's identifier. |
||
220 | * |
||
221 | * @param string $ident The property identifier. |
||
222 | * @throws InvalidArgumentException If the identifier is not a string. |
||
223 | * @return self |
||
224 | */ |
||
225 | public function setIdent($ident) |
||
226 | { |
||
227 | if (!is_string($ident)) { |
||
228 | throw new InvalidArgumentException( |
||
229 | 'Ident needs to be string.' |
||
230 | ); |
||
231 | } |
||
232 | $this->ident = $ident; |
||
233 | |||
234 | return $this; |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Retrieve the property's identifier. |
||
239 | * |
||
240 | * @return string |
||
241 | */ |
||
242 | public function getIdent() |
||
243 | { |
||
244 | return $this->ident; |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * Legacy support of ident() instead of getIdent(). |
||
249 | * |
||
250 | * @return string |
||
251 | */ |
||
252 | public function ident() |
||
253 | { |
||
254 | return $this->getIdent(); |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * Retrieve the property's localized identifier. |
||
259 | * |
||
260 | * @param string|null $lang The language code to return the identifier with. |
||
261 | * @throws LogicException If the property is not multilingual. |
||
262 | * @throws RuntimeException If the property has no identifier. |
||
263 | * @throws InvalidArgumentException If the language code is invalid. |
||
264 | * @return string |
||
265 | */ |
||
266 | public function l10nIdent($lang = null) |
||
267 | { |
||
268 | if ($this->ident === '') { |
||
269 | throw new RuntimeException('Missing Property Identifier'); |
||
270 | } |
||
271 | |||
272 | if (!$this['l10n']) { |
||
273 | throw new LogicException(sprintf( |
||
274 | 'Property "%s" is not multilingual', |
||
275 | $this->ident |
||
276 | )); |
||
277 | } |
||
278 | |||
279 | View Code Duplication | if ($lang === null) { |
|
280 | $lang = $this->translator()->getLocale(); |
||
281 | } elseif (!is_string($lang)) { |
||
282 | throw new InvalidArgumentException(sprintf( |
||
283 | 'Language must be a string for Property "%s"', |
||
284 | $this->ident |
||
285 | )); |
||
286 | } |
||
287 | |||
288 | return sprintf('%1$s_%2$s', $this->ident, $lang); |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Set the property's value. |
||
293 | * |
||
294 | * @deprecated |
||
295 | * |
||
296 | * @param mixed $val The property (raw) value. |
||
297 | * @return self |
||
298 | */ |
||
299 | final public function setVal($val) |
||
305 | |||
306 | /** |
||
307 | * Clear the property's value. |
||
308 | * |
||
309 | * @deprecated |
||
310 | * |
||
311 | * @return self |
||
312 | */ |
||
313 | final public function clearVal() |
||
319 | |||
320 | /** |
||
321 | * Retrieve the property's value. |
||
322 | * |
||
323 | * @deprecated |
||
324 | * |
||
325 | * @return mixed |
||
326 | */ |
||
327 | final public function val() |
||
331 | |||
332 | /** |
||
333 | * Parse the given value. |
||
334 | * |
||
335 | * > Note: the base method (defined here) returns the current value intact. |
||
336 | * > Other properties can reimplement this method to parse their values, |
||
337 | * > such as {@see \Charcoal\Property\ObjectProperty::parseVal()} who could parse objects into object IDs. |
||
338 | * |
||
339 | * @param mixed $val The value to be parsed (normalized). |
||
340 | * @throws InvalidArgumentException If the value does not match property settings. |
||
341 | * @return mixed Returns the parsed value. |
||
342 | */ |
||
343 | final public function parseVal($val) |
||
385 | |||
386 | /** |
||
387 | * @param mixed $val A single value to parse. |
||
388 | * @return mixed The parsed value. |
||
389 | */ |
||
390 | public function parseOne($val) |
||
394 | |||
395 | /** |
||
396 | * @param mixed $val Optional. The value to to convert for input. |
||
397 | * @param array $options Optional input options. |
||
398 | * @return string |
||
399 | */ |
||
400 | public function inputVal($val, array $options = []) |
||
435 | |||
436 | /** |
||
437 | * @param mixed $val The value to to convert for display. |
||
438 | * @param array $options Optional display options. |
||
439 | * @return string |
||
440 | */ |
||
441 | public function displayVal($val, array $options = []) |
||
477 | |||
478 | /** |
||
479 | * @param mixed $label The property label. |
||
480 | * @return self |
||
481 | */ |
||
482 | public function setLabel($label) |
||
488 | |||
489 | /** |
||
490 | * @return Translation |
||
491 | */ |
||
492 | public function getLabel() |
||
500 | |||
501 | /** |
||
502 | * @param boolean $l10n The l10n, or "translatable" flag. |
||
503 | * @return self |
||
504 | */ |
||
505 | public function setL10n($l10n) |
||
511 | |||
512 | /** |
||
513 | * The l10n flag sets the property as being translatable, meaning the data is held for multple languages. |
||
514 | * |
||
515 | * @return boolean |
||
516 | */ |
||
517 | public function getL10n() |
||
521 | |||
522 | /** |
||
523 | * @param mixed $val A L10N variable. |
||
524 | * @return Translation The translation value. |
||
525 | */ |
||
526 | public function parseValAsL10n($val) |
||
530 | |||
531 | /** |
||
532 | * @param boolean $hidden The hidden flag. |
||
533 | * @return self |
||
534 | */ |
||
535 | public function setHidden($hidden) |
||
541 | |||
542 | /** |
||
543 | * @return boolean |
||
544 | */ |
||
545 | public function getHidden() |
||
549 | |||
550 | /** |
||
551 | * Set whether this property accepts multiple values or a single value. |
||
552 | * |
||
553 | * @param boolean $multiple The multiple flag. |
||
554 | * @return self |
||
555 | */ |
||
556 | public function setMultiple($multiple) |
||
573 | |||
574 | /** |
||
575 | * Determine if this property accepts multiple values or a single value. |
||
576 | * |
||
577 | * The multiple flag sets the property as being "repeatable", or allow to represent an array of multiple values. |
||
578 | * |
||
579 | * ## Notes |
||
580 | * - The multiple flag can be forced to false (or true) in implementing property class. |
||
581 | * - How a multiple behaves also depend on `multipleOptions`. |
||
582 | * |
||
583 | * @return boolean |
||
584 | */ |
||
585 | public function getMultiple() |
||
589 | |||
590 | /** |
||
591 | * Set the multiple options / configuration, when property is `multiple`. |
||
592 | * |
||
593 | * ## Options structure |
||
594 | * - `separator` (string) The separator charactor. |
||
595 | * - `min` (integer) The minimum number of values. (0 = no limit). |
||
596 | * - `max` (integer) The maximum number of values. (0 = no limit). |
||
597 | * |
||
598 | * @param array $multipleOptions The property multiple options. |
||
599 | * @return self |
||
600 | */ |
||
601 | public function setMultipleOptions(array $multipleOptions) |
||
609 | |||
610 | /** |
||
611 | * The options defining the property behavior when the multiple flag is set to true. |
||
612 | * |
||
613 | * @see self::defaultMultipleOptions |
||
614 | * @param string|null $key Optional setting to retrieve from the options. |
||
615 | * @return array|mixed|null |
||
616 | */ |
||
617 | public function getMultipleOptions($key = null) |
||
633 | |||
634 | /** |
||
635 | * Output the property multiple options as json. |
||
636 | * |
||
637 | * @return string |
||
638 | */ |
||
639 | public function multipleOptionsAsJson() |
||
643 | |||
644 | /** |
||
645 | * Retrieve the default settings for a multi-value property. |
||
646 | * |
||
647 | * @return array |
||
648 | */ |
||
649 | public function defaultMultipleOptions() |
||
657 | |||
658 | /** |
||
659 | * Retrieve the value delimiter for a multi-value property. |
||
660 | * |
||
661 | * @return string |
||
662 | */ |
||
663 | public function multipleSeparator() |
||
667 | |||
668 | /** |
||
669 | * @param mixed $val A multi-value variable. |
||
670 | * @return array The array of values. |
||
671 | */ |
||
672 | public function parseValAsMultiple($val) |
||
688 | |||
689 | /** |
||
690 | * @param boolean $allow The property allow null flag. |
||
691 | * @return self |
||
692 | */ |
||
693 | public function setAllowNull($allow) |
||
699 | |||
700 | /** |
||
701 | * The allow null flag sets the property as being able to be of a "null" value. |
||
702 | * |
||
703 | * ## Notes |
||
704 | * - This flag typically modifies the storage database to also allow null values. |
||
705 | * |
||
706 | * @return boolean |
||
707 | */ |
||
708 | public function getAllowNull() |
||
712 | |||
713 | /** |
||
714 | * @param boolean $required The property required flag. |
||
715 | * @return self |
||
716 | */ |
||
717 | public function setRequired($required) |
||
723 | |||
724 | /** |
||
725 | * Required flag sets the property as being required, meaning not allowed to be null / empty. |
||
726 | * |
||
727 | * ## Notes |
||
728 | * - The actual meaning of "required" might be different for implementing property class. |
||
729 | * |
||
730 | * @return boolean |
||
731 | */ |
||
732 | public function getRequired() |
||
736 | |||
737 | /** |
||
738 | * @param boolean $unique The property unique flag. |
||
739 | * @return self |
||
740 | */ |
||
741 | public function setUnique($unique) |
||
747 | |||
748 | /** |
||
749 | * @return boolean |
||
750 | */ |
||
751 | public function getUnique() |
||
755 | |||
756 | /** |
||
757 | * @param boolean $active The property active flag. Inactive properties should have no effects. |
||
758 | * @return self |
||
759 | */ |
||
760 | public function setActive($active) |
||
766 | |||
767 | /** |
||
768 | * @return boolean |
||
769 | */ |
||
770 | public function getActive() |
||
774 | |||
775 | /** |
||
776 | * Legacy support of active() instead of getActive(). |
||
777 | * |
||
778 | * @return string |
||
779 | */ |
||
780 | public function active() |
||
784 | |||
785 | /** |
||
786 | * @param boolean $storable The storable flag. |
||
787 | * @return self |
||
788 | */ |
||
789 | public function setStorable($storable) |
||
795 | |||
796 | /** |
||
797 | * @return boolean |
||
798 | */ |
||
799 | public function getStorable() |
||
803 | |||
804 | /** |
||
805 | * @param mixed $description The property description. |
||
806 | * @return self |
||
807 | */ |
||
808 | public function setDescription($description) |
||
813 | |||
814 | /** |
||
815 | * @return Translation|null |
||
816 | */ |
||
817 | public function getDescription() |
||
821 | |||
822 | /** |
||
823 | * @param mixed $notes The property notes. |
||
824 | * @return self |
||
825 | */ |
||
826 | public function setNotes($notes) |
||
831 | |||
832 | /** |
||
833 | * @return Translation|null |
||
834 | */ |
||
835 | public function getNotes() |
||
839 | |||
840 | /** |
||
841 | * The property's default validation methods. |
||
842 | * |
||
843 | * - `required` |
||
844 | * - `unique` |
||
845 | * - `allowNull` |
||
846 | * |
||
847 | * ## Notes |
||
848 | * - Those 3 base validation methods should always be merged, in implementing factory class. |
||
849 | * |
||
850 | * @return string[] |
||
851 | */ |
||
852 | public function validationMethods() |
||
860 | |||
861 | /** |
||
862 | * @return boolean |
||
863 | */ |
||
864 | public function validateRequired() |
||
874 | |||
875 | /** |
||
876 | * @return boolean |
||
877 | */ |
||
878 | public function validateUnique() |
||
887 | |||
888 | /** |
||
889 | * @return boolean |
||
890 | */ |
||
891 | public function validateAllowNull() |
||
901 | |||
902 | /** |
||
903 | * @param mixed $val The value, at time of saving. |
||
904 | * @return mixed |
||
905 | */ |
||
906 | public function save($val) |
||
911 | |||
912 | /** |
||
913 | * @param string $type The display type. |
||
914 | * @return self |
||
915 | */ |
||
916 | public function setDisplayType($type) |
||
922 | |||
923 | /** |
||
924 | * @return string |
||
925 | */ |
||
926 | public function getDisplayType() |
||
942 | |||
943 | /** |
||
944 | * View options. |
||
945 | * @param string $ident The display ident (ex: charcoal/admin/property/display/text). |
||
946 | * @return array Should ALWAYS be an array. |
||
947 | */ |
||
948 | final public function viewOptions($ident = null) |
||
968 | |||
969 | /** |
||
970 | * Set view options for both display and input |
||
971 | * |
||
972 | * @param array $viewOpts View options. |
||
973 | * @return self |
||
974 | */ |
||
975 | final public function setViewOptions(array $viewOpts = []) |
||
981 | |||
982 | /** |
||
983 | * @param Container $container A Pimple DI container. |
||
984 | * @return void |
||
985 | */ |
||
986 | protected function setDependencies(Container $container) |
||
991 | |||
992 | /** |
||
993 | * Attempt to get the multilingual value in the requested language. |
||
994 | * |
||
995 | * @param mixed $val The multilingual value to lookup. |
||
996 | * @param mixed $lang The language to return the value in. |
||
997 | * @return string|null |
||
998 | */ |
||
999 | protected function l10nVal($val, $lang = null) |
||
1015 | |||
1016 | /** |
||
1017 | * Create a new metadata object. |
||
1018 | * |
||
1019 | * @param array $data Optional metadata to merge on the object. |
||
1020 | * @see DescribableTrait::createMetadata() |
||
1021 | * @return PropertyMetadata |
||
1022 | */ |
||
1023 | protected function createMetadata(array $data = null) |
||
1028 | |||
1029 | /** |
||
1030 | * Retrieve the class name of the metadata object. |
||
1031 | * |
||
1032 | * @see DescribableTrait::metadataClass() |
||
1033 | * @return string |
||
1034 | */ |
||
1035 | protected function metadataClass() |
||
1039 | |||
1040 | /** |
||
1041 | * Create a Validator object |
||
1042 | * |
||
1043 | * @see ValidatableTrait::createValidator() |
||
1044 | * @return ValidatorInterface |
||
1045 | */ |
||
1046 | protected function createValidator() |
||
1052 | |||
1053 | /** |
||
1054 | * @param PDO $pdo The database connection (PDO) instance. |
||
1055 | * @return void |
||
1056 | */ |
||
1057 | private function setPdo(PDO $pdo) |
||
1061 | } |
||
1062 |
This method has been deprecated.