Complex classes like Model 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 Model, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
50 | abstract class Model implements ArrayAccess |
||
51 | { |
||
52 | const DEFAULT_ID_NAME = 'id'; |
||
53 | |||
54 | ///////////////////////////// |
||
55 | // Model visible variables |
||
56 | ///////////////////////////// |
||
57 | |||
58 | /** |
||
59 | * List of model ID property names. |
||
60 | * |
||
61 | * @var array |
||
62 | */ |
||
63 | protected static $ids = [self::DEFAULT_ID_NAME]; |
||
64 | |||
65 | /** |
||
66 | * Property definitions expressed as a key-value map with |
||
67 | * property names as the keys. |
||
68 | * i.e. ['enabled' => ['type' => Type::BOOLEAN]]. |
||
69 | * |
||
70 | * @var array |
||
71 | */ |
||
72 | protected static $properties = []; |
||
73 | |||
74 | /** |
||
75 | * @var array |
||
76 | */ |
||
77 | protected $_values = []; |
||
78 | |||
79 | /** |
||
80 | * @var array |
||
81 | */ |
||
82 | private $_unsaved = []; |
||
83 | |||
84 | /** |
||
85 | * @var bool |
||
86 | */ |
||
87 | protected $_persisted = false; |
||
88 | |||
89 | /** |
||
90 | * @var array |
||
91 | */ |
||
92 | protected $_relationships = []; |
||
93 | |||
94 | /** |
||
95 | * @var AbstractRelation[] |
||
96 | */ |
||
97 | private $relationships = []; |
||
98 | |||
99 | ///////////////////////////// |
||
100 | // Base model variables |
||
101 | ///////////////////////////// |
||
102 | |||
103 | /** |
||
104 | * @var array |
||
105 | */ |
||
106 | private static $initialized = []; |
||
107 | |||
108 | /** |
||
109 | * @var DriverInterface |
||
110 | */ |
||
111 | private static $driver; |
||
112 | |||
113 | /** |
||
114 | * @var array |
||
115 | */ |
||
116 | private static $accessors = []; |
||
117 | |||
118 | /** |
||
119 | * @var array |
||
120 | */ |
||
121 | private static $mutators = []; |
||
122 | |||
123 | /** |
||
124 | * @var string |
||
125 | */ |
||
126 | private $tablename; |
||
127 | |||
128 | /** |
||
129 | * @var bool |
||
130 | */ |
||
131 | private $hasId; |
||
132 | |||
133 | /** |
||
134 | * @var array |
||
135 | */ |
||
136 | private $idValues; |
||
137 | |||
138 | /** |
||
139 | * @var bool |
||
140 | */ |
||
141 | private $loaded = false; |
||
142 | |||
143 | /** |
||
144 | * @var Errors |
||
145 | */ |
||
146 | private $errors; |
||
147 | |||
148 | /** |
||
149 | * @var bool |
||
150 | */ |
||
151 | private $ignoreUnsaved = false; |
||
152 | |||
153 | /** |
||
154 | * Creates a new model object. |
||
155 | * |
||
156 | * @param array|string|Model|false $id ordered array of ids or comma-separated id string |
||
|
|||
157 | * @param array $values optional key-value map to pre-seed model |
||
158 | */ |
||
159 | public function __construct(array $values = []) |
||
188 | |||
189 | /** |
||
190 | * Performs initialization on this model. |
||
191 | */ |
||
192 | private function init() |
||
201 | |||
202 | /** |
||
203 | * The initialize() method is called once per model. This is a great |
||
204 | * place to install event listeners. Any methods on the model that have |
||
205 | * "autoInitialize" in the name will automatically be called. |
||
206 | */ |
||
207 | protected function initialize() |
||
218 | |||
219 | /** |
||
220 | * Sets the driver for all models. |
||
221 | */ |
||
222 | public static function setDriver(DriverInterface $driver) |
||
226 | |||
227 | /** |
||
228 | * Gets the driver for all models. |
||
229 | * |
||
230 | * @throws DriverMissingException when a driver has not been set yet |
||
231 | */ |
||
232 | public static function getDriver(): DriverInterface |
||
240 | |||
241 | /** |
||
242 | * Clears the driver for all models. |
||
243 | */ |
||
244 | public static function clearDriver() |
||
248 | |||
249 | /** |
||
250 | * Gets the name of the model, i.e. User. |
||
251 | */ |
||
252 | public static function modelName(): string |
||
259 | |||
260 | /** |
||
261 | * Gets the model ID. |
||
262 | * |
||
263 | * @return string|number|false ID |
||
264 | */ |
||
265 | public function id() |
||
282 | |||
283 | /** |
||
284 | * Gets a key-value map of the model ID. |
||
285 | * |
||
286 | * @return array ID map |
||
287 | */ |
||
288 | public function ids(): array |
||
292 | |||
293 | /** |
||
294 | * Checks if the model has an identifier present. |
||
295 | * This does not indicate whether the model has been |
||
296 | * persisted to the database or loaded from the database. |
||
297 | */ |
||
298 | public function hasId(): bool |
||
302 | |||
303 | ///////////////////////////// |
||
304 | // Magic Methods |
||
305 | ///////////////////////////// |
||
306 | |||
307 | /** |
||
308 | * Converts the model into a string. |
||
309 | * |
||
310 | * @return string |
||
311 | */ |
||
312 | public function __toString() |
||
319 | |||
320 | /** |
||
321 | * Shortcut to a get() call for a given property. |
||
322 | * |
||
323 | * @param string $name |
||
324 | * |
||
325 | * @return mixed |
||
326 | */ |
||
327 | public function __get($name) |
||
333 | |||
334 | /** |
||
335 | * Sets an unsaved value. |
||
336 | * |
||
337 | * @param string $name |
||
338 | * @param mixed $value |
||
339 | */ |
||
340 | public function __set($name, $value) |
||
369 | |||
370 | /** |
||
371 | * Checks if an unsaved value or property exists by this name. |
||
372 | * |
||
373 | * @param string $name |
||
374 | * |
||
375 | * @return bool |
||
376 | */ |
||
377 | public function __isset($name) |
||
385 | |||
386 | /** |
||
387 | * Unsets an unsaved value. |
||
388 | * |
||
389 | * @param string $name |
||
390 | */ |
||
391 | public function __unset($name) |
||
402 | |||
403 | ///////////////////////////// |
||
404 | // ArrayAccess Interface |
||
405 | ///////////////////////////// |
||
406 | |||
407 | public function offsetExists($offset) |
||
411 | |||
412 | public function offsetGet($offset) |
||
416 | |||
417 | public function offsetSet($offset, $value) |
||
421 | |||
422 | public function offsetUnset($offset) |
||
426 | |||
427 | public static function __callStatic($name, $parameters) |
||
434 | |||
435 | ///////////////////////////// |
||
436 | // Property Definitions |
||
437 | ///////////////////////////// |
||
438 | |||
439 | /** |
||
440 | * Gets the model definition. |
||
441 | */ |
||
442 | public static function definition(): Definition |
||
446 | |||
447 | /** |
||
448 | * The buildDefinition() method is called once per model. It's used |
||
449 | * to generate the model definition. This is a great place to add any |
||
450 | * dynamic model properties. |
||
451 | */ |
||
452 | public static function buildDefinition(): Definition |
||
465 | |||
466 | /** |
||
467 | * Gets the names of the model ID properties. |
||
468 | */ |
||
469 | public static function getIDProperties(): array |
||
473 | |||
474 | /** |
||
475 | * Gets the mutator method name for a given property name. |
||
476 | * Looks for methods in the form of `setPropertyValue`. |
||
477 | * i.e. the mutator for `last_name` would be `setLastNameValue`. |
||
478 | * |
||
479 | * @param string $property property |
||
480 | * |
||
481 | * @return string|null method name if it exists |
||
482 | */ |
||
483 | public static function getMutator(string $property): ?string |
||
501 | |||
502 | /** |
||
503 | * Gets the accessor method name for a given property name. |
||
504 | * Looks for methods in the form of `getPropertyValue`. |
||
505 | * i.e. the accessor for `last_name` would be `getLastNameValue`. |
||
506 | * |
||
507 | * @param string $property property |
||
508 | * |
||
509 | * @return string|null method name if it exists |
||
510 | */ |
||
511 | public static function getAccessor(string $property): ?string |
||
529 | |||
530 | ///////////////////////////// |
||
531 | // CRUD Operations |
||
532 | ///////////////////////////// |
||
533 | |||
534 | /** |
||
535 | * Gets the table name for storing this model. |
||
536 | */ |
||
537 | public function getTablename(): string |
||
547 | |||
548 | /** |
||
549 | * Gets the ID of the connection in the connection manager |
||
550 | * that stores this model. |
||
551 | */ |
||
552 | public function getConnection(): ?string |
||
556 | |||
557 | protected function usesTransactions(): bool |
||
561 | |||
562 | /** |
||
563 | * Saves the model. |
||
564 | * |
||
565 | * @return bool true when the operation was successful |
||
566 | */ |
||
567 | public function save(): bool |
||
575 | |||
576 | /** |
||
577 | * Saves the model. Throws an exception when the operation fails. |
||
578 | * |
||
579 | * @throws ModelException when the model cannot be saved |
||
580 | */ |
||
581 | public function saveOrFail() |
||
592 | |||
593 | /** |
||
594 | * Creates a new model. |
||
595 | * |
||
596 | * @param array $data optional key-value properties to set |
||
597 | * |
||
598 | * @return bool true when the operation was successful |
||
599 | * |
||
600 | * @throws BadMethodCallException when called on an existing model |
||
601 | */ |
||
602 | public function create(array $data = []): bool |
||
726 | |||
727 | /** |
||
728 | * Ignores unsaved values when fetching the next value. |
||
729 | * |
||
730 | * @return $this |
||
731 | */ |
||
732 | public function ignoreUnsaved() |
||
738 | |||
739 | /** |
||
740 | * Fetches property values from the model. |
||
741 | * |
||
742 | * This method looks up values in this order: |
||
743 | * IDs, local cache, unsaved values, storage layer, defaults |
||
744 | * |
||
745 | * @param array $properties list of property names to fetch values of |
||
746 | */ |
||
747 | public function get(array $properties): array |
||
768 | |||
769 | /** |
||
770 | * Loads the model from the database if needed. |
||
771 | */ |
||
772 | private function loadIfNeeded(array $properties, bool $ignoreUnsaved): void |
||
789 | |||
790 | /** |
||
791 | * Gets a property value from the model. |
||
792 | * |
||
793 | * Values are looked up in this order: |
||
794 | * 1. unsaved values |
||
795 | * 2. local values |
||
796 | * 3. default value |
||
797 | * 4. null |
||
798 | * |
||
799 | * @return mixed |
||
800 | */ |
||
801 | private function getValue(string $name, bool $ignoreUnsaved) |
||
824 | |||
825 | /** |
||
826 | * Populates a newly created model with its ID. |
||
827 | */ |
||
828 | private function getNewId() |
||
850 | |||
851 | protected function getMassAssignmentWhitelist(): ?array |
||
860 | |||
861 | protected function getMassAssignmentBlacklist(): ?array |
||
870 | |||
871 | /** |
||
872 | * Sets a collection values on the model from an untrusted input. |
||
873 | * |
||
874 | * @param array $values |
||
875 | * |
||
876 | * @throws MassAssignmentException when assigning a value that is protected or not whitelisted |
||
877 | * |
||
878 | * @return $this |
||
879 | */ |
||
880 | public function setValues($values) |
||
911 | |||
912 | /** |
||
913 | * Converts the model to an array. |
||
914 | */ |
||
915 | public function toArray(): array |
||
962 | |||
963 | /** |
||
964 | * Checks if the unsaved value for a property is present and |
||
965 | * is different from the original value. |
||
966 | * |
||
967 | * @property string|null $name |
||
968 | * @property bool $hasChanged when true, checks if the unsaved value is different from the saved value |
||
969 | */ |
||
970 | public function dirty(?string $name = null, bool $hasChanged = false): bool |
||
990 | |||
991 | /** |
||
992 | * Updates the model. |
||
993 | * |
||
994 | * @param array $data optional key-value properties to set |
||
995 | * |
||
996 | * @return bool true when the operation was successful |
||
997 | * |
||
998 | * @throws BadMethodCallException when not called on an existing model |
||
999 | */ |
||
1000 | public function set(array $data = []): bool |
||
1099 | |||
1100 | /** |
||
1101 | * Delete the model. |
||
1102 | * |
||
1103 | * @return bool true when the operation was successful |
||
1104 | */ |
||
1105 | public function delete(): bool |
||
1155 | |||
1156 | /** |
||
1157 | * Restores a soft-deleted model. |
||
1158 | */ |
||
1159 | public function restore(): bool |
||
1193 | |||
1194 | /** |
||
1195 | * Checks if the model has been deleted. |
||
1196 | */ |
||
1197 | public function isDeleted(): bool |
||
1205 | |||
1206 | ///////////////////////////// |
||
1207 | // Queries |
||
1208 | ///////////////////////////// |
||
1209 | |||
1210 | /** |
||
1211 | * Generates a new query instance. |
||
1212 | */ |
||
1213 | public static function query(): Query |
||
1228 | |||
1229 | /** |
||
1230 | * Generates a new query instance that includes soft-deleted models. |
||
1231 | */ |
||
1232 | public static function withDeleted(): Query |
||
1241 | |||
1242 | /** |
||
1243 | * Finds a single instance of a model given it's ID. |
||
1244 | * |
||
1245 | * @param mixed $id |
||
1246 | * |
||
1247 | * @return static|null |
||
1248 | */ |
||
1249 | public static function find($id): ?self |
||
1266 | |||
1267 | /** |
||
1268 | * Finds a single instance of a model given it's ID or throws an exception. |
||
1269 | * |
||
1270 | * @param mixed $id |
||
1271 | * |
||
1272 | * @return static |
||
1273 | * |
||
1274 | * @throws ModelNotFoundException when a model could not be found |
||
1275 | */ |
||
1276 | public static function findOrFail($id): self |
||
1285 | |||
1286 | /** |
||
1287 | * Tells if this model instance has been persisted to the data layer. |
||
1288 | * |
||
1289 | * NOTE: this does not actually perform a check with the data layer |
||
1290 | */ |
||
1291 | public function persisted(): bool |
||
1295 | |||
1296 | /** |
||
1297 | * Loads the model from the storage layer. |
||
1298 | * |
||
1299 | * @return $this |
||
1300 | */ |
||
1301 | public function refresh() |
||
1325 | |||
1326 | /** |
||
1327 | * Loads values into the model. |
||
1328 | * |
||
1329 | * @param array $values values |
||
1330 | * |
||
1331 | * @return $this |
||
1332 | */ |
||
1333 | public function refreshWith(array $values) |
||
1341 | |||
1342 | /** |
||
1343 | * Clears the cache for this model. |
||
1344 | * |
||
1345 | * @return $this |
||
1346 | */ |
||
1347 | public function clearCache() |
||
1356 | |||
1357 | ///////////////////////////// |
||
1358 | // Relationships |
||
1359 | ///////////////////////////// |
||
1360 | |||
1361 | /** |
||
1362 | * Gets the relationship manager for a property. |
||
1363 | * |
||
1364 | * @throws InvalidArgumentException when the relationship manager cannot be created |
||
1365 | */ |
||
1366 | private function getRelationship(Property $property): AbstractRelation |
||
1375 | |||
1376 | /** |
||
1377 | * Saves any unsaved models attached through a relationship. This will only |
||
1378 | * save attached models that have not been saved yet. |
||
1379 | */ |
||
1380 | private function saveRelationships(bool $usesTransactions): bool |
||
1414 | |||
1415 | /** |
||
1416 | * This hydrates an individual property in the model. It can be a |
||
1417 | * scalar value or relationship. |
||
1418 | * |
||
1419 | * @internal |
||
1420 | * |
||
1421 | * @param $value |
||
1422 | */ |
||
1423 | public function hydrateValue(string $name, $value): void |
||
1432 | |||
1433 | /** |
||
1434 | * @deprecated |
||
1435 | * |
||
1436 | * Gets the model(s) for a relationship |
||
1437 | * |
||
1438 | * @param string $k property |
||
1439 | * |
||
1440 | * @throws InvalidArgumentException when the relationship manager cannot be created |
||
1441 | * |
||
1442 | * @return Model|array|null |
||
1443 | */ |
||
1444 | public function relation(string $k) |
||
1453 | |||
1454 | /** |
||
1455 | * @deprecated |
||
1456 | * |
||
1457 | * Sets the model for a one-to-one relationship (has-one or belongs-to) |
||
1458 | * |
||
1459 | * @return $this |
||
1460 | */ |
||
1461 | public function setRelation(string $k, Model $model) |
||
1468 | |||
1469 | /** |
||
1470 | * @deprecated |
||
1471 | * |
||
1472 | * Sets the model for a one-to-many relationship |
||
1473 | * |
||
1474 | * @return $this |
||
1475 | */ |
||
1476 | public function setRelationCollection(string $k, iterable $models) |
||
1482 | |||
1483 | /** |
||
1484 | * @deprecated |
||
1485 | * |
||
1486 | * Sets the model for a one-to-one relationship (has-one or belongs-to) as null |
||
1487 | * |
||
1488 | * @return $this |
||
1489 | */ |
||
1490 | public function clearRelation(string $k) |
||
1497 | |||
1498 | ///////////////////////////// |
||
1499 | // Events |
||
1500 | ///////////////////////////// |
||
1501 | |||
1502 | /** |
||
1503 | * Subscribes to a listener to an event. |
||
1504 | * |
||
1505 | * @param string $event event name |
||
1506 | * @param int $priority optional priority, higher #s get called first |
||
1507 | */ |
||
1508 | public static function listen(string $event, callable $listener, int $priority = 0): void |
||
1512 | |||
1513 | /** |
||
1514 | * Adds a listener to the model.creating and model.updating events. |
||
1515 | */ |
||
1516 | public static function saving(callable $listener, int $priority = 0): void |
||
1521 | |||
1522 | /** |
||
1523 | * Adds a listener to the model.created and model.updated events. |
||
1524 | */ |
||
1525 | public static function saved(callable $listener, int $priority = 0): void |
||
1530 | |||
1531 | /** |
||
1532 | * Adds a listener to the model.creating, model.updating, and model.deleting events. |
||
1533 | */ |
||
1534 | public static function beforePersist(callable $listener, int $priority = 0): void |
||
1540 | |||
1541 | /** |
||
1542 | * Adds a listener to the model.created, model.updated, and model.deleted events. |
||
1543 | */ |
||
1544 | public static function afterPersist(callable $listener, int $priority = 0): void |
||
1550 | |||
1551 | /** |
||
1552 | * Adds a listener to the model.creating event. |
||
1553 | */ |
||
1554 | public static function creating(callable $listener, int $priority = 0): void |
||
1558 | |||
1559 | /** |
||
1560 | * Adds a listener to the model.created event. |
||
1561 | */ |
||
1562 | public static function created(callable $listener, int $priority = 0): void |
||
1566 | |||
1567 | /** |
||
1568 | * Adds a listener to the model.updating event. |
||
1569 | */ |
||
1570 | public static function updating(callable $listener, int $priority = 0): void |
||
1574 | |||
1575 | /** |
||
1576 | * Adds a listener to the model.updated event. |
||
1577 | */ |
||
1578 | public static function updated(callable $listener, int $priority = 0): void |
||
1582 | |||
1583 | /** |
||
1584 | * Adds a listener to the model.deleting event. |
||
1585 | */ |
||
1586 | public static function deleting(callable $listener, int $priority = 0): void |
||
1590 | |||
1591 | /** |
||
1592 | * Adds a listener to the model.deleted event. |
||
1593 | */ |
||
1594 | public static function deleted(callable $listener, int $priority = 0): void |
||
1598 | |||
1599 | /** |
||
1600 | * Dispatches the given event and checks if it was successful. |
||
1601 | * |
||
1602 | * @return bool true if the events were successfully propagated |
||
1603 | */ |
||
1604 | private function performDispatch(AbstractEvent $event, bool $usesTransactions): bool |
||
1619 | |||
1620 | ///////////////////////////// |
||
1621 | // Validation |
||
1622 | ///////////////////////////// |
||
1623 | |||
1624 | /** |
||
1625 | * Gets the error stack for this model. |
||
1626 | */ |
||
1627 | public function getErrors(): Errors |
||
1635 | |||
1636 | /** |
||
1637 | * Checks if the model in its current state is valid. |
||
1638 | */ |
||
1639 | public function valid(): bool |
||
1653 | } |
||
1654 |
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.
Consider the following example. The parameter
$italy
is not defined by the methodfinale(...)
.The most likely cause is that the parameter was removed, but the annotation was not.