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 |
||
17 | class Cache implements iCache |
||
18 | { |
||
19 | |||
20 | /** |
||
21 | * @var iAdapter |
||
22 | */ |
||
23 | private $adapter; |
||
24 | |||
25 | /** |
||
26 | * @var iSerializer |
||
27 | */ |
||
28 | private $serializer; |
||
29 | |||
30 | /** |
||
31 | * @var string |
||
32 | */ |
||
33 | private $prefix = ''; |
||
34 | |||
35 | /** |
||
36 | * @var bool |
||
37 | */ |
||
38 | private $isReady = false; |
||
39 | |||
40 | /** |
||
41 | * @var bool |
||
42 | */ |
||
43 | private $isActive = true; |
||
44 | |||
45 | /** |
||
46 | * @var mixed no cache, if admin-session is set |
||
47 | */ |
||
48 | private $isAdminSession = false; |
||
49 | |||
50 | /** |
||
51 | * __construct |
||
52 | * |
||
53 | * @param null|iAdapter $adapter |
||
54 | * @param null|iSerializer $serializer |
||
55 | * @param boolean $checkForUser check for dev-ip or if cms-user is logged-in |
||
56 | * @param boolean $cacheEnabled false will disable the cache (use it e.g. for global settings) |
||
57 | * @param string|boolean $isAdminSession set a user-id, if the user is a admin (so we can disable cache for this user) |
||
58 | */ |
||
59 | 27 | public function __construct($adapter = null, $serializer = null, $checkForUser = true, $cacheEnabled = true, $isAdminSession = false) |
|
60 | { |
||
61 | 27 | $this->isAdminSession = $isAdminSession; |
|
62 | |||
63 | // check for active-cache |
||
64 | 27 | $this->setActive($cacheEnabled); |
|
65 | 27 | if ($this->isActive === true && $checkForUser === true) { |
|
66 | $this->setActive($this->isCacheActiveForTheCurrentUser()); |
||
67 | } |
||
68 | |||
69 | 27 | if ($this->isActive === true) { |
|
70 | |||
71 | 27 | $this->setPrefix($this->getTheDefaultPrefix()); |
|
72 | |||
73 | if ( |
||
74 | $adapter === null |
||
75 | 27 | || |
|
76 | 27 | !is_object($adapter) |
|
77 | 27 | || |
|
78 | !$adapter instanceof iAdapter |
||
79 | 27 | ) { |
|
80 | $adapter = $this->autoConnectToAvailableCacheSystem(); |
||
81 | } |
||
82 | |||
83 | // Memcache(d) has his own "serializer", but it seems to be working different as the php-implementation. |
||
84 | 27 | if (!is_object($serializer) && $serializer === null) { |
|
85 | $serializer = new SerializerIgbinary(); |
||
86 | } |
||
87 | 27 | } |
|
88 | |||
89 | // check if we will use the cache |
||
90 | if ( |
||
91 | 1 | $serializer instanceof iSerializer |
|
92 | 27 | && |
|
93 | $adapter instanceof iAdapter |
||
94 | 27 | ) { |
|
95 | 27 | $this->setCacheIsReady(true); |
|
96 | |||
97 | 27 | $this->adapter = $adapter; |
|
98 | 27 | $this->serializer = $serializer; |
|
99 | 27 | } |
|
100 | 27 | } |
|
101 | |||
102 | /** |
||
103 | * enable / disable the cache |
||
104 | * |
||
105 | * @param boolean $isActive |
||
106 | */ |
||
107 | 27 | public function setActive($isActive) |
|
108 | { |
||
109 | 27 | $this->isActive = (boolean)$isActive; |
|
110 | 27 | } |
|
111 | |||
112 | /** |
||
113 | * check if the current use is a admin || dev || server == client |
||
114 | * |
||
115 | * @return bool |
||
116 | */ |
||
117 | public function isCacheActiveForTheCurrentUser() |
||
118 | { |
||
119 | $active = true; |
||
120 | |||
121 | // test the cache, with this GET-parameter |
||
122 | $testCache = isset($_GET['testCache']) ? (int)$_GET['testCache'] : 0; |
||
123 | |||
124 | if ($testCache != 1) { |
||
125 | if ( |
||
126 | // server == client |
||
127 | ( |
||
128 | isset($_SERVER['SERVER_ADDR']) |
||
129 | && |
||
130 | $_SERVER['SERVER_ADDR'] == $this->getClientIp() |
||
131 | ) |
||
132 | || |
||
133 | // admin is logged-in |
||
134 | $this->isAdminSession |
||
135 | || |
||
136 | // user is a dev |
||
137 | $this->checkForDev() === true |
||
138 | ) { |
||
139 | $active = false; |
||
140 | } |
||
141 | } |
||
142 | |||
143 | return $active; |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * returns the IP address of the client |
||
148 | * |
||
149 | * @param bool $trust_proxy_headers Whether or not to trust the |
||
150 | * proxy headers HTTP_CLIENT_IP |
||
151 | * and HTTP_X_FORWARDED_FOR. ONLY |
||
152 | * use if your $_SERVER is behind a |
||
153 | * proxy that sets these values |
||
154 | * |
||
155 | * @return string |
||
156 | */ |
||
157 | private function getClientIp($trust_proxy_headers = false) |
||
158 | { |
||
159 | $remoteAddr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'NO_REMOTE_ADDR'; |
||
160 | |||
161 | if ($trust_proxy_headers) { |
||
162 | return $remoteAddr; |
||
163 | } |
||
164 | |||
165 | if (isset($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP']) { |
||
166 | $ip = $_SERVER['HTTP_CLIENT_IP']; |
||
167 | } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR']) { |
||
168 | $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; |
||
169 | } else { |
||
170 | $ip = $remoteAddr; |
||
171 | } |
||
172 | |||
173 | return $ip; |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * check for developer |
||
178 | * |
||
179 | * @return bool |
||
180 | */ |
||
181 | private function checkForDev() |
||
182 | { |
||
183 | $return = false; |
||
184 | |||
185 | if (function_exists('checkForDev')) { |
||
186 | $return = checkForDev(); |
||
187 | } else { |
||
188 | |||
189 | // for testing with dev-address |
||
190 | $noDev = isset($_GET['noDev']) ? (int)$_GET['noDev'] : 0; |
||
191 | $remoteAddr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'NO_REMOTE_ADDR'; |
||
192 | |||
193 | if ( |
||
194 | $noDev != 1 |
||
195 | && |
||
196 | ( |
||
197 | $remoteAddr == '127.0.0.1' |
||
198 | || |
||
199 | $remoteAddr == '::1' |
||
200 | || |
||
201 | PHP_SAPI == 'cli' |
||
202 | ) |
||
203 | ) { |
||
204 | $return = true; |
||
205 | } |
||
206 | } |
||
207 | |||
208 | return $return; |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * set the default-prefix via "SERVER_NAME" + "SESSION"-language |
||
213 | */ |
||
214 | 27 | protected function getTheDefaultPrefix() |
|
215 | { |
||
216 | 27 | return (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '') . '_' . (isset($_SESSION['language']) ? $_SESSION['language'] : '') . '_' . (isset($_SESSION['language_extra']) ? $_SESSION['language_extra'] : ''); |
|
217 | } |
||
218 | |||
219 | /** |
||
220 | * auto-connect to the available cache-system on the server |
||
221 | * |
||
222 | * @return iAdapter |
||
223 | */ |
||
224 | protected function autoConnectToAvailableCacheSystem() |
||
225 | { |
||
226 | static $adapterCache; |
||
227 | |||
228 | if (is_object($adapterCache) && $adapterCache instanceof iAdapter) { |
||
229 | return $adapterCache; |
||
230 | } else { |
||
231 | |||
232 | $memcached = null; |
||
233 | $isMemcachedAvailable = false; |
||
234 | if (extension_loaded('memcached')) { |
||
235 | $memcached = new \Memcached(); |
||
236 | $isMemcachedAvailable = $memcached->addServer('127.0.0.1', '11211'); |
||
237 | } |
||
238 | |||
239 | if ($isMemcachedAvailable === false) { |
||
240 | $memcached = null; |
||
241 | } |
||
242 | |||
243 | $adapterMemcached = new AdapterMemcached($memcached); |
||
244 | if ($adapterMemcached->installed() === true) { |
||
245 | |||
246 | // fallback to Memcached |
||
247 | $adapter = $adapterMemcached; |
||
248 | |||
249 | } else { |
||
250 | |||
251 | $memcache = null; |
||
252 | $isMemcacheAvailable = false; |
||
253 | if (class_exists('\Memcache')) { |
||
254 | $memcache = new \Memcache; |
||
255 | /** @noinspection PhpUsageOfSilenceOperatorInspection */ |
||
256 | $isMemcacheAvailable = @$memcache->connect('127.0.0.1', 11211); |
||
257 | } |
||
258 | |||
259 | if ($isMemcacheAvailable === false) { |
||
260 | $memcache = null; |
||
261 | } |
||
262 | |||
263 | $adapterMemcache = new AdapterMemcache($memcache); |
||
264 | if ($adapterMemcache->installed() === true) { |
||
265 | |||
266 | // fallback to Memcache |
||
267 | $adapter = $adapterMemcache; |
||
268 | |||
269 | } else { |
||
270 | |||
271 | $redis = null; |
||
272 | $isRedisAvailable = false; |
||
273 | if (extension_loaded('redis')) { |
||
274 | if (class_exists('\Predis\Client')) { |
||
275 | $redis = new \Predis\Client( |
||
276 | array( |
||
277 | 'scheme' => 'tcp', |
||
278 | 'host' => '127.0.0.1', |
||
279 | 'port' => 6379, |
||
280 | 'timeout' => '2.0', |
||
281 | ) |
||
282 | ); |
||
283 | try { |
||
284 | $redis->connect(); |
||
285 | $isRedisAvailable = $redis->getConnection()->isConnected(); |
||
286 | } catch (\Exception $e) { |
||
287 | // nothing |
||
288 | } |
||
289 | } |
||
290 | } |
||
291 | |||
292 | if ($isRedisAvailable === false) { |
||
293 | $redis = null; |
||
294 | } |
||
295 | |||
296 | $adapterRedis = new AdapterPredis($redis); |
||
297 | if ($adapterRedis->installed() === true) { |
||
298 | |||
299 | // fallback to Redis |
||
300 | $adapter = $adapterRedis; |
||
301 | |||
302 | } else { |
||
303 | |||
304 | $adapterXcache = new AdapterXcache(); |
||
305 | if ($adapterXcache->installed() === true) { |
||
306 | |||
307 | // fallback to Xcache |
||
308 | $adapter = $adapterXcache; |
||
309 | |||
310 | } else { |
||
311 | |||
312 | $adapterApc = new AdapterApc(); |
||
313 | if ($adapterApc->installed() === true) { |
||
314 | |||
315 | // fallback to APC || APCu |
||
316 | $adapter = $adapterApc; |
||
317 | |||
318 | } else { |
||
319 | |||
320 | $adapterFile = new AdapterFile(); |
||
321 | if ($adapterFile->installed() === true) { |
||
322 | |||
323 | // fallback to File-Cache |
||
324 | $adapter = $adapterFile; |
||
325 | |||
326 | } else { |
||
327 | // no cache-adapter available -> use a array |
||
328 | $adapter = new AdapterArray(); |
||
329 | } |
||
330 | } |
||
331 | } |
||
332 | } |
||
333 | } |
||
334 | } |
||
335 | |||
336 | // save to static cache |
||
337 | $adapterCache = $adapter; |
||
338 | } |
||
339 | |||
340 | return $adapter; |
||
341 | } |
||
342 | |||
343 | /** |
||
344 | * set cacheIsReady state |
||
345 | * |
||
346 | * @param boolean $isReady |
||
347 | */ |
||
348 | 27 | private function setCacheIsReady($isReady) |
|
352 | |||
353 | /** |
||
354 | * get the cacheIsReady state |
||
355 | * |
||
356 | * @return boolean |
||
357 | */ |
||
358 | 3 | public function getCacheIsReady() |
|
362 | |||
363 | /** |
||
364 | * get cached-item by key |
||
365 | * |
||
366 | * @param string $key |
||
367 | * |
||
368 | * @return mixed |
||
369 | */ |
||
370 | 12 | public function getItem($key) |
|
383 | |||
384 | /** |
||
385 | * calculate store-key (prefix + $rawKey) |
||
386 | * |
||
387 | * @param String $rawKey |
||
388 | * |
||
389 | * @return string |
||
390 | */ |
||
391 | 23 | private function calculateStoreKey($rawKey) |
|
395 | |||
396 | /** |
||
397 | * @return mixed |
||
398 | */ |
||
399 | 23 | public function getPrefix() |
|
403 | |||
404 | /** |
||
405 | * set prefix [WARNING: do not use if you don't know what you do] |
||
406 | * |
||
407 | * @param string $prefix |
||
408 | */ |
||
409 | 27 | public function setPrefix($prefix) |
|
413 | |||
414 | /** |
||
415 | * set cache-item by key => value + date |
||
416 | * |
||
417 | * @param string $key |
||
418 | * @param mixed $value |
||
419 | * @param \DateTime $date |
||
420 | * |
||
421 | * @return mixed|void |
||
422 | * @throws \Exception |
||
423 | */ |
||
424 | 5 | public function setItemToDate($key, $value, \DateTime $date) |
|
436 | |||
437 | /** |
||
438 | * set cache-item by key => value + ttl |
||
439 | * |
||
440 | * @param string $key |
||
441 | * @param mixed $value |
||
442 | * @param int $ttl |
||
443 | * |
||
444 | * @return bool |
||
445 | */ |
||
446 | 12 | public function setItem($key, $value, $ttl = 0) |
|
466 | |||
467 | /** |
||
468 | * remove cached-item |
||
469 | * |
||
470 | * @param string $key |
||
471 | * |
||
472 | * @return bool |
||
473 | */ |
||
474 | 1 | View Code Duplication | public function removeItem($key) |
484 | |||
485 | /** |
||
486 | * check if cached-item exists |
||
487 | * |
||
488 | * @param string $key |
||
489 | * |
||
490 | * @return boolean |
||
491 | */ |
||
492 | 4 | View Code Duplication | public function existsItem($key) |
502 | |||
503 | } |
||
504 |
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: