Create a Cache Provider class in Symfony - php

We have memcache on our Symfony 3.4 app:
cache:
app: cache.adapter.memcached
default_memcached_provider: "%app.memcached.dsn%"
However, we've been asked to use several cache servers, so just passing one DSN is no good.
Looking here (https://symfony.com/blog/new-in-symfony-3-3-memcached-cache-adapter), I see you can create it in code like this:
$client = MemcachedAdapter::createConnection(array(
// format => memcached://[user:pass#][ip|host|socket[:port]][?weight=int]
// 'weight' ranges from 0 to 100 and it's used to prioritize servers
'memcached://my.server.com:11211'
'memcached://rmf:abcdef#localhost'
'memcached://127.0.0.1?weight=50'
'memcached://username:the-password#/var/run/memcached.sock'
'memcached:///var/run/memcached.sock?weight=20'
));
However, that isn't autowired.
I believe we need to either make a provider class, or somehow get it to make calls to addServer($dsn), once instantiated. I also saw the following on random posts:
memcache:
class: Memcached
calls:
- [ addServer, [ %app.memcached.dsn.1% ]]
- [ addServer, [ %app.memcached.dsn.2% ]]
However it isn't really helping or I have missed something out.
Can anyone help? How do I create this provider class?

You can copy above code snippet as a service configuration to your services.yaml, which probably roughly looks like this:
# app/config/services.yaml
services:
app.memcached_client:
class: Memcached
factory: 'Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection'
arguments: [['memcached://my.server.com:11211', 'memcached://rmf:abcdef#localhost']]
app.memcached_adapter:
class: Symfony\Component\Cache\Adapter\MemcachedAdapter
arguments:
- '#app.memcached_client'
Then in your configuration you should be able to reference the adapter using the client created by the factory, e.g. something like:
# app/config/config.yaml
framework:
cache:
app: app.memcached_adapter
You might also be able to overwrite the default alias cache.adapter.memcached instead of having your own adapter.
Your approach using Memcached::addServer might work as well, but just like with MemcachedAdapter::createConnection this will return the Client, which needs to be passed to the cache adapter. That's why there is a second service app.memcached_adapter, which is used in the cache configuration.
Please be aware that I have not tested this, so this is rather a rough outline than a fully working solution,

For one of my projects running Symfony 3.4 the configuration was simpler:
Create a service that will be used as a client:
app.memcached_client:
class: Memcached
factory: ['AppBundle\Services\Memcached', 'createConnection']
arguments: ['memcached://%memcache_ip%:%memcache_port%']
The AppBundle\Services\Memcached can have all the custom logic I need like so:
class Memcached
{
public static function createConnection($dns)
{
$options = [
'persistent_id' => 'some id'
];
// Some more custom logic. Maybe adding some custom options
// For example for AWS Elasticache
if (defined('Memcached::OPT_CLIENT_MODE') && defined('Memcached::DYNAMIC_CLIENT_MODE')) {
$options['CLIENT_MODE'] = \Memcached::DYNAMIC_CLIENT_MODE;
}
return \Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection($dns, $options);
}
}
And then I used that service in my config.yml:
framework:
cache:
default_memcached_provider: app.memcached_client

Related

Function as service in Symfony2

I am storing a string in database that I want to access from various places in my application. I figure out that the best solution will be create a function that is taking that string from database and register it as a service.
Function:
public function shopUrlAction()
{
return new Response($this->getDoctrine()->getRepository('AppBundle:Settings')->find(1)->getName());
}
service.yml
services:
app.default_controller:
class: AppBundle\Controller\DefaultController
output in other controller:
$return['base_url'] = $this->forward('app.default_controller:shopUrlAction');
Unfortunately I am constantly getting
CRITICAL - Uncaught PHP Exception
Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException:
"You have requested a non-existent service "app.default_controller"."
at /app/bootstrap.php.cache line
2099 Context:
{"exception":"Object(Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException)"}
I've cleared cache.
As I see from your question you have service.yml instead of services.yml (in plural form).
You should include your service.yml in main config.yml in imports section or use standard path to it (AppBundle/Resources/config/services.yml)

Getting the file_locator service in symfony2 custom router

I want to create a custom route loader as instructed in http://symfony.com/doc/current/cookbook/routing/custom_route_loader.html.
What I have to do is read the read the routes from an xml file (not in "symfony xml" format) and create the according route collection.However I want to do that using the '#' directive.as in:
xmlRoutes:
resource: '#FooBarBundle/Resources/routes.xml'
But in order to resolve the path to routes.xml I need the 'file_locator' service from the container.is it possible to access services in a custom router class.if not, how can I make a symfony\Component\Config\FileLocator to resolve that path?
Yes you could access the file_locator as it's a service. What you need to do is make your custom_route_loader a service itself (I dind't read the cookbook you linked but I'm pretty sure that they would advice to define it as a service) and inject the file_locator service into it.
So basically you'll do something like
#config.yml
[...]
services:
yourbundlename.custom_route_loader:
class: Path\To\Your\Bundle\CustomRouteLoader
arguments: [ #file_locator ]
And into you CustmRouteLoaderClass
#Path\To\Your\Bundle\CustomRouteLoader
class CustomRouteLoader
{
public function __construct(Symfony\Component\HttpKernel\Config\FileLocator $file_locator) {
$this->file_locator = $file_locator;
[...]
}
[...]
}

How to invalidate container cache in Symfony2?

Part of my Symfony app configuration is being loaded from legacy database, so sometimes I need to invalidate container cache to make use of updated data.
Is there any API to invalidate Symfony container cache programmatically?
As per CacheClearCommand:
$filesystem = $this->container->get('filesystem');
$realCacheDir = $this->container->getParameter('kernel.cache_dir');
$this->container->get('cache_clearer')->clear($realCacheDir);
$filesystem->remove($realCacheDir);
Directly call CacheClearCommand from code:
services.yml
clear_cache_command_service:
class: Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand
calls:
- [setContainer, ["#service_container"] ]
Than it's possible to do something like this (note that this will warmup the cache):
$clearCacheCommand = $this->container->get('clear_cache_command_service');
$clearCacheCommand->run(new ArgvInput(), new ConsoleOutput());

Symfony 2.3: How do you configure SwiftMailer to automatically use a custom plugin?

I have created a custom SwiftMailer plugin which I would like to have SwiftMailer use by default in my Symfony 2.3 application. The only documentation I can find in this regard is here: http://symfony.com/doc/current/reference/dic_tags.html#swiftmailer-plugin
I have set up the service as follows:
acme_test_bundle.swiftmailer.embed_images:
class: Acme\TestBundle\SwiftMailer\Plugins\ImageEmbedPlugin
tags:
- { name: swiftmailer.plugin }
SwiftMailer is not using the plugin even though this service has been created. Have I done something wrong and is there anything else I should be doing?
I managed to figure this out myself.
Contrary to what the Symfony 2.3 documentation says, you need to tag the service with which mailer you will be using (usually the 'default' one).
Therefore, I needed to change swiftmailer.plugin to swiftmailer.default.plugin
So, the service definition is now:
acme_test_bundle.swiftmailer.embed_images:
class: Acme\TestBundle\SwiftMailer\Plugins\ImageEmbedPlugin
tags:
- { name: swiftmailer.default.plugin }
To provide some additional context to the OP's answer.
As of Symfony 2.3 in the SwiftmailerBundle CompilerPass process it performs the following.
$mailers = $container->getParameter('swiftmailer.mailers');
foreach ($mailers as $name => $mailer) {
$plugins = $container->findTaggedServiceIds(sprintf('swiftmailer.%s.plugin', $name));
foreach ($plugins as $id => $args) {
$transport->addMethodCall('registerPlugin', [new Reference($id)]);
}
}
Based on this you would need to add all of the mailer names to your tags that you would like to add the plugin to, in the format of swiftmailer.%mailer_name%.plugin. Replacing %mailer_name% with the name of your mailers.
When not using the multiple mailers configuration for swiftmailer the %mailer_name% is default which is set in the Bundle Configuration.
$v['default_mailer'] = isset($v['default_mailer']) ? (string) $v['default_mailer'] : 'default';
$v['mailers'] = array($v['default_mailer'] => $mailer);
Example config.yml
swiftmailer:
default_mailer: first_mailer #alias: default
mailers:
first_mailer:
#...
second_mailer:
#...
services:
#...
swiftmailer.plugin.array_logger:
class: Swift_Plugins_Loggers_ArrayLogger
swiftmailer.plugin.logger:
class: Swift_Plugins_LoggerPlugin
arguments: ['#swiftmailer.plugin.array_logger']
tags:
- { name: swiftmailer.default.plugin }
- { name: swiftmailer.second_mailer.plugin }

Removing Symfony2 scope widening notice

I'm trying to speed up my stack by removing references to the service container where possible. In this case I only need the request:
email_error_message:
class: Core\MyBundle\Services\Email\ErrorMessage
arguments: [ #request, %params ]
However, that throws a scope widening issue. I'm not concerned with refactoring the code for now, I just wish to get rid of the warning by adding strict = true. But I can't seem to get the YAML syntax right:
email_error_message:
class: Core\MyBundle\Services\Email\ErrorMessage
arguments:
- { type: service, id: request, strict: false }
- %params%
This isn't working though. Any ideas?
EDIT
I realise I could change the scope of this service to request, but that isn't an option in this case.
You want to restrict the scope of the service to the request scope, since you need to make sure you're passed the right Request instance - if you are using the service from within a subrequest for example, or whether through the main request. Adjust your service config to:
services:
email_error_message:
class: Core\MyBundle\Services\Email\ErrorMessage
scope: request
arguments: [ #request, %params% ]
See the docs for more details.
Edit as per your question edit, you're not able to change the scope. In which case, your syntax should be as follows:
services:
email_error_message:
class: Core\MyBundle\Services\Email\ErrorMessage
arguments: [ #request=, %params% ]
with the appended = symbol. Note that I've not seen this referenced anywhere, and it's from digging around in the code for the DI container ;-)

Categories