Skip to main content

Integrating Memcached with Symfony2

Overview

This is a short tutorial to add Memcached support to your Symfony 2 application. This is actually not a really good way to add caching support to your application as your API should support multiple caching mechanisms just like Zend_Cache does; however, all I need for my application is a way to access a Memcached singleton instance and this setup satisfies that requirement.

Configuration

Generate a new bundle in your application source directory using the command below.

php app/console generate:bundle

In your app/config.yml, create a section for your bundle:

app/config/config.yml

acme_memcached:
    servers:
        server1:
            host: localhost
            port: 11211
            weight: 25
        server2:
            host: localhost
            port: 11211
            weight: 75

Note: You should also add a persistence flag to this configuration; however, I am going to omit this flag to keep this tutorial simple.

Once we have a format that we like, we need to update the Configuration class in our Memcached bundle to validate our configuration format. Here is what it should look like:

src/Acme/MemcachedBundle/DependencyInjection/Configuration.php

namespace Acme\MemcachedBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('acme_memcached');

        $rootNode->children()
            ->arrayNode('servers')
                ->isRequired()
                ->requiresAtLeastOneElement()
//                ->useAttributeAsKey('host', false)
                ->prototype('array')
                    ->children()
                        ->scalarNode('host')->isRequired()->cannotBeEmpty()->end()
                        ->scalarNode('port')->defaultValue(11211)->cannotBeEmpty()->end()
                        ->scalarNode('weight')->defaultValue(0)->end()
                    ->end()
                ->end()
            ->end()
        ->end();

        return $treeBuilder;
    }
}

The important thing to notice here is the prototype() call which defines a prototype for multiple children nodes (server) under a parent node (servers).

Next step is to update the AcmeMemcachedExtension class and assign a parameter name to our servers array as illustrated below.

src/Acme/MemcachedBundle/DependencyInjection/AcmeMemcachedExtension.php

namespace Acme\MemcachedBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

class AcmeMemcachedExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);
        
        $container->setParameter('acme_memcached.servers', $config['servers']);
        unset($config['servers']);
        
        $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.xml');
    }
}

The setParameter() call above is an important step for our implementation, because it will allow us to inject an array containing multiple server definitions into our Memcached service as a single argument.

Now, let's define a class that extends the base PECL Memcached class. This is going to be the class that will be instantiated by the Symfony service container.

src/Acme/MemcachedBundle/Core/Memcached.php

namespace Acme\MemcachedBundle\Core;

class Memcached extends \Memcached
{
    public function __construct($servers) 
    {
        parent::__construct();
                
        foreach ($servers as $server) {
            
            if (!isset($server['host'])) {
                throw new \LogicException("Memcached host must be defined for server $server");
            }
            
            if (!isset($server['port'])) {
                throw new \LogicException("Memcached port must be defined for server $server");
            }
            
            if (!isset($server['weight'])) {
                $server['weight'] = 0;
            }
            
            $this->addServer($server['host'], $server['port'], $server['weight']);
            
        }
    }
}

The next step is to update our services.xml and tie configuration with implementation.

src/Acme/MemcachedBundle/Resources/config/services.xml

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <parameters>
        <parameter key="acme_memcached.class">Acme\MemcachedBundle\Core\Memcached</parameter>
    </parameters>

    <services>
        <service id="acme_memcached" class="%acme_memcached.class%">
            <argument>%acme_memcached.servers%</argument>
        </service>
    </services>

</container>

That's it. Now we can access our Memcached singleton with the usual mechanism in our actions:

$memcached = $this->get('acme_memcached');

Comments

  1. Hey,

    This is a good example of getting straight forward access to a class..

    Another way I found to do this is in a services.yml file:

    parameters:
    memcached.servers:
    - { host: 127.0.0.1, port: 11211 }
    services:
    memcached:
    class: Memcached
    calls:
    - [ addServers, [ %memcached.servers% ]]

    Obviously that is much shorter and gives you similar access to the memcached instance.

    I comparing the two solutions, but I don't see either have any dis/advantages. You're solution of having a wrapper obviously gives you some customization flexibility though..

    ReplyDelete
    Replies
    1. Alex, I was actually not aware of this solution. It is indeed much shorter.

      Delete
  2. Check out http://www.leaseweblabs.com/2013/03/memcache-support-in-symfony2-wdt/ and https://github.com/LeaseWeb/LswMemcacheBundle

    It adds debugging support and options parsing.

    ReplyDelete

Post a Comment

Popular posts from this blog

Securing Symfony2 REST services with FOSOAuthServerBundle

Overview In my previous article, I wrote about setting up a Symfony2 REST service using FOSRestBundle. However, this REST service was behind a firewall protected by a generic form_login provider. Not really ideal if you wish to open your REST API to other applications. So in this article, I will try to explain how to set up FOSOAuthServerBundle to protect your REST API methods using OAuth2. Before we start getting into the gritty details, it is a good idea to have a look at the official OAuth2 documentation . Let's begin... FOSOAuthServerBundle Installation You have to install v1.1.0 of FOSOAuthServerBundle if you are using Symfony 2.0.x. If not, see the docs . First, add the following entries to your deps file: [FOSOAuthServerBundle] git=git://github.com/FriendsOfSymfony/FOSOAuthServerBundle.git target=bundles/FOS/OAuthServerBundle version=origin/1.1.x [oauth2-php] git=git://github.com/FriendsOfSymfony/oauth2-php.git Run the vendors script to install these...

Adding post-login logic to FOSUserBundle

Having finally figured out how to use FOSUserBundle in my project, I decided to keep track of all logins next. The implementation turned out to be a breeze thanks to Symfony2's security listener mechanism. As usual, the first step is to create a MongoDB document for this purpose. This is a very simple document that contains a user's id, session id, IP address, and login date. src/Acme/UserBundle/Document/LoginHistory.php namespace Acme\UserBundle\Document; class LoginHistory { protected $id; protected $userId; protected $sessionId; protected $ip; protected $createdAt; /** * Get id * * @return custom_id $id */ public function getId() { return $this->id; } /** * Set userId * * @param int $userId */ public function setUserId($userId) { $this->userId = $userId; } /** * Get userId * * @return int $userId */ public function getUserId...

Symfony 2 + DoctrineMongoDBBundle + FOSUserBundle Tutorial

It's been a while I have not added an entry to my blog. I have been busy playing with Symfony 2, MongoDB, and FOSUserBundle for a while now so here is a tutorial to integrate Symfony 2, MongoDB, and FOSUserBundle. Objectives * Install DoctrineMongoDBBundle * Install FOSUserBundle * Create user model ** Utilize groups ** Add additional properties to user model * Create customized registration form and handler At this point I am going to assume that you have a running Symfony 2 and MongoDB installation in place already and a basic understanding of how to configure Symfony 2 services. I will also exclude view related steps from this tutorial. Setting Up DoctrineMongoDBBundle Add the following to your deps file: [doctrine-mongodb] git=http://github.com/doctrine/mongodb.git [doctrine-mongodb-odm] git=http://github.com/doctrine/mongodb-odm.git [DoctrineMongoDBBundle] git=http://github.com/symfony/DoctrineMongoDBBundle.git target=/bundles/Symfony/Bundle/Doctrin...