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');
Hey,
ReplyDeleteThis 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..
Alex, I was actually not aware of this solution. It is indeed much shorter.
DeleteCheck out http://www.leaseweblabs.com/2013/03/memcache-support-in-symfony2-wdt/ and https://github.com/LeaseWeb/LswMemcacheBundle
ReplyDeleteIt adds debugging support and options parsing.