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 class name of the "structure" collection to use. |
||
143 | * |
||
144 | * Must be a fully-qualified PHP namespace and an implementation of {@see ArrayAccess}. |
||
145 | * |
||
146 | * @var string |
||
147 | */ |
||
148 | private $structureModelClass = StructureModel::class; |
||
149 | |||
150 | /** |
||
151 | * Store the factory instance. |
||
152 | * |
||
153 | * @var FactoryInterface |
||
154 | */ |
||
155 | protected $structureModelFactory; |
||
156 | |||
157 | /** |
||
158 | * Retrieve the property's type identifier. |
||
159 | * |
||
160 | * @return string |
||
161 | */ |
||
162 | public function type() |
||
166 | |||
167 | /** |
||
168 | * Retrieve the property's structure. |
||
169 | * |
||
170 | * @return MetadataInterface|null |
||
171 | */ |
||
172 | public function getStructureMetadata() |
||
173 | { |
||
174 | if ($this->structureMetadata === null || $this->isStructureFinalized === false) { |
||
175 | $this->structureMetadata = $this->loadStructureMetadata(); |
||
176 | } |
||
177 | |||
178 | return $this->structureMetadata; |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * Set the property's structure. |
||
183 | * |
||
184 | * @param MetadataInterface|array|null $data The property's structure (fields, data). |
||
185 | * @throws InvalidArgumentException If the structure is invalid. |
||
186 | * @return self |
||
187 | */ |
||
188 | public function setStructureMetadata($data) |
||
214 | |||
215 | /** |
||
216 | * Retrieve the metadata interfaces used by the property as a structure. |
||
217 | * |
||
218 | * @return array |
||
219 | */ |
||
220 | public function getStructureInterfaces() |
||
221 | { |
||
222 | if (empty($this->structureInterfaces)) { |
||
223 | return $this->structureInterfaces; |
||
224 | } |
||
225 | |||
226 | return array_keys($this->structureInterfaces); |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Set the given metadata interfaces for the property to use as a structure. |
||
231 | * |
||
232 | * @param array $interfaces One or more metadata interfaces to use. |
||
233 | * @return self |
||
234 | */ |
||
235 | public function setStructureInterfaces(array $interfaces) |
||
243 | |||
244 | /** |
||
245 | * Add the given metadata interfaces for the property to use as a structure. |
||
246 | * |
||
247 | * @param array $interfaces One or more metadata interfaces to use. |
||
248 | * @return self |
||
249 | */ |
||
250 | public function addStructureInterfaces(array $interfaces) |
||
258 | |||
259 | /** |
||
260 | * Add the given metadata interfaces for the property to use as a structure. |
||
261 | * |
||
262 | * @param string $interface A metadata interface to use. |
||
263 | * @throws InvalidArgumentException If the interface is not a string. |
||
264 | * @return self |
||
265 | */ |
||
266 | public function addStructureInterface($interface) |
||
284 | |||
285 | /** |
||
286 | * Load the property's structure. |
||
287 | * |
||
288 | * @return MetadataInterface |
||
289 | */ |
||
290 | protected function loadStructureMetadata() |
||
324 | |||
325 | /** |
||
326 | * Retrieve a singleton of the structure model for prototyping. |
||
327 | * |
||
328 | * @return ArrayAccess|DescribableInterface |
||
329 | */ |
||
330 | public function structureProto() |
||
344 | |||
345 | /** |
||
346 | * Set the class name of the data-model structure. |
||
347 | * |
||
348 | * @param string $className The class name of the structure. |
||
349 | * @throws InvalidArgumentException If the class name is not a string. |
||
350 | * @return self |
||
351 | */ |
||
352 | protected function setStructureModelClass($className) |
||
364 | |||
365 | /** |
||
366 | * Retrieve the class name of the data-model structure. |
||
367 | * |
||
368 | * @return string |
||
369 | */ |
||
370 | public function getStructureModelClass() |
||
374 | |||
375 | /** |
||
376 | * Convert the given value into a structure. |
||
377 | * |
||
378 | * Options: |
||
379 | * - `default_data` (_boolean_|_array_) — If TRUE, the default data defined |
||
380 | * in the structure's metadata is merged. If an array, that is merged. |
||
381 | * |
||
382 | * @param mixed $val The value to "structurize". |
||
383 | * @param array|MetadataInterface $options Optional structure options. |
||
384 | * @throws InvalidArgumentException If the options are invalid. |
||
385 | * @return ModelInterface|ModelInterface[] |
||
386 | */ |
||
387 | public function structureVal($val, $options = []) |
||
437 | |||
438 | /** |
||
439 | * Retrieve the structure as a plain array. |
||
440 | * |
||
441 | * @return array |
||
442 | */ |
||
443 | public function toStructure() |
||
447 | |||
448 | /** |
||
449 | * @param null|string $model Model ident. |
||
450 | * @return ArrayAccess|DescribableInterface|mixed |
||
451 | * @throws UnexpectedValueException If the structure is invalid. |
||
452 | */ |
||
453 | View Code Duplication | public function toModel($model = null) |
|
471 | |||
472 | /** |
||
473 | * PropertyInterface::save(). |
||
474 | * @param mixed $val The value, at time of saving. |
||
475 | * @return mixed |
||
476 | */ |
||
477 | public function save($val) |
||
504 | |||
505 | /** |
||
506 | * Inject dependencies from a DI Container. |
||
507 | * |
||
508 | * @param Container $container A dependencies container instance. |
||
509 | * @return void |
||
510 | */ |
||
511 | protected function setDependencies(Container $container) |
||
517 | |||
518 | /** |
||
519 | * Retrieve the structure model factory. |
||
520 | * |
||
521 | * @throws RuntimeException If the model factory was not previously set. |
||
522 | * @return FactoryInterface |
||
523 | */ |
||
524 | protected function structureModelFactory() |
||
535 | |||
536 | /** |
||
537 | * Set an structure model factory. |
||
538 | * |
||
539 | * @param FactoryInterface $factory The model factory, to create objects. |
||
540 | * @return self |
||
541 | */ |
||
542 | private function setStructureModelFactory(FactoryInterface $factory) |
||
548 | |||
549 | /** |
||
550 | * Parse a metadata identifier from given interface. |
||
551 | * |
||
552 | * Change `\` and `.` to `/` and force lowercase |
||
553 | * |
||
554 | * @param string $interface A metadata interface to convert. |
||
555 | * @return string |
||
556 | */ |
||
557 | protected function parseStructureInterface($interface) |
||
564 | |||
565 | /** |
||
566 | * Create a new metadata object for structures. |
||
567 | * |
||
568 | * Similar to {@see \Charcoal\Model\DescribableTrait::createMetadata()}. |
||
569 | * |
||
570 | * @return MetadataInterface |
||
571 | */ |
||
572 | protected function createStructureMetadata() |
||
577 | |||
578 | /** |
||
579 | * Retrieve the class name of the metadata object. |
||
580 | * |
||
581 | * @return string |
||
582 | */ |
||
583 | protected function getStructureMetadataClass() |
||
587 | |||
588 | /** |
||
589 | * Create a data-model structure. |
||
590 | * |
||
591 | * @todo Add support for simple {@see ArrayAccess} models. |
||
592 | * @throws UnexpectedValueException If the structure is invalid. |
||
593 | * @return ArrayAccess |
||
594 | */ |
||
595 | View Code Duplication | private function createStructureModel() |
|
610 | |||
611 | /** |
||
612 | * Create a data-model structure. |
||
613 | * |
||
614 | * @param MetadataInterface $metadata The model's definition. |
||
615 | * @param array ...$datasets The dataset(s) to modelize. |
||
616 | * @throws UnexpectedValueException If the structure is invalid. |
||
617 | * @return DescribableInterface |
||
618 | */ |
||
619 | private function createStructureModelWith( |
||
642 | } |
||
643 |