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.