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 RestRequest 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 RestRequest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
11 | class RestRequest |
||
12 | { |
||
13 | /** |
||
14 | * Request parameter (query string) constants. |
||
15 | */ |
||
16 | const PARAM_INCLUSIONS = 'include'; |
||
17 | const PARAM_FIELDSETS = 'fields'; |
||
18 | const PARAM_SORTING = 'sort'; |
||
19 | const PARAM_PAGINATION = 'page'; |
||
20 | const PARAM_FILTERING = 'filter'; |
||
21 | |||
22 | /** |
||
23 | * Filter parameters. |
||
24 | */ |
||
25 | const FILTER_AUTOCOMPLETE = 'autocomplete'; |
||
26 | const FILTER_AUTOCOMPLETE_KEY = 'key'; |
||
27 | const FILTER_AUTOCOMPLETE_VALUE = 'value'; |
||
28 | const FILTER_QUERY = 'query'; |
||
29 | const FILTER_QUERY_CRITERIA = 'criteria'; |
||
30 | |||
31 | /** |
||
32 | * The request method, such as GET, POST, PATCH, etc. |
||
33 | * |
||
34 | * @var string |
||
35 | */ |
||
36 | private $requestMethod; |
||
37 | |||
38 | /** |
||
39 | * The parsed URL/URI, via PHP's parse_url(). |
||
40 | * |
||
41 | * @var array |
||
42 | */ |
||
43 | private $parsedUri = []; |
||
44 | |||
45 | /** |
||
46 | * The entity type requested. |
||
47 | * |
||
48 | * @var string |
||
49 | */ |
||
50 | private $entityType; |
||
51 | |||
52 | /** |
||
53 | * The entity identifier (id) value, if sent. |
||
54 | * |
||
55 | * @var string|null |
||
56 | */ |
||
57 | private $identifier; |
||
58 | |||
59 | /** |
||
60 | * The entity relationship properties, if sent. |
||
61 | * |
||
62 | * @var array |
||
63 | */ |
||
64 | private $relationship = []; |
||
65 | |||
66 | /** |
||
67 | * Relationship fields to include with the response. |
||
68 | * AKA: sideloading the entities of relationships. |
||
69 | * Either a associative array of relationshipKeys => true to specifically include. |
||
70 | * Or a single associative key of '*' => true if all should be included. |
||
71 | * |
||
72 | * @var array |
||
73 | */ |
||
74 | private $inclusions = []; |
||
75 | |||
76 | /** |
||
77 | * Sorting criteria. |
||
78 | * |
||
79 | * @var array |
||
80 | */ |
||
81 | private $sorting = []; |
||
82 | |||
83 | /** |
||
84 | * Fields to only include with the response. |
||
85 | * |
||
86 | * @var array |
||
87 | */ |
||
88 | private $fields = []; |
||
89 | |||
90 | /** |
||
91 | * Pagination (limit/skip) criteria. |
||
92 | * |
||
93 | * @var array |
||
94 | */ |
||
95 | private $pagination = []; |
||
96 | |||
97 | /** |
||
98 | * Any request filters, such as quering, search, autocomplete, etc. |
||
99 | * Must ultimately be handled by the Adapter to function. |
||
100 | * |
||
101 | * @var array |
||
102 | */ |
||
103 | private $filters = []; |
||
104 | |||
105 | /** |
||
106 | * The request payload, if sent. |
||
107 | * Used for updating/creating entities. |
||
108 | * |
||
109 | * @var RestPayload|null |
||
110 | */ |
||
111 | private $payload; |
||
112 | |||
113 | /** |
||
114 | * The REST configuration. |
||
115 | * |
||
116 | * @var RestConfiguration |
||
117 | */ |
||
118 | private $config; |
||
119 | |||
120 | /** |
||
121 | * Constructor. |
||
122 | * |
||
123 | * @param RestConfiguration $config The REST configuration. |
||
124 | * @param string $method The request method. |
||
125 | * @param string $uri The complete URI (URL) of the request, included scheme, host, path, and query string. |
||
126 | * @param string|null $payload The request payload (body). |
||
127 | */ |
||
128 | public function __construct(RestConfiguration $config, $method, $uri, $payload = null) |
||
144 | |||
145 | /** |
||
146 | * Generates the request URL based on its current object state. |
||
147 | * |
||
148 | * @todo Add support for inclusions and other items. |
||
149 | * @return string |
||
150 | */ |
||
151 | public function getUrl() |
||
162 | |||
163 | protected function adjustRootEndpoint($path) |
||
172 | |||
173 | /** |
||
174 | * Gets the scheme, such as http or https. |
||
175 | * |
||
176 | * @return string |
||
177 | */ |
||
178 | public function getScheme() |
||
182 | |||
183 | /** |
||
184 | * Gets the hostname. |
||
185 | * |
||
186 | * @return string |
||
187 | */ |
||
188 | public function getHost() |
||
192 | |||
193 | /** |
||
194 | * Gets the request method, such as GET, POST, PATCH, etc. |
||
195 | * |
||
196 | * @return string |
||
197 | */ |
||
198 | public function getMethod() |
||
202 | |||
203 | /** |
||
204 | * Gets the requested entity type. |
||
205 | * |
||
206 | * @return string |
||
207 | */ |
||
208 | public function getEntityType() |
||
212 | |||
213 | /** |
||
214 | * Gets the requested entity identifier (id), if sent. |
||
215 | * |
||
216 | * @return string|null |
||
217 | */ |
||
218 | public function getIdentifier() |
||
222 | |||
223 | /** |
||
224 | * Gets the query string based on the current object properties. |
||
225 | * |
||
226 | * @return string |
||
227 | */ |
||
228 | public function getQueryString() |
||
249 | |||
250 | /** |
||
251 | * Determines if an entity identifier (id) was sent with the request. |
||
252 | * |
||
253 | * @return bool |
||
254 | */ |
||
255 | public function hasIdentifier() |
||
259 | |||
260 | /** |
||
261 | * Determines if this is an entity relationship request. |
||
262 | * |
||
263 | * @return bool |
||
264 | */ |
||
265 | public function isRelationship() |
||
269 | |||
270 | /** |
||
271 | * Gets the entity relationship request. |
||
272 | * |
||
273 | * @return array |
||
274 | */ |
||
275 | public function getRelationship() |
||
279 | |||
280 | /** |
||
281 | * Gets the entity relationship field key. |
||
282 | * |
||
283 | * @return string|null |
||
284 | */ |
||
285 | public function getRelationshipFieldKey() |
||
292 | |||
293 | /** |
||
294 | * Determines if this is an entity relationship retrieve request. |
||
295 | * |
||
296 | * @return bool |
||
297 | */ |
||
298 | View Code Duplication | public function isRelationshipRetrieve() |
|
305 | |||
306 | /** |
||
307 | * Determines if this is an entity relationship modify (create/update/delete) request. |
||
308 | * |
||
309 | * @return bool |
||
310 | */ |
||
311 | View Code Duplication | public function isRelationshipModify() |
|
318 | |||
319 | /** |
||
320 | * Determines if this has an autocomplete filter enabled. |
||
321 | * |
||
322 | * @return bool |
||
323 | */ |
||
324 | View Code Duplication | public function isAutocomplete() |
|
332 | |||
333 | /** |
||
334 | * Gets the autocomplete attribute key. |
||
335 | * |
||
336 | * @return string|null |
||
337 | */ |
||
338 | View Code Duplication | public function getAutocompleteKey() |
|
345 | |||
346 | /** |
||
347 | * Gets the autocomplete search value. |
||
348 | * |
||
349 | * @return string|null |
||
350 | */ |
||
351 | View Code Duplication | public function getAutocompleteValue() |
|
358 | |||
359 | /** |
||
360 | * Determines if this has the database query filter enabled. |
||
361 | * |
||
362 | * @return bool |
||
363 | */ |
||
364 | View Code Duplication | public function isQuery() |
|
372 | |||
373 | /** |
||
374 | * Gets the query criteria value. |
||
375 | * |
||
376 | * @return array |
||
377 | */ |
||
378 | public function getQueryCriteria() |
||
394 | |||
395 | /** |
||
396 | * Determines if specific sideloaded include fields were requested. |
||
397 | * |
||
398 | * @return bool |
||
399 | */ |
||
400 | public function hasInclusions() |
||
405 | |||
406 | /** |
||
407 | * Gets specific sideloaded relationship fields to include. |
||
408 | * |
||
409 | * @return array |
||
410 | */ |
||
411 | public function getInclusions() |
||
415 | |||
416 | /** |
||
417 | * Determines if a specific return fieldset has been specified. |
||
418 | * |
||
419 | * @return bool |
||
420 | */ |
||
421 | public function hasFieldset() |
||
426 | |||
427 | /** |
||
428 | * Gets the return fieldset to use. |
||
429 | * |
||
430 | * @return array |
||
431 | */ |
||
432 | public function getFieldset() |
||
436 | |||
437 | /** |
||
438 | * Determines if the request has specified sorting criteria. |
||
439 | * |
||
440 | * @return bool |
||
441 | */ |
||
442 | public function hasSorting() |
||
447 | |||
448 | /** |
||
449 | * Gets the sorting criteria. |
||
450 | * |
||
451 | * @return array |
||
452 | */ |
||
453 | public function getSorting() |
||
457 | |||
458 | /** |
||
459 | * Determines if the request has specified pagination (limit/offset) criteria. |
||
460 | * |
||
461 | * @return bool |
||
462 | */ |
||
463 | public function hasPagination() |
||
468 | |||
469 | /** |
||
470 | * Gets the pagination (limit/offset) criteria. |
||
471 | * |
||
472 | * @return array |
||
473 | */ |
||
474 | public function getPagination() |
||
478 | |||
479 | /** |
||
480 | * Sets the pagination (limit/offset) criteria. |
||
481 | * |
||
482 | * @param int $offset |
||
483 | * @param int $limit |
||
484 | * @return self |
||
485 | */ |
||
486 | public function setPagination($offset, $limit) |
||
492 | |||
493 | /** |
||
494 | * Determines if the request has any filtering criteria. |
||
495 | * |
||
496 | * @return bool |
||
497 | */ |
||
498 | public function hasFilters() |
||
502 | |||
503 | /** |
||
504 | * Determines if a specific filter exists, by key |
||
505 | * |
||
506 | * @param string $key |
||
507 | * @return bool |
||
508 | */ |
||
509 | public function hasFilter($key) |
||
513 | |||
514 | /** |
||
515 | * Gets a specific filter, by key. |
||
516 | * |
||
517 | * @param string $key |
||
518 | * @return mixed|null |
||
519 | */ |
||
520 | public function getFilter($key) |
||
527 | |||
528 | /** |
||
529 | * Gets the request payload. |
||
530 | * |
||
531 | * @return RestPayload|null |
||
532 | */ |
||
533 | public function getPayload() |
||
537 | |||
538 | /** |
||
539 | * Determines if a request payload is present. |
||
540 | * |
||
541 | * @return bool |
||
542 | */ |
||
543 | public function hasPayload() |
||
547 | |||
548 | /** |
||
549 | * Parses the incoming request URI/URL and sets the appropriate properties on this RestRequest object. |
||
550 | * |
||
551 | * @param string $uri |
||
552 | * @return self |
||
553 | * @throws RestException |
||
554 | */ |
||
555 | private function parse($uri) |
||
573 | |||
574 | /** |
||
575 | * Parses the incoming request path and sets appropriate properties on this RestRequest object. |
||
576 | * |
||
577 | * @param string $path |
||
578 | * @return self |
||
579 | * @throws RestException |
||
580 | */ |
||
581 | private function parsePath($path) |
||
595 | |||
596 | /** |
||
597 | * Extracts the entity type from an array of path parts. |
||
598 | * |
||
599 | * @param array $parts |
||
600 | * @return self |
||
601 | */ |
||
602 | private function extractEntityType(array $parts) |
||
607 | |||
608 | /** |
||
609 | * Extracts the entity identifier (id) from an array of path parts. |
||
610 | * |
||
611 | * @param array $parts |
||
612 | * @return self |
||
613 | */ |
||
614 | private function extractIdentifier(array $parts) |
||
621 | |||
622 | /** |
||
623 | * Extracts the entity relationship properties from an array of path parts. |
||
624 | * |
||
625 | * @param array $parts |
||
626 | * @return self |
||
627 | */ |
||
628 | private function extractRelationship(array $parts) |
||
648 | |||
649 | /** |
||
650 | * Parses the incoming request query string and sets appropriate properties on this RestRequest object. |
||
651 | * |
||
652 | * @param string $queryString |
||
653 | * @return self |
||
654 | * @throws RestException |
||
655 | */ |
||
656 | private function parseQueryString($queryString) |
||
674 | |||
675 | /** |
||
676 | * Extracts relationship inclusions from an array of query params. |
||
677 | * |
||
678 | * @param array $params |
||
679 | * @return self |
||
680 | */ |
||
681 | private function extractInclusions(array $params) |
||
698 | |||
699 | /** |
||
700 | * Extracts sorting criteria from an array of query params. |
||
701 | * |
||
702 | * @param array $params |
||
703 | * @return self |
||
704 | */ |
||
705 | private function extractSorting(array $params) |
||
722 | |||
723 | /** |
||
724 | * Extracts fields to return from an array of query params. |
||
725 | * |
||
726 | * @param array $params |
||
727 | * @return self |
||
728 | */ |
||
729 | View Code Duplication | private function extractFields(array $params) |
|
743 | |||
744 | /** |
||
745 | * Extracts pagination criteria from an array of query params. |
||
746 | * |
||
747 | * @param array $params |
||
748 | * @return self |
||
749 | */ |
||
750 | private function extractPagination(array $params) |
||
765 | |||
766 | /** |
||
767 | * Extracts filtering criteria from an array of query params. |
||
768 | * |
||
769 | * @param array $params |
||
770 | * @return self |
||
771 | */ |
||
772 | View Code Duplication | private function extractFilters(array $params) |
|
786 | |||
787 | /** |
||
788 | * Gets query string parameters that this request supports. |
||
789 | * |
||
790 | * @return array |
||
791 | */ |
||
792 | public function getSupportedParams() |
||
802 | |||
803 | /** |
||
804 | * Helper that determines if a key and value is set and is not empty. |
||
805 | * |
||
806 | * @param string $key |
||
807 | * @param mixed $value |
||
808 | * @return bool |
||
809 | */ |
||
810 | private function issetNotEmpty($key, $value) |
||
814 | } |
||
815 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: