Memcached instance added to DI Container not working

Hello,

I’ve been struggling with the recommended approach for adding a Memcached object to the Slim 4 DI Container. This is what i have in public/index.php

// Build PHP-DI Container instance
$container = $containerBuilder->build();

// Memcached instance
$container->set('memcached', function () {
  $memcachedThrottleKey = 'my-domain/throttle-key-' . $_SERVER['REMOTE_ADDR'];
  $mc = new Memcached(); 
  $mc->addServer('127.0.0.1', 11211);
  $mc->set($memcachedThrottleKey, date("Y-m-d h:i:s")); 
  
  return $mc;
});

// Instantiate the app
AppFactory::setContainer($container);

Then in one of my actions I have …

/**
 * @param Memcached $memcached
 * @param LoggerInterface $logger
 */
public function __construct(ContainerInterface $container, LoggerInterface $logger)
{
    parent::__construct($logger);
    $this->memcached = $container->get('memcached');
}

protected function action(): Response
{       
      $memcachedThrottleKey = 'my-domain/throttle-key-' . $_SERVER['REMOTE_ADDR'];
      $lastThrottleTime = $this->memcached->get($memcachedThrottleKey);
      $this->logger->info("Last throttle time: " . $lastThrottleTime);
               .
               .
               .
}

However, in the app.log file, you can see the value retrieved from the memcached object is empty:
[2020-10-05 01:51:12] slim-app.INFO: Last throttle time: [] {“uid”:“077eeee”}
You will notice too, that I’m using service locator in the constructor of my action which I would also perfer not to do. Any guidance would be appreciated. I’m at a loss.

I would always try to seperate the container configuration from the request / response context. In other words: Configure the dependencies in your container and use the request / response objects only within the Action class or the middleware.

First create a “MemcachedThrottleKeyMiddleware” class that puts the throttle key. Then fetch the key from memecache in your action (or better within a repository).

Example:

// Build PHP-DI Container instance
$container = $containerBuilder->build();

// Use the classname as container key
$container->set(Memcached::class, function () {
  $mc = new Memcached(); 
  $mc->addServer('127.0.0.1', 11211);
  
  return $mc;
});

AppFactory::setContainer($container);

// Instantiate the app
// ...

// Add the middleware
// ...
$app->add(\App\Middleware\MemcachedThrottleKeyMiddleware::class); // <-- here
$app->add(ErrorMiddleware::class);

// Add routes
// ...

$app->run();

The middleware:

<?php

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Memcached;

final class MemcachedThrottleKeyMiddleware implements MiddlewareInterface
{
    /**
     * @var Memcached
     */
    private $memcached;

    public function __construct(Memcached $memcached)
    {
        $this->memcached = $memcached;
    }

    public function process(
        ServerRequestInterface $request, 
        RequestHandlerInterface $handler
    ): ResponseInterface
    {
            $memcachedThrottleKey = 'my-domain/throttle-key-' . $request->getServerParams()['REMOTE_ADDR'];
            $this->memcached->set($memcachedThrottleKey, date("Y-m-d h:i:s")); 
            $request = $request->withAttribute('memcachedThrottleKey', $memcachedThrottleKey);
            return $handler->handle($request);
        }
    }
}

Action usage

/**
 * @param Memcached $memcached
 * @param LoggerInterface $logger
 */
public function __construct(Memcached $memcached, LoggerInterface $logger)
{
    parent::__construct($logger);
    $this->memcached = $memcached;
}

public function __invoke($request, $response): Response
{       
      $memcachedThrottleKey = $request->getAttribute('memcachedThrottleKey');
      $lastThrottleTime = $this->memcached->get($memcachedThrottleKey);
      $this->logger->info("Last throttle time: " . $lastThrottleTime);
      // ...

     return $response;
}

Hi odan, thank you for your reply. I’ve spent some time implementing your solution, which creates a better separation of concerns. However, unfortunately I’m still not seeing the cached value in the logs. Full disclosure; triple-checks lead me to believe I have configured this properly. I’m not ruling out shared hosting issues with Memcached.

[2020-10-05 20:45:42] slim-app.INFO: Last throttle time:  [] {"uid":"c7e34b1"}

The Middleware:

<?php

namespace App\Application\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Memcached;

final class MemcachedThrottleKeyMiddleware implements MiddlewareInterface
{
  /**
   * @var Memcached
   */
  private $memcached;

  public function __construct(Memcached $memcached)
  {
      $this->memcached = $memcached;
  }

  public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface  
  {
      $memcachedThrottleKey = 'my-domain/throttle-key-' . $request->getServerParams()['REMOTE_ADDR'];
      $this->memcached->set($memcachedThrottleKey, date("Y-m-d h:i:s")); 
      $request = $request->withAttribute('memcachedThrottleKey', $memcachedThrottleKey);
      return $handler->handle($request);
  }
}

index.php:

    $container->set(Memcached::class, function () {
      $mc = new Memcached(); 
      $mc->addServer('127.0.0.1', 11211);
      
      return $mc;
    });

middleware.php:

<?php
declare(strict_types=1);

use App\Application\Middleware\SessionMiddleware;
use App\Application\Middleware\MemcachedThrottleKeyMiddleware;
use Slim\App;

return function (App $app) {
    $app->add(SessionMiddleware::class);
    $app->add(MemcachedThrottleKeyMiddleware::class);
    $app->addBodyParsingMiddleware();
};

The action:

class ContactAction extends MailerAction
{
    /**
     * @var Memcached
     */
    protected $memcached;
    
    /**
     * @param Memcached $memcached
     * @param LoggerInterface $logger
     */
    public function __construct(Memcached $memcached, LoggerInterface $logger)
    {
        parent::__construct($logger);
        $this->memcached = $memcached;
    }
    
    protected function action(): Response
    {    
      $memcachedThrottleKey = $this->request->getAttribute('memcachedThrottleKey');
      $lastThrottleTime = $this->memcached->get($memcachedThrottleKey);
    
      $this->logger->info("Last throttle time: " . $lastThrottleTime);
      // ...
    }

ContactAction extends MailerAction which extends Action. Again; I believe this is setup and configured properly and the solution provided by you (odan) is the best. Memcached is simply prohibited by GoDaddy’s economy tier.