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 Cache 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 Cache, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class Cache implements iCache |
||
19 | { |
||
20 | |||
21 | /** |
||
22 | * @var iAdapter |
||
23 | */ |
||
24 | private $adapter; |
||
25 | |||
26 | /** |
||
27 | * @var iSerializer |
||
28 | */ |
||
29 | private $serializer; |
||
30 | |||
31 | /** |
||
32 | * @var string |
||
33 | */ |
||
34 | private $prefix = ''; |
||
35 | |||
36 | /** |
||
37 | * @var bool |
||
38 | */ |
||
39 | private $isReady = false; |
||
40 | |||
41 | /** |
||
42 | * @var bool |
||
43 | */ |
||
44 | private $isActive = true; |
||
45 | |||
46 | /** |
||
47 | * @var mixed no cache, if admin-session is set |
||
48 | */ |
||
49 | private $isAdminSession = false; |
||
50 | |||
51 | /** |
||
52 | * __construct |
||
53 | * |
||
54 | * @param null|iAdapter $adapter |
||
55 | * @param null|iSerializer $serializer |
||
56 | * @param boolean $checkForUser check for dev-ip or if cms-user is logged-in |
||
57 | * @param boolean $cacheEnabled false will disable the cache (use it e.g. for global settings) |
||
58 | * @param string|boolean $isAdminSession set a user-id, if the user is a admin (so we can disable cache for this |
||
59 | * user) |
||
60 | */ |
||
61 | 42 | public function __construct($adapter = null, $serializer = null, $checkForUser = true, $cacheEnabled = true, $isAdminSession = false) |
|
62 | { |
||
63 | 42 | $this->isAdminSession = $isAdminSession; |
|
64 | |||
65 | // First check if the cache is active at all. |
||
66 | 42 | $this->setActive($cacheEnabled); |
|
67 | if ( |
||
68 | 42 | $this->isActive === true |
|
69 | 42 | && |
|
70 | $checkForUser === true |
||
71 | 42 | ) { |
|
72 | $this->setActive($this->isCacheActiveForTheCurrentUser()); |
||
73 | } |
||
74 | |||
75 | // If the cache is active, then try to auto-connect to the best possible cache-system. |
||
76 | 42 | if ($this->isActive === true) { |
|
77 | |||
78 | 42 | $this->setPrefix($this->getTheDefaultPrefix()); |
|
79 | |||
80 | if ( |
||
81 | $adapter === null |
||
82 | 42 | || |
|
83 | 42 | !is_object($adapter) |
|
84 | 42 | || |
|
85 | !$adapter instanceof iAdapter |
||
86 | 42 | ) { |
|
87 | $adapter = $this->autoConnectToAvailableCacheSystem(); |
||
88 | } |
||
89 | |||
90 | // INFO: Memcache(d) has his own "serializer", so don't use it twice |
||
91 | 42 | if (!is_object($serializer) && $serializer === null) { |
|
92 | if ( |
||
93 | $adapter instanceof AdapterMemcached |
||
94 | || |
||
95 | $adapter instanceof AdapterMemcache |
||
96 | ) { |
||
97 | $serializer = new SerializerNo(); |
||
98 | } else { |
||
99 | // set default serializer |
||
100 | $serializer = new SerializerIgbinary(); |
||
101 | } |
||
102 | } |
||
103 | 42 | } |
|
104 | |||
105 | // Final checks ... |
||
106 | if ( |
||
107 | $serializer instanceof iSerializer |
||
108 | 42 | && |
|
109 | $adapter instanceof iAdapter |
||
110 | 42 | ) { |
|
111 | 42 | $this->setCacheIsReady(true); |
|
112 | |||
113 | 42 | $this->adapter = $adapter; |
|
114 | 42 | $this->serializer = $serializer; |
|
115 | 42 | } |
|
116 | 42 | } |
|
117 | |||
118 | /** |
||
119 | * enable / disable the cache |
||
120 | * |
||
121 | * @param boolean $isActive |
||
122 | */ |
||
123 | 42 | public function setActive($isActive) |
|
124 | { |
||
125 | 42 | $this->isActive = (boolean)$isActive; |
|
126 | 42 | } |
|
127 | |||
128 | /** |
||
129 | * check if the current use is a admin || dev || server == client |
||
130 | * |
||
131 | * @return bool |
||
132 | */ |
||
133 | public function isCacheActiveForTheCurrentUser() |
||
161 | |||
162 | /** |
||
163 | * returns the IP address of the client |
||
164 | * |
||
165 | * @param bool $trust_proxy_headers Whether or not to trust the |
||
166 | * proxy headers HTTP_CLIENT_IP |
||
167 | * and HTTP_X_FORWARDED_FOR. ONLY |
||
168 | * use if your $_SERVER is behind a |
||
169 | * proxy that sets these values |
||
170 | * |
||
171 | * @return string |
||
172 | */ |
||
173 | private function getClientIp($trust_proxy_headers = false) |
||
191 | |||
192 | /** |
||
193 | * Check for local developer. |
||
194 | * |
||
195 | * @return bool |
||
196 | */ |
||
197 | private function checkForDev() |
||
198 | { |
||
199 | $return = false; |
||
200 | |||
201 | if (function_exists('checkForDev')) { |
||
202 | $return = checkForDev(); |
||
203 | } else { |
||
204 | |||
205 | // for testing with dev-address |
||
206 | $noDev = isset($_GET['noDev']) ? (int)$_GET['noDev'] : 0; |
||
207 | $remoteAddr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'NO_REMOTE_ADDR'; |
||
208 | |||
209 | if ( |
||
210 | $noDev != 1 |
||
211 | && |
||
212 | ( |
||
213 | $remoteAddr === '127.0.0.1' |
||
214 | || |
||
215 | $remoteAddr === '::1' |
||
216 | || |
||
217 | PHP_SAPI === 'cli' |
||
218 | ) |
||
219 | ) { |
||
220 | $return = true; |
||
221 | } |
||
222 | } |
||
223 | |||
224 | return $return; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Set the default-prefix via "SERVER"-var + "SESSION"-language. |
||
229 | */ |
||
230 | 42 | protected function getTheDefaultPrefix() |
|
238 | |||
239 | /** |
||
240 | * Auto-connect to the available cache-system on the server. |
||
241 | * |
||
242 | * @return iAdapter |
||
243 | */ |
||
244 | protected function autoConnectToAvailableCacheSystem() |
||
392 | |||
393 | /** |
||
394 | * Set "isReady" state. |
||
395 | * |
||
396 | * @param boolean $isReady |
||
397 | */ |
||
398 | 42 | private function setCacheIsReady($isReady) |
|
402 | |||
403 | /** |
||
404 | * Get the "isReady" state. |
||
405 | * |
||
406 | * @return boolean |
||
407 | */ |
||
408 | 4 | public function getCacheIsReady() |
|
412 | |||
413 | /** |
||
414 | * Get cached-item by key. |
||
415 | * |
||
416 | * @param string $key |
||
417 | * @param int $staticCacheHitCounter WARNING: This static cache has no TTL, it will be cleaned on the next request |
||
418 | * and it will use more memory as e.g. memcache. |
||
419 | * |
||
420 | * @return mixed |
||
421 | */ |
||
422 | 19 | public function getItem($key, $staticCacheHitCounter = 0) |
|
471 | |||
472 | /** |
||
473 | * Calculate store-key (prefix + $rawKey). |
||
474 | * |
||
475 | * @param string $rawKey |
||
476 | * |
||
477 | * @return string |
||
478 | */ |
||
479 | 33 | private function calculateStoreKey($rawKey) |
|
489 | |||
490 | /** |
||
491 | * Clean store-key (required e.g. for the "File"-Adapter). |
||
492 | * |
||
493 | * @param string $str |
||
494 | * |
||
495 | * @return string |
||
496 | */ |
||
497 | 6 | private function cleanStoreKey($str) |
|
523 | |||
524 | /** |
||
525 | * Get the prefix. |
||
526 | * |
||
527 | * @return string |
||
528 | */ |
||
529 | 33 | public function getPrefix() |
|
533 | |||
534 | /** |
||
535 | * !!! Set the prefix. !!! |
||
536 | * |
||
537 | * WARNING: Do not use if you don't know what you do. Because this will overwrite the default prefix. |
||
538 | * |
||
539 | * @param string $prefix |
||
540 | */ |
||
541 | 42 | public function setPrefix($prefix) |
|
545 | |||
546 | /** |
||
547 | * Set cache-item by key => value + date. |
||
548 | * |
||
549 | * @param string $key |
||
550 | * @param mixed $value |
||
551 | * @param \DateTime $date |
||
552 | * |
||
553 | * @return boolean |
||
554 | * @throws \Exception |
||
555 | */ |
||
556 | 6 | public function setItemToDate($key, $value, \DateTime $date) |
|
568 | |||
569 | /** |
||
570 | * Set cache-item by key => value + ttl. |
||
571 | * |
||
572 | * @param string $key |
||
573 | * @param mixed $value |
||
574 | * @param int $ttl |
||
575 | * |
||
576 | * @return bool |
||
577 | */ |
||
578 | 18 | public function setItem($key, $value, $ttl = 0) |
|
597 | |||
598 | /** |
||
599 | * Remove a cached-item. |
||
600 | * |
||
601 | * @param string $key |
||
602 | * |
||
603 | * @return bool |
||
604 | */ |
||
605 | 2 | View Code Duplication | public function removeItem($key) |
615 | |||
616 | /** |
||
617 | * Remove all cached-items. |
||
618 | * |
||
619 | * @return bool |
||
620 | */ |
||
621 | 1 | public function removeAll() |
|
629 | |||
630 | /** |
||
631 | * Check if cached-item exists. |
||
632 | * |
||
633 | * @param string $key |
||
634 | * |
||
635 | * @return boolean |
||
636 | */ |
||
637 | 6 | View Code Duplication | public function existsItem($key) |
647 | |||
648 | /** |
||
649 | * Get the current adapter class-name. |
||
650 | * |
||
651 | * @return string |
||
652 | */ |
||
653 | 2 | public function getUsedAdapterClassName() |
|
657 | |||
658 | /** |
||
659 | * Get the current serializer class-name. |
||
660 | * |
||
661 | * @return string |
||
662 | */ |
||
663 | 2 | public function getUsedSerializerClassName() |
|
667 | } |
||
668 |
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: