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 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 |
||
22 | abstract class Model implements \ArrayAccess |
||
23 | { |
||
24 | const IMMUTABLE = 0; |
||
25 | const MUTABLE_CREATE_ONLY = 1; |
||
26 | const MUTABLE = 2; |
||
27 | |||
28 | const TYPE_STRING = 'string'; |
||
29 | const TYPE_NUMBER = 'number'; |
||
30 | const TYPE_BOOLEAN = 'boolean'; |
||
31 | const TYPE_DATE = 'date'; |
||
32 | const TYPE_OBJECT = 'object'; |
||
33 | const TYPE_ARRAY = 'array'; |
||
34 | |||
35 | const ERROR_REQUIRED_FIELD_MISSING = 'required_field_missing'; |
||
36 | const ERROR_VALIDATION_FAILED = 'validation_failed'; |
||
37 | const ERROR_NOT_UNIQUE = 'not_unique'; |
||
38 | |||
39 | const DEFAULT_ID_PROPERTY = 'id'; |
||
40 | |||
41 | ///////////////////////////// |
||
42 | // Model visible variables |
||
43 | ///////////////////////////// |
||
44 | |||
45 | /** |
||
46 | * List of model ID property names. |
||
47 | * |
||
48 | * @staticvar array |
||
49 | */ |
||
50 | protected static $ids = [self::DEFAULT_ID_PROPERTY]; |
||
51 | |||
52 | /** |
||
53 | * Property definitions expressed as a key-value map with |
||
54 | * property names as the keys. |
||
55 | * i.e. ['enabled' => ['type' => Model::TYPE_BOOLEAN]]. |
||
56 | * |
||
57 | * @staticvar array |
||
58 | */ |
||
59 | protected static $properties = []; |
||
60 | |||
61 | /** |
||
62 | * @staticvar \Pimple\Container |
||
63 | */ |
||
64 | protected static $injectedApp; |
||
65 | |||
66 | /** |
||
67 | * @staticvar array |
||
68 | */ |
||
69 | protected static $dispatchers; |
||
70 | |||
71 | /** |
||
72 | * @var number|string|bool |
||
73 | */ |
||
74 | protected $_id; |
||
75 | |||
76 | /** |
||
77 | * @var \Pimple\Container |
||
78 | */ |
||
79 | protected $app; |
||
80 | |||
81 | /** |
||
82 | * @var array |
||
83 | */ |
||
84 | protected $_values = []; |
||
85 | |||
86 | /** |
||
87 | * @var array |
||
88 | */ |
||
89 | protected $_unsaved = []; |
||
90 | |||
91 | /** |
||
92 | * @var array |
||
93 | */ |
||
94 | protected $_relationships = []; |
||
95 | |||
96 | ///////////////////////////// |
||
97 | // Base model variables |
||
98 | ///////////////////////////// |
||
99 | |||
100 | /** |
||
101 | * @staticvar array |
||
102 | */ |
||
103 | private static $propertyDefinitionBase = [ |
||
104 | 'type' => self::TYPE_STRING, |
||
105 | 'mutable' => self::MUTABLE, |
||
106 | 'null' => false, |
||
107 | 'unique' => false, |
||
108 | 'required' => false, |
||
109 | ]; |
||
110 | |||
111 | /** |
||
112 | * @staticvar array |
||
113 | */ |
||
114 | private static $defaultIDProperty = [ |
||
115 | 'type' => self::TYPE_NUMBER, |
||
116 | 'mutable' => self::IMMUTABLE, |
||
117 | ]; |
||
118 | |||
119 | /** |
||
120 | * @staticvar array |
||
121 | */ |
||
122 | private static $timestampProperties = [ |
||
123 | 'created_at' => [ |
||
124 | 'type' => self::TYPE_DATE, |
||
125 | 'default' => null, |
||
126 | 'null' => true, |
||
127 | 'validate' => 'timestamp|db_timestamp', |
||
128 | ], |
||
129 | 'updated_at' => [ |
||
130 | 'type' => self::TYPE_DATE, |
||
131 | 'validate' => 'timestamp|db_timestamp', |
||
132 | ], |
||
133 | ]; |
||
134 | |||
135 | /** |
||
136 | * @staticvar array |
||
137 | */ |
||
138 | private static $initialized = []; |
||
139 | |||
140 | /** |
||
141 | * @staticvar Model\Driver\DriverInterface |
||
142 | */ |
||
143 | private static $driver; |
||
144 | |||
145 | /** |
||
146 | * @staticvar array |
||
147 | */ |
||
148 | private static $accessors = []; |
||
149 | |||
150 | /** |
||
151 | * @staticvar array |
||
152 | */ |
||
153 | private static $mutators = []; |
||
154 | |||
155 | /** |
||
156 | * @var bool |
||
157 | */ |
||
158 | private $_ignoreUnsaved; |
||
159 | |||
160 | /** |
||
161 | * Creates a new model object. |
||
162 | * |
||
163 | * @param array|string|Model|false $id ordered array of ids or comma-separated id string |
||
164 | * @param array $values optional key-value map to pre-seed model |
||
165 | */ |
||
166 | public function __construct($id = false, array $values = []) |
||
195 | |||
196 | /** |
||
197 | * Performs initialization on this model. |
||
198 | */ |
||
199 | private function init() |
||
208 | |||
209 | /** |
||
210 | * The initialize() method is called once per model. It's used |
||
211 | * to perform any one-off tasks before the model gets |
||
212 | * constructed. This is a great place to add any model |
||
213 | * properties. When extending this method be sure to call |
||
214 | * parent::initialize() as some important stuff happens here. |
||
215 | * If extending this method to add properties then you should |
||
216 | * call parent::initialize() after adding any properties. |
||
217 | */ |
||
218 | protected function initialize() |
||
243 | |||
244 | /** |
||
245 | * Injects a DI container. |
||
246 | * |
||
247 | * @param \Pimple\Container $app |
||
248 | */ |
||
249 | public static function inject(Container $app) |
||
253 | |||
254 | /** |
||
255 | * Gets the DI container used for this model. |
||
256 | * |
||
257 | * @return Container |
||
258 | */ |
||
259 | public function getApp() |
||
263 | |||
264 | /** |
||
265 | * Sets the driver for all models. |
||
266 | * |
||
267 | * @param Model\Driver\DriverInterface $driver |
||
268 | */ |
||
269 | public static function setDriver(DriverInterface $driver) |
||
273 | |||
274 | /** |
||
275 | * Gets the driver for all models. |
||
276 | * |
||
277 | * @return Model\Driver\DriverInterface |
||
278 | */ |
||
279 | public static function getDriver() |
||
283 | |||
284 | /** |
||
285 | * Gets the name of the model without namespacing. |
||
286 | * |
||
287 | * @return string |
||
288 | */ |
||
289 | public static function modelName() |
||
298 | |||
299 | /** |
||
300 | * Gets the model ID. |
||
301 | * |
||
302 | * @return string|number|false ID |
||
303 | */ |
||
304 | public function id() |
||
308 | |||
309 | /** |
||
310 | * Gets a key-value map of the model ID. |
||
311 | * |
||
312 | * @return array ID map |
||
313 | */ |
||
314 | public function ids() |
||
332 | |||
333 | ///////////////////////////// |
||
334 | // Magic Methods |
||
335 | ///////////////////////////// |
||
336 | |||
337 | /** |
||
338 | * Converts the model into a string. |
||
339 | * |
||
340 | * @return string |
||
341 | */ |
||
342 | public function __toString() |
||
346 | |||
347 | /** |
||
348 | * Shortcut to a get() call for a given property. |
||
349 | * |
||
350 | * @param string $name |
||
351 | * |
||
352 | * @return mixed |
||
353 | */ |
||
354 | public function __get($name) |
||
360 | |||
361 | /** |
||
362 | * Sets an unsaved value. |
||
363 | * |
||
364 | * @param string $name |
||
365 | * @param mixed $value |
||
366 | */ |
||
367 | public function __set($name, $value) |
||
382 | |||
383 | /** |
||
384 | * Checks if an unsaved value or property exists by this name. |
||
385 | * |
||
386 | * @param string $name |
||
387 | * |
||
388 | * @return bool |
||
389 | */ |
||
390 | public function __isset($name) |
||
394 | |||
395 | /** |
||
396 | * Unsets an unsaved value. |
||
397 | * |
||
398 | * @param string $name |
||
399 | */ |
||
400 | public function __unset($name) |
||
411 | |||
412 | ///////////////////////////// |
||
413 | // ArrayAccess Interface |
||
414 | ///////////////////////////// |
||
415 | |||
416 | public function offsetExists($offset) |
||
420 | |||
421 | public function offsetGet($offset) |
||
425 | |||
426 | public function offsetSet($offset, $value) |
||
430 | |||
431 | public function offsetUnset($offset) |
||
435 | |||
436 | public static function __callStatic($name, $parameters) |
||
443 | |||
444 | ///////////////////////////// |
||
445 | // Property Definitions |
||
446 | ///////////////////////////// |
||
447 | |||
448 | /** |
||
449 | * Gets all the property definitions for the model. |
||
450 | * |
||
451 | * @return array key-value map of properties |
||
452 | */ |
||
453 | public static function getProperties() |
||
457 | |||
458 | /** |
||
459 | * Gets a property defition for the model. |
||
460 | * |
||
461 | * @param string $property property to lookup |
||
462 | * |
||
463 | * @return array|null property |
||
464 | */ |
||
465 | public static function getProperty($property) |
||
469 | |||
470 | /** |
||
471 | * Gets the names of the model ID properties. |
||
472 | * |
||
473 | * @return array |
||
474 | */ |
||
475 | public static function getIDProperties() |
||
479 | |||
480 | /** |
||
481 | * Checks if the model has a property. |
||
482 | * |
||
483 | * @param string $property property |
||
484 | * |
||
485 | * @return bool has property |
||
486 | */ |
||
487 | public static function hasProperty($property) |
||
491 | |||
492 | /** |
||
493 | * Gets the mutator method name for a given proeprty name. |
||
494 | * Looks for methods in the form of `setPropertyValue`. |
||
495 | * i.e. the mutator for `last_name` would be `setLastNameValue`. |
||
496 | * |
||
497 | * @param string $property property |
||
498 | * |
||
499 | * @return string|false method name if it exists |
||
500 | */ |
||
501 | View Code Duplication | public static function getMutator($property) |
|
519 | |||
520 | /** |
||
521 | * Gets the accessor method name for a given proeprty name. |
||
522 | * Looks for methods in the form of `getPropertyValue`. |
||
523 | * i.e. the accessor for `last_name` would be `getLastNameValue`. |
||
524 | * |
||
525 | * @param string $property property |
||
526 | * |
||
527 | * @return string|false method name if it exists |
||
528 | */ |
||
529 | View Code Duplication | public static function getAccessor($property) |
|
547 | |||
548 | ///////////////////////////// |
||
549 | // CRUD Operations |
||
550 | ///////////////////////////// |
||
551 | |||
552 | /** |
||
553 | * Saves the model. |
||
554 | * |
||
555 | * @return bool |
||
556 | */ |
||
557 | public function save() |
||
565 | |||
566 | /** |
||
567 | * Creates a new model. |
||
568 | * |
||
569 | * @param array $data optional key-value properties to set |
||
570 | * |
||
571 | * @return bool |
||
572 | */ |
||
573 | public function create(array $data = []) |
||
661 | |||
662 | /** |
||
663 | * Ignores unsaved values when fetching the next value. |
||
664 | * |
||
665 | * @return self |
||
666 | */ |
||
667 | public function ignoreUnsaved() |
||
673 | |||
674 | /** |
||
675 | * Fetches property values from the model. |
||
676 | * |
||
677 | * This method looks up values in this order: |
||
678 | * IDs, local cache, unsaved values, storage layer, defaults |
||
679 | * |
||
680 | * @param array $properties list of property names to fetch values of |
||
681 | * |
||
682 | * @return array |
||
683 | */ |
||
684 | public function get(array $properties) |
||
729 | |||
730 | /** |
||
731 | * Gets the ID for a newly created model. |
||
732 | * |
||
733 | * @return string |
||
734 | */ |
||
735 | protected function getNewID() |
||
752 | |||
753 | /** |
||
754 | * Converts the model to an array. |
||
755 | * |
||
756 | * @return array model array |
||
757 | */ |
||
758 | public function toArray() |
||
781 | |||
782 | /** |
||
783 | * Converts the model to an array. |
||
784 | * |
||
785 | * @param array $exclude properties to exclude |
||
786 | * @param array $include properties to include |
||
787 | * @param array $expand properties to expand |
||
788 | * |
||
789 | * @return array properties |
||
790 | */ |
||
791 | public function toArrayDeprecated(array $exclude = [], array $include = [], array $expand = []) |
||
838 | |||
839 | /** |
||
840 | * Expands any relational properties within a result. |
||
841 | * |
||
842 | * @param array $result |
||
843 | * @param array $namedExc |
||
844 | * @param array $namedInc |
||
845 | * @param array $namedExp |
||
846 | * |
||
847 | * @return array |
||
848 | */ |
||
849 | private function toArrayExpand(array $result, array $namedExc, array $namedInc, array $namedExp) |
||
873 | |||
874 | /** |
||
875 | * Updates the model. |
||
876 | * |
||
877 | * @param array $data optional key-value properties to set |
||
878 | * |
||
879 | * @return bool |
||
880 | */ |
||
881 | public function set(array $data = []) |
||
944 | |||
945 | /** |
||
946 | * Delete the model. |
||
947 | * |
||
948 | * @return bool success |
||
949 | */ |
||
950 | public function delete() |
||
977 | |||
978 | ///////////////////////////// |
||
979 | // Queries |
||
980 | ///////////////////////////// |
||
981 | |||
982 | /** |
||
983 | * Generates a new query instance. |
||
984 | * |
||
985 | * @return Model\Query |
||
986 | */ |
||
987 | public static function query() |
||
996 | |||
997 | /** |
||
998 | * Gets the toal number of records matching an optional criteria. |
||
999 | * |
||
1000 | * @param array $where criteria |
||
1001 | * |
||
1002 | * @return int total |
||
1003 | */ |
||
1004 | public static function totalRecords(array $where = []) |
||
1011 | |||
1012 | /** |
||
1013 | * Checks if the model exists in the database. |
||
1014 | * |
||
1015 | * @return bool |
||
1016 | */ |
||
1017 | public function exists() |
||
1021 | |||
1022 | /** |
||
1023 | * @deprecated alias for refresh() |
||
1024 | */ |
||
1025 | public function load() |
||
1029 | |||
1030 | /** |
||
1031 | * Loads the model from the storage layer. |
||
1032 | * |
||
1033 | * @return self |
||
1034 | */ |
||
1035 | public function refresh() |
||
1052 | |||
1053 | /** |
||
1054 | * Loads values into the model. |
||
1055 | * |
||
1056 | * @param array $values values |
||
1057 | * |
||
1058 | * @return self |
||
1059 | */ |
||
1060 | public function refreshWith(array $values) |
||
1066 | |||
1067 | /** |
||
1068 | * Clears the cache for this model. |
||
1069 | * |
||
1070 | * @return self |
||
1071 | */ |
||
1072 | public function clearCache() |
||
1080 | |||
1081 | ///////////////////////////// |
||
1082 | // Relationships |
||
1083 | ///////////////////////////// |
||
1084 | |||
1085 | /** |
||
1086 | * Gets the model object corresponding to a relation |
||
1087 | * WARNING no check is used to see if the model returned actually exists. |
||
1088 | * |
||
1089 | * @param string $propertyName property |
||
1090 | * |
||
1091 | * @return \Pulsar\Model model |
||
1092 | */ |
||
1093 | public function relation($propertyName) |
||
1105 | |||
1106 | /** |
||
1107 | * Creates the parent side of a One-To-One relationship. |
||
1108 | * |
||
1109 | * @param string $model foreign model class |
||
1110 | * @param string $foreignKey identifying key on foreign model |
||
1111 | * @param string $localKey identifying key on local model |
||
1112 | * |
||
1113 | * @return Relation |
||
1114 | */ |
||
1115 | View Code Duplication | public function hasOne($model, $foreignKey = '', $localKey = '') |
|
1130 | |||
1131 | /** |
||
1132 | * Creates the child side of a One-To-One or One-To-Many relationship. |
||
1133 | * |
||
1134 | * @param string $model foreign model class |
||
1135 | * @param string $foreignKey identifying key on foreign model |
||
1136 | * @param string $localKey identifying key on local model |
||
1137 | * |
||
1138 | * @return Relation |
||
1139 | */ |
||
1140 | View Code Duplication | public function belongsTo($model, $foreignKey = '', $localKey = '') |
|
1155 | |||
1156 | /** |
||
1157 | * Creates the parent side of a Many-To-One or Many-To-Many relationship. |
||
1158 | * |
||
1159 | * @param string $model foreign model class |
||
1160 | * @param string $foreignKey identifying key on foreign model |
||
1161 | * @param string $localKey identifying key on local model |
||
1162 | * |
||
1163 | * @return Relation |
||
1164 | */ |
||
1165 | View Code Duplication | public function hasMany($model, $foreignKey = '', $localKey = '') |
|
1180 | |||
1181 | /** |
||
1182 | * Creates the child side of a Many-To-Many relationship. |
||
1183 | * |
||
1184 | * @param string $model foreign model class |
||
1185 | * @param string $foreignKey identifying key on foreign model |
||
1186 | * @param string $localKey identifying key on local model |
||
1187 | * |
||
1188 | * @return Relation |
||
1189 | */ |
||
1190 | View Code Duplication | public function belongsToMany($model, $foreignKey = '', $localKey = '') |
|
1205 | |||
1206 | ///////////////////////////// |
||
1207 | // Events |
||
1208 | ///////////////////////////// |
||
1209 | |||
1210 | /** |
||
1211 | * Gets the event dispatcher. |
||
1212 | * |
||
1213 | * @return \Symfony\Component\EventDispatcher\EventDispatcher |
||
1214 | */ |
||
1215 | public static function getDispatcher($ignoreCache = false) |
||
1224 | |||
1225 | /** |
||
1226 | * Subscribes to a listener to an event. |
||
1227 | * |
||
1228 | * @param string $event event name |
||
1229 | * @param callable $listener |
||
1230 | * @param int $priority optional priority, higher #s get called first |
||
1231 | */ |
||
1232 | public static function listen($event, callable $listener, $priority = 0) |
||
1236 | |||
1237 | /** |
||
1238 | * Adds a listener to the model.creating event. |
||
1239 | * |
||
1240 | * @param callable $listener |
||
1241 | * @param int $priority |
||
1242 | */ |
||
1243 | public static function creating(callable $listener, $priority = 0) |
||
1247 | |||
1248 | /** |
||
1249 | * Adds a listener to the model.created event. |
||
1250 | * |
||
1251 | * @param callable $listener |
||
1252 | * @param int $priority |
||
1253 | */ |
||
1254 | public static function created(callable $listener, $priority = 0) |
||
1258 | |||
1259 | /** |
||
1260 | * Adds a listener to the model.updating event. |
||
1261 | * |
||
1262 | * @param callable $listener |
||
1263 | * @param int $priority |
||
1264 | */ |
||
1265 | public static function updating(callable $listener, $priority = 0) |
||
1269 | |||
1270 | /** |
||
1271 | * Adds a listener to the model.updated event. |
||
1272 | * |
||
1273 | * @param callable $listener |
||
1274 | * @param int $priority |
||
1275 | */ |
||
1276 | public static function updated(callable $listener, $priority = 0) |
||
1280 | |||
1281 | /** |
||
1282 | * Adds a listener to the model.deleting event. |
||
1283 | * |
||
1284 | * @param callable $listener |
||
1285 | * @param int $priority |
||
1286 | */ |
||
1287 | public static function deleting(callable $listener, $priority = 0) |
||
1291 | |||
1292 | /** |
||
1293 | * Adds a listener to the model.deleted event. |
||
1294 | * |
||
1295 | * @param callable $listener |
||
1296 | * @param int $priority |
||
1297 | */ |
||
1298 | public static function deleted(callable $listener, $priority = 0) |
||
1302 | |||
1303 | /** |
||
1304 | * Dispatches an event. |
||
1305 | * |
||
1306 | * @param string $eventName |
||
1307 | * |
||
1308 | * @return Model\ModelEvent |
||
1309 | */ |
||
1310 | protected function dispatch($eventName) |
||
1316 | |||
1317 | /** |
||
1318 | * Dispatches the model.creating event. |
||
1319 | * |
||
1320 | * @return bool |
||
1321 | */ |
||
1322 | View Code Duplication | private function beforeCreate() |
|
1336 | |||
1337 | /** |
||
1338 | * Dispatches the model.created event. |
||
1339 | * |
||
1340 | * @return bool |
||
1341 | */ |
||
1342 | View Code Duplication | private function afterCreate() |
|
1356 | |||
1357 | /** |
||
1358 | * Dispatches the model.updating event. |
||
1359 | * |
||
1360 | * @param array $data |
||
1361 | * |
||
1362 | * @return bool |
||
1363 | */ |
||
1364 | View Code Duplication | private function beforeUpdate(array &$data) |
|
1378 | |||
1379 | /** |
||
1380 | * Dispatches the model.updated event. |
||
1381 | * |
||
1382 | * @return bool |
||
1383 | */ |
||
1384 | View Code Duplication | private function afterUpdate() |
|
1398 | |||
1399 | /** |
||
1400 | * Dispatches the model.deleting event. |
||
1401 | * |
||
1402 | * @return bool |
||
1403 | */ |
||
1404 | View Code Duplication | private function beforeDelete() |
|
1418 | |||
1419 | /** |
||
1420 | * Dispatches the model.created event. |
||
1421 | * |
||
1422 | * @return bool |
||
1423 | */ |
||
1424 | View Code Duplication | private function afterDelete() |
|
1438 | |||
1439 | ///////////////////////////// |
||
1440 | // Validation |
||
1441 | ///////////////////////////// |
||
1442 | |||
1443 | /** |
||
1444 | * Validates and marshals a value to storage. |
||
1445 | * |
||
1446 | * @param array $property |
||
1447 | * @param string $propertyName |
||
1448 | * @param mixed $value |
||
1449 | * |
||
1450 | * @return bool |
||
1451 | */ |
||
1452 | private function filterAndValidate(array $property, $propertyName, &$value) |
||
1472 | |||
1473 | /** |
||
1474 | * Validates a value for a property. |
||
1475 | * |
||
1476 | * @param array $property |
||
1477 | * @param string $propertyName |
||
1478 | * @param mixed $value |
||
1479 | * |
||
1480 | * @return bool |
||
1481 | */ |
||
1482 | private function validate(array $property, $propertyName, $value) |
||
1502 | |||
1503 | /** |
||
1504 | * Checks if a value is unique for a property. |
||
1505 | * |
||
1506 | * @param array $property |
||
1507 | * @param string $propertyName |
||
1508 | * @param mixed $value |
||
1509 | * |
||
1510 | * @return bool |
||
1511 | */ |
||
1512 | private function checkUniqueness(array $property, $propertyName, $value) |
||
1526 | |||
1527 | /** |
||
1528 | * Gets the marshaled default value for a property (if set). |
||
1529 | * |
||
1530 | * @param string $property |
||
1531 | * |
||
1532 | * @return mixed |
||
1533 | */ |
||
1534 | private function getPropertyDefault(array $property) |
||
1538 | } |
||
1539 |
PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.
Let’s take a look at an example:
If we look at the
getEmail()
method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:On the hand, if we look at the
setEmail()
, this method _has_ side-effects. In the following case, we could not remove the method call: