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 BelongsToMany 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 BelongsToMany, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 12 | class BelongsToMany extends Relation { |
||
| 13 | |||
| 14 | /** |
||
| 15 | * @var array |
||
| 16 | */ |
||
| 17 | protected $associationConstraint = array(); |
||
| 18 | |||
| 19 | /** |
||
| 20 | * @var string Table name for "many-to-many" relations |
||
| 21 | */ |
||
| 22 | protected $table; |
||
| 23 | |||
| 24 | /** |
||
| 25 | * Instantiate a new many-to-many relation. |
||
| 26 | * |
||
| 27 | * @param Relation $parent |
||
| 28 | * @param string $target |
||
| 29 | * @param string $foreignKey [optional] |
||
| 30 | * @param string $localKey [optional] |
||
| 31 | * @param string $table [optional] |
||
| 32 | * @param array $constraint [optional] |
||
| 33 | */ |
||
| 34 | public function __construct(Record $parent, $target, $foreignKey = null, $localKey = null, $table = null, array $constraint = array(), array $associationConstraint = array()) { |
||
| 43 | |||
| 44 | /** |
||
| 45 | * Retrieve the IDs of models that should be inserted into the relation |
||
| 46 | * table, given models that are already related and models that should be |
||
| 47 | * associated. |
||
| 48 | * |
||
| 49 | * Returns the difference of the IDs of each set of instances. |
||
| 50 | * |
||
| 51 | * @param array $old |
||
| 52 | * @param array $new |
||
| 53 | * @return array |
||
| 54 | */ |
||
| 55 | protected static function insertIds($old, $new) { |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Group foreign keys into arrays for each local key found. |
||
| 74 | * |
||
| 75 | * Expects an array with at least local keys and foreign keys set. |
||
| 76 | * |
||
| 77 | * Returns an adjacency list of local keys to related foreign keys. |
||
| 78 | * |
||
| 79 | * @param array $relations |
||
| 80 | * @return array |
||
| 81 | */ |
||
| 82 | protected function bundleRelations(array $relations) { |
||
| 95 | |||
| 96 | /** |
||
| 97 | * List the given instances with their IDs as keys. |
||
| 98 | * |
||
| 99 | * @param Record[]|Record|array $instances |
||
| 100 | * @return Record[] |
||
| 101 | */ |
||
| 102 | protected static function listById($instances) { |
||
| 111 | |||
| 112 | /** |
||
| 113 | * Set the default keys for the relation if they have not already been set. |
||
| 114 | */ |
||
| 115 | View Code Duplication | protected function setDefaultKeys() { |
|
| 124 | |||
| 125 | /** |
||
| 126 | * Set the default many-to-many relation table name. |
||
| 127 | * |
||
| 128 | * Sorts parent and related class names alphabetically. |
||
| 129 | */ |
||
| 130 | protected function setDefaultTable() { |
||
| 143 | |||
| 144 | /** |
||
| 145 | * Retrieve the default filter for the association table. |
||
| 146 | * |
||
| 147 | * @return array |
||
| 148 | */ |
||
| 149 | protected function defaultAssociationConstraint() { |
||
| 152 | |||
| 153 | /** |
||
| 154 | * Set a filter to constrain the association table. |
||
| 155 | * |
||
| 156 | * @param array $filter |
||
| 157 | */ |
||
| 158 | public function constrainAssociation(array $filter) { |
||
| 161 | |||
| 162 | /** |
||
| 163 | * Retrieve the custom filter used to constrain the association table. |
||
| 164 | * |
||
| 165 | * @return array |
||
| 166 | */ |
||
| 167 | public function associationConstraint() { |
||
| 170 | |||
| 171 | /** |
||
| 172 | * Retrieve the filter for the association table. |
||
| 173 | * |
||
| 174 | * @return array |
||
| 175 | */ |
||
| 176 | public function associationFilter() { |
||
| 179 | |||
| 180 | /** |
||
| 181 | * Retrieve the filter for the related models. |
||
| 182 | * |
||
| 183 | * Optionally accepts a list of related IDs to filter by. |
||
| 184 | * |
||
| 185 | * TODO: Test this without the if statement and just merging regardless |
||
| 186 | * |
||
| 187 | * @param array $related |
||
| 188 | * @return array |
||
| 189 | */ |
||
| 190 | public function filter(array $related = array()) { |
||
| 201 | |||
| 202 | /** |
||
| 203 | * Retrieve the table of the many-to-many relation. |
||
| 204 | * |
||
| 205 | * @return string |
||
| 206 | */ |
||
| 207 | public function table() { |
||
| 210 | |||
| 211 | /** |
||
| 212 | * Retrieve the related IDs from the association table. |
||
| 213 | * |
||
| 214 | * Takes into consideration the regular relation filter, if it's not empty, |
||
| 215 | * and loads IDs from the target table accordingly. |
||
| 216 | * |
||
| 217 | * @param int $limit |
||
| 218 | */ |
||
| 219 | protected function relatedIds($limit = 0) { |
||
| 232 | |||
| 233 | /** |
||
| 234 | * Retrieve the data of the related models. |
||
| 235 | * |
||
| 236 | * @param int $limit |
||
| 237 | * @return array |
||
| 238 | */ |
||
| 239 | public function read($limit = 0) { |
||
| 244 | |||
| 245 | /** |
||
| 246 | * Eagerly load the related models for the given parent instances. |
||
| 247 | * |
||
| 248 | * Returns the given instances with their related models loaded. |
||
| 249 | * |
||
| 250 | * @param array $instances |
||
| 251 | * @param string $name TODO: Remove this and store as a property |
||
| 252 | * @return array |
||
| 253 | */ |
||
| 254 | public function eager(array $instances, $name) { |
||
| 306 | |||
| 307 | /** |
||
| 308 | * Retrieve the related models. |
||
| 309 | * |
||
| 310 | * @return Record[] |
||
| 311 | */ |
||
| 312 | public function retrieve() { |
||
| 315 | |||
| 316 | /** |
||
| 317 | * Associate the given models. |
||
| 318 | * |
||
| 319 | * Returns the number of models successfully associated. |
||
| 320 | * |
||
| 321 | * @param Record[]|Record $instances |
||
| 322 | * @return int |
||
| 323 | */ |
||
| 324 | public function associate($instances) { |
||
| 351 | |||
| 352 | /** |
||
| 353 | * Dissociate the given models from the parent model. |
||
| 354 | * |
||
| 355 | * Returns the number of models successfully dissociated. |
||
| 356 | * |
||
| 357 | * @param Record[]|Record $instances [optional] |
||
| 358 | * @return int |
||
| 359 | */ |
||
| 360 | public function dissociate($instances) { |
||
| 382 | |||
| 383 | /** |
||
| 384 | * Dissociate all currently associated models. |
||
| 385 | * |
||
| 386 | * Returns the number of models successfully dissociated. |
||
| 387 | * |
||
| 388 | * @return int |
||
| 389 | */ |
||
| 390 | public function purge() { |
||
| 397 | |||
| 398 | /** |
||
| 399 | * Dissociate all models and associate the given models. |
||
| 400 | * |
||
| 401 | * Returns the number of models successfully associated. |
||
| 402 | * |
||
| 403 | * @param Record[]|Record $instances [optional] |
||
| 404 | * @return int |
||
| 405 | */ |
||
| 406 | public function sync($instances) { |
||
| 411 | |||
| 412 | /** |
||
| 413 | * Count the number of related model instances. |
||
| 414 | * |
||
| 415 | * Counts loaded instances if they are present, queries storage otherwise. |
||
| 416 | * |
||
| 417 | * @return int |
||
| 418 | */ |
||
| 419 | public function count() { |
||
| 432 | |||
| 433 | } |
||
| 434 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.