88use Psr \Log \LoggerInterface ;
99use Symfony \Component \Cache \Adapter \AbstractAdapter ;
1010use Symfony \Component \Cache \Adapter \FilesystemAdapter ;
11+ use Symfony \Component \Cache \Adapter \MemcachedAdapter ;
1112use Symfony \Component \Cache \PruneableInterface ;
1213
1314/**
@@ -243,7 +244,7 @@ private function saveRemediations(array $decisions): bool
243244 }
244245 }
245246
246- return $ this ->adapter -> commit ();
247+ return $ this ->commit ();
247248 }
248249
249250 private function removeRemediations (array $ decisions ): bool
@@ -268,7 +269,7 @@ private function removeRemediations(array $decisions): bool
268269 }
269270 }
270271
271- return $ this ->adapter -> commit ();
272+ return $ this ->commit ();
272273 }
273274
274275 /**
@@ -291,17 +292,22 @@ private function saveRemediationsForIp(array $decisions, string $ip): string
291292 $ remediation = $ this ->formatRemediationFromDecision (null );
292293 $ remediationResult = $ this ->addRemediationToCacheItem ($ ip , $ remediation [0 ], $ remediation [1 ], $ remediation [2 ]);
293294 }
294- $ this ->adapter -> commit ();
295+ $ this ->commit ();
295296
296297 return $ remediationResult ;
297298 }
298299
299300 public function clear (): bool
300301 {
301- $ cleared = $ this ->adapter ->clear ();
302+ $ this ->setCustomErrorHandler ();
303+ try {
304+ $ cleared = $ this ->adapter ->clear ();
305+ } finally {
306+ $ this ->unsetCustomErrorHandler ();
307+ }
302308 $ this ->warmedUp = false ;
303309 $ this ->defferUpdateCacheConfig (['warmed_up ' => $ this ->warmedUp ]);
304- $ this ->adapter -> commit ();
310+ $ this ->commit ();
305311 $ this ->logger ->info ('' , ['type ' => 'CACHE_CLEARED ' ]);
306312
307313 return $ cleared ;
@@ -328,7 +334,7 @@ public function warmUp(): int
328334 if ($ newDecisions ) {
329335 $ this ->warmedUp = $ this ->saveRemediations ($ newDecisions );
330336 $ this ->defferUpdateCacheConfig (['warmed_up ' => $ this ->warmedUp ]);
331- $ this ->adapter -> commit ();
337+ $ this ->commit ();
332338 if (!$ this ->warmedUp ) {
333339 throw new BouncerException ('Unable to warm the cache up ' );
334340 }
@@ -338,7 +344,7 @@ public function warmUp(): int
338344 // Store the fact that the cache has been warmed up.
339345 $ this ->defferUpdateCacheConfig (['warmed_up ' => true ]);
340346
341- $ this ->adapter -> commit ();
347+ $ this ->commit ();
342348 $ this ->logger ->info ('' , ['type ' => 'CACHE_WARMED_UP ' , 'added_decisions ' => $ nbNew ]);
343349
344350 return $ nbNew ;
@@ -447,6 +453,9 @@ public function get(string $ip): string
447453 return $ remediation ;
448454 }
449455
456+ /**
457+ * Prune the cache (only when using PHP File System cache).
458+ */
450459 public function prune (): bool
451460 {
452461 if ($ this ->adapter instanceof PruneableInterface) {
@@ -458,4 +467,59 @@ public function prune(): bool
458467
459468 throw new BouncerException ('Cache Adapter ' .\get_class ($ this ->adapter ).' is not prunable. ' );
460469 }
470+
471+ /**
472+ * When Memcached connection fail, it throw an unhandled warning.
473+ * To catch this warning as a clean execption we have to temporarily change the error handler.
474+ */
475+ private function setCustomErrorHandler (): void
476+ {
477+ if ($ this ->adapter instanceof MemcachedAdapter) {
478+ set_error_handler (function () {
479+ throw new BouncerException ('Error when connecting to Memcached. Please fix the Memcached DSN or select another cache technology. ' );
480+ });
481+ }
482+ }
483+
484+ /**
485+ * When the selected cache adapter is MemcachedAdapter, revert to the previous error handler.
486+ * */
487+ private function unsetCustomErrorHandler (): void
488+ {
489+ if ($ this ->adapter instanceof MemcachedAdapter) {
490+ restore_error_handler ();
491+ }
492+ }
493+
494+ /**
495+ * Wrap the cacheAdapter to catch warnings.
496+ *
497+ * @throws BouncerException if the connection was not successful
498+ * */
499+ private function commit (): bool
500+ {
501+ $ this ->setCustomErrorHandler ();
502+ try {
503+ $ result = $ this ->adapter ->commit ();
504+ } finally {
505+ $ this ->unsetCustomErrorHandler ();
506+ }
507+
508+ return $ result ;
509+ }
510+
511+ /**
512+ * Test the connection to the cache system (Redis or Memcached).
513+ *
514+ * @throws BouncerException if the connection was not successful
515+ * */
516+ public function testConnection (): void
517+ {
518+ $ this ->setCustomErrorHandler ();
519+ try {
520+ $ this ->adapter ->getItem (' ' );
521+ } finally {
522+ $ this ->unsetCustomErrorHandler ();
523+ }
524+ }
461525}
0 commit comments