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...

Setting up a Symfony2 REST service with FOSRestBundle

Installation First thing is to download and setup the FOSRestBundle. If you are running Symfony 2.0.x, get the FOSRestBundle version 0.6, otherwise, download the code in the master branch. The FOSRestBundle depends on the JMSSerializerBundle so the following instructions are going to include some extra information for setting up this bundle to complete our REST service. First thing is to install our dependencies. Add the following to your deps file: [metadata] git=http://github.com/schmittjoh/metadata.git version=1.1.0 [JMSSerializerBundle] git=git://github.com/schmittjoh/JMSSerializerBundle.git target=bundles/JMS/SerializerBundle [FOSRest] git=git://github.com/FriendsOfSymfony/FOSRest.git target=fos/FOS/Rest [FOSRestBundle] git=git://github.com/FriendsOfSymfony/FOSRestBundle.git target=bundles/FOS/RestBundle version=origin/0.6 Run the vendors script to install these bundles: php bin/vendors install Next, update your app/autoload.php file...