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 Record 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 Record, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 20 | class Record extends Model { |
||
| 21 | |||
| 22 | /** |
||
| 23 | * Overrides the name of the database table that persists the model. The |
||
| 24 | * model's lowercased class name is used if this is not set. |
||
| 25 | * |
||
| 26 | * @var string Database table name |
||
| 27 | */ |
||
| 28 | protected $table; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * @var \Darya\Storage\Readable Instance storage |
||
| 32 | */ |
||
| 33 | protected $storage; |
||
| 34 | |||
| 35 | /** |
||
| 36 | * @var \Darya\Storage\Readable Shared storage |
||
| 37 | */ |
||
| 38 | protected static $sharedStorage; |
||
| 39 | |||
| 40 | /** |
||
| 41 | * @var array Definitions of related models |
||
| 42 | */ |
||
| 43 | protected $relations = array(); |
||
| 44 | |||
| 45 | /** |
||
| 46 | * @var array Default searchable attributes |
||
| 47 | */ |
||
| 48 | protected $search = array(); |
||
| 49 | |||
| 50 | /** |
||
| 51 | * Instantiate a new record with the given data or load an instance from |
||
| 52 | * storage if the given data is a valid primary key. |
||
| 53 | * |
||
| 54 | * @param mixed $data An array of key-value attributes to set or a primary key to load by |
||
| 55 | */ |
||
| 56 | public function __construct($data = null) { |
||
| 63 | |||
| 64 | /** |
||
| 65 | * Generate instances of the model with the given sets of attributes. |
||
| 66 | * |
||
| 67 | * @param array $rows |
||
| 68 | * @return Record[] |
||
| 69 | */ |
||
| 70 | View Code Duplication | public static function generate(array $rows = array()) { |
|
|
|
|||
| 71 | $instances = array(); |
||
| 72 | |||
| 73 | foreach ($rows as $key => $attributes) { |
||
| 74 | $instances[$key] = new static; |
||
| 75 | $instances[$key]->setMany($attributes); |
||
| 76 | } |
||
| 77 | |||
| 78 | return $instances; |
||
| 79 | } |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Determine whether the given attribute or relation is set on the record. |
||
| 83 | * |
||
| 84 | * @param string $attribute |
||
| 85 | */ |
||
| 86 | public function has($attribute) { |
||
| 87 | return $this->hasRelated($attribute) || parent::has($attribute); |
||
| 88 | } |
||
| 89 | |||
| 90 | /** |
||
| 91 | * Retrieve the given attribute or relation from the record. |
||
| 92 | * |
||
| 93 | * @param string $attribute |
||
| 94 | * @return mixed |
||
| 95 | */ |
||
| 96 | public function get($attribute) { |
||
| 103 | |||
| 104 | /** |
||
| 105 | * Set the value of an attribute or relation on the model. |
||
| 106 | * |
||
| 107 | * @param string $attribute |
||
| 108 | * @param mixed $value |
||
| 109 | */ |
||
| 110 | public function set($attribute, $value = null) { |
||
| 117 | |||
| 118 | /** |
||
| 119 | * Retrieve the name of the table this model belongs to. |
||
| 120 | * |
||
| 121 | * If none is set, it defaults to creating it from the class name. |
||
| 122 | * |
||
| 123 | * For example: |
||
| 124 | * Page -> pages |
||
| 125 | * PageSection -> page_sections |
||
| 126 | * |
||
| 127 | * @return string |
||
| 128 | */ |
||
| 129 | public function table() { |
||
| 130 | if ($this->table) { |
||
| 131 | return $this->table; |
||
| 132 | } |
||
| 133 | |||
| 134 | return preg_replace_callback('/([A-Z])/', function($matches) { |
||
| 135 | return '_' . strtolower($matches[1]); |
||
| 136 | }, lcfirst(basename(get_class($this)))) . 's'; |
||
| 137 | } |
||
| 138 | |||
| 139 | /** |
||
| 140 | * Get and optionally set the model's storage instance. |
||
| 141 | * |
||
| 142 | * @return \Darya\Storage\Readable |
||
| 143 | */ |
||
| 144 | public function storage(Readable $storage = null) { |
||
| 149 | |||
| 150 | /** |
||
| 151 | * Get the storage shared to all instances of this model. |
||
| 152 | * |
||
| 153 | * @return \Darya\Storage\Readable |
||
| 154 | */ |
||
| 155 | public static function getSharedStorage() { |
||
| 158 | |||
| 159 | /** |
||
| 160 | * Share the given database connection to all instances of this model. |
||
| 161 | * |
||
| 162 | * @param \Darya\Storage\Readable $storage |
||
| 163 | */ |
||
| 164 | public static function setSharedStorage(Readable $storage) { |
||
| 167 | |||
| 168 | /** |
||
| 169 | * Prepare the record's data for storage. This is here until repositories |
||
| 170 | * are implemented. |
||
| 171 | * |
||
| 172 | * @return array |
||
| 173 | */ |
||
| 174 | protected function prepareData() { |
||
| 175 | $types = $this->attributes; |
||
| 176 | |||
| 177 | $changed = array_intersect_key($this->data, array_flip($this->changed)); |
||
| 178 | |||
| 179 | $data = $this->id() && $changed ? $changed : $this->data; |
||
| 180 | |||
| 181 | foreach ($data as $attribute => $value) { |
||
| 182 | if (isset($types[$attribute])) { |
||
| 183 | $type = $types[$attribute]; |
||
| 184 | |||
| 185 | switch ($type) { |
||
| 186 | case 'int': |
||
| 187 | $value = (int) $value; |
||
| 188 | break; |
||
| 189 | case 'date': |
||
| 190 | $value = date('Y-m-d', $value); |
||
| 191 | break; |
||
| 192 | case 'datetime': |
||
| 193 | $value = date('Y-m-d H:i:s', $value); |
||
| 194 | break; |
||
| 195 | case 'time': |
||
| 196 | $value = date('H:i:s', $value); |
||
| 197 | break; |
||
| 198 | } |
||
| 199 | |||
| 200 | $data[$attribute] = $value; |
||
| 201 | } |
||
| 202 | } |
||
| 203 | |||
| 204 | return $data; |
||
| 205 | } |
||
| 206 | |||
| 207 | /** |
||
| 208 | * Prepare the given filter. |
||
| 209 | * |
||
| 210 | * Creates a filter for the record's key attribute if the given value is not |
||
| 211 | * an array. |
||
| 212 | * |
||
| 213 | * @param mixed $filter |
||
| 214 | * @return string |
||
| 215 | */ |
||
| 216 | protected static function prepareFilter($filter) { |
||
| 228 | |||
| 229 | /** |
||
| 230 | * Prepare the given list data. |
||
| 231 | * |
||
| 232 | * @param array $data |
||
| 233 | * @param string $attribute |
||
| 234 | * @return array |
||
| 235 | */ |
||
| 236 | protected static function prepareListing($data, $attribute) { |
||
| 250 | |||
| 251 | /** |
||
| 252 | * Load record data from storage using the given criteria. |
||
| 253 | * |
||
| 254 | * @param array|string|int $filter [optional] |
||
| 255 | * @param array|string $order [optional] |
||
| 256 | * @param int $limit [optional] |
||
| 257 | * @param int $offset [optional] |
||
| 258 | * @return array |
||
| 259 | */ |
||
| 260 | public static function load($filter = array(), $order = array(), $limit = null, $offset = 0) { |
||
| 267 | |||
| 268 | /** |
||
| 269 | * Load a record instance from storage using the given criteria. |
||
| 270 | * |
||
| 271 | * Returns false if the record cannot be found. |
||
| 272 | * |
||
| 273 | * @param array|string|int $filter [optional] |
||
| 274 | * @param array|string $order [optional] |
||
| 275 | * @return Record|bool |
||
| 276 | */ |
||
| 277 | public static function find($filter = array(), $order = array()) { |
||
| 282 | |||
| 283 | /** |
||
| 284 | * Load a record instance from storage using the given criteria or create a |
||
| 285 | * new instance if nothing is found. |
||
| 286 | * |
||
| 287 | * @param array|string|int $filter [optional] |
||
| 288 | * @param array|string $order [optional] |
||
| 289 | * @return Record |
||
| 290 | */ |
||
| 291 | public static function findOrNew($filter = array(), $order = array()) { |
||
| 296 | |||
| 297 | /** |
||
| 298 | * Load multiple record instances from storage using the given criteria. |
||
| 299 | * |
||
| 300 | * @param array|string|int $filter [optional] |
||
| 301 | * @param array|string $order [optional] |
||
| 302 | * @param int $limit [optional] |
||
| 303 | * @param int $offset [optional] |
||
| 304 | * @return array |
||
| 305 | */ |
||
| 306 | public static function all($filter = array(), $order = array(), $limit = null, $offset = 0) { |
||
| 309 | |||
| 310 | /** |
||
| 311 | * Eagerly load the given relations of multiple record instances. |
||
| 312 | * |
||
| 313 | * @param array|string $relations |
||
| 314 | * @return array |
||
| 315 | */ |
||
| 316 | public static function eager($relations) { |
||
| 328 | |||
| 329 | /** |
||
| 330 | * Search for record instances in storage using the given criteria. |
||
| 331 | * |
||
| 332 | * @param string $query |
||
| 333 | * @param array $attributes [optional] |
||
| 334 | * @param array|string|int $filter [optional] |
||
| 335 | * @param array|string $order [optional] |
||
| 336 | * @param int $limit [optional] |
||
| 337 | * @param int $offset [optional] |
||
| 338 | * @return array |
||
| 339 | */ |
||
| 340 | public static function search($query, $attributes = array(), $filter = array(), $order = array(), $limit = null, $offset = 0) { |
||
| 354 | |||
| 355 | /** |
||
| 356 | * Retrieve key => value pairs using `id` for keys and the given attribute |
||
| 357 | * for values. |
||
| 358 | * |
||
| 359 | * @param string $attribute |
||
| 360 | * @param array $filter [optional] |
||
| 361 | * @param array $order [optional] |
||
| 362 | * @param int $limit [optional] |
||
| 363 | * @param int $offset [optional] |
||
| 364 | * @return array |
||
| 365 | */ |
||
| 366 | public static function listing($attribute, $filter = array(), $order = array(), $limit = null, $offset = 0) { |
||
| 374 | |||
| 375 | /** |
||
| 376 | * Retrieve the distinct values of the given attribute. |
||
| 377 | * |
||
| 378 | * @param string $attribute |
||
| 379 | * @param array $filter [optional] |
||
| 380 | * @param array $order [optional] |
||
| 381 | * @param int $limit [optional] |
||
| 382 | * @param int $offset [optional] |
||
| 383 | * @return array |
||
| 384 | */ |
||
| 385 | public static function distinct($attribute, $filter = array(), $order = array(), $limit = null, $offset = 0) { |
||
| 395 | |||
| 396 | /** |
||
| 397 | * Create a query builder for the model. |
||
| 398 | * |
||
| 399 | * @return Builder |
||
| 400 | */ |
||
| 401 | public static function query() { |
||
| 418 | |||
| 419 | /** |
||
| 420 | * Save the record to storage. |
||
| 421 | * |
||
| 422 | * @return bool |
||
| 423 | */ |
||
| 424 | public function save() { |
||
| 462 | |||
| 463 | /** |
||
| 464 | * Save multiple record instances to storage. |
||
| 465 | * |
||
| 466 | * Returns the number of instances that saved successfully. |
||
| 467 | * |
||
| 468 | * @param array $instances |
||
| 469 | * @return int |
||
| 470 | */ |
||
| 471 | public static function saveMany($instances) { |
||
| 482 | |||
| 483 | /** |
||
| 484 | * Delete the record from storage. |
||
| 485 | * |
||
| 486 | * @return bool |
||
| 487 | */ |
||
| 488 | public function delete() { |
||
| 499 | |||
| 500 | /** |
||
| 501 | * Determine whether the given attribute is a relation. |
||
| 502 | * |
||
| 503 | * @param string $attribute |
||
| 504 | * @return bool |
||
| 505 | */ |
||
| 506 | protected function hasRelation($attribute) { |
||
| 511 | |||
| 512 | /** |
||
| 513 | * Retrieve the given relation. |
||
| 514 | * |
||
| 515 | * @param string $attribute |
||
| 516 | * @return \Darya\ORM\Relation |
||
| 517 | */ |
||
| 518 | public function relation($attribute) { |
||
| 537 | |||
| 538 | /** |
||
| 539 | * Determine whether the given relation has any set model(s). |
||
| 540 | * |
||
| 541 | * @param string $attribute |
||
| 542 | * @return bool |
||
| 543 | */ |
||
| 544 | protected function hasRelated($attribute) { |
||
| 549 | |||
| 550 | /** |
||
| 551 | * Retrieve the model(s) of the given relation. |
||
| 552 | * |
||
| 553 | * @param string $attribute |
||
| 554 | * @return array |
||
| 555 | */ |
||
| 556 | public function related($attribute) { |
||
| 567 | |||
| 568 | /** |
||
| 569 | * Set the given related model(s). |
||
| 570 | * |
||
| 571 | * @param string $attribute |
||
| 572 | * @param mixed $value |
||
| 573 | */ |
||
| 574 | protected function setRelated($attribute, $value) { |
||
| 587 | |||
| 588 | /** |
||
| 589 | * Retrieve the default search attributes for the model. |
||
| 590 | * |
||
| 591 | * @return array |
||
| 592 | */ |
||
| 593 | public function defaultSearchAttributes() { |
||
| 596 | |||
| 597 | /** |
||
| 598 | * Retrieve a relation. Shortcut for `relation()`. |
||
| 599 | * |
||
| 600 | * @param string $method |
||
| 601 | * @param array $arguments |
||
| 602 | * @return Relation |
||
| 603 | */ |
||
| 604 | public function __call($method, $arguments) { |
||
| 607 | |||
| 608 | } |
||
| 609 |
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.