How to invalidate container cache in Symfony2? - php

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());

Related

Create a Cache Provider class in Symfony

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

Symfony : clear cache and warmup when users are using the website

With Symfony, when config or twig files are modified, the cache must be cleared and a warmup must be performed to take into account the new values.
My problem is when users are working on the website and I would like to update a file which needs a Symfony warmup command : the command fails if a user is consulting the cache at the same times by browsing the website. Then the cache is corrupted and I need to run again the clear cache and the warmup command when users are angry because the website is not working and hit the F5 button again and again making this process endless...
To avoid this, I am always planning a maintenance and block website accessibility during the cache warmup.
But, it is a complex task to simply fix a typo, isn't it?
Is there a way to clear and warmup single file? Or any idea to handle this process correctly?
Works for me, might work for you.
I usually have two versions of my app placed side by side. Only one is connected to web server. If I have to make any changes I update inactive version, clear cache, warmup cache, etc. Then I switch active version in web server.
That way you have as much time as you want for maintenance and switch is unnoticeable for your users.
You can also configure web server to allow inactive version to be available in some internal channel. That way, after you done what you wanted to change you can peek if everything works as expected(or let testers do their work) before you go public.
At first you have to register a EventListener:
# /config/services.yaml
parameters:
lockFilePath: "%kernel.root_dir%/../web/.lock"
services:
maintenance_listener:
class: App\EventListener\MaintenanceListener
arguments:
- "%lockFilePath%"
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Then the EventListener itself:
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class MaintenanceListener
{
private $lockFilePath;
public function __construct($lockFilePath)
{
$this->lockFilePath = $lockFilePath;
}
public function onKernelRequest(GetResponseEvent $event)
{
if ( ! file_exists($this->lockFilePath)) {
return;
}
$event->setResponse(
new Response(
'site is in maintenance mode',
Response::HTTP_SERVICE_UNAVAILABLE
)
);
$event->stopPropagation();
}
}
So if you now create a .lock file in /web then your site is in maintenance mode.
If you want to display a special template, you can inject #templating into the EventListener.

How to regenerate routes cache when the configuration changes in Symfony 2

I'm currently writing a custom route loader in Symfony 2 that will generate routes based on some configuration options defined in the main config file. The problem is that Symfony caches routes generated by custom routes loaders. Is there a way for me to update the cache when that config file changes?
I defined a configuration like this in app/config/config.yml
admin:
entities:
- BlogBundle\Entity\Post
- BlogBundle\Entity\Comment
My route loader read the config file and generates some routes based on the entities. Now the problem is that once those routes are generated and cached by Symfony I can't change them unless I manually call php app/console cache:clear. What I mean is if I add an entity to the config:
admin:
entities:
- BlogBundle\Entity\Post
- BlogBundle\Entity\Comment
- TrainingBundle\Entity\Training
I will have to manually clear the cache again with php app/console cache:clear in order to create and cache the new routes. I want the routes cache to be invalidated if I change the config, so that a new request to the server will force the regeneration of the routes.
Option 1
If your custom loader class can gain access to the kernel or the container (via DI), you could call the console cache clear command from that class.
E.g.
namespace AppBundle\MyLoader;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\HttpFoundation\Response;
class MyLoader
{
private $kernel;
public function __construct($kernel)
{
$this->kernel = $kernel;
}
public function myFunction()
{
$application = new Application($this->kernel);
$application->setAutoExit(false);
$input = new ArrayInput(array(
'command' => 'cache:clear',
'--env' => 'prod',
));
// You can use NullOutput() if you don't need the output
$output = new BufferedOutput();
$application->run($input, $output);
// return the output, don't use if you used NullOutput()
$content = $output->fetch();
// return new Response(""), if you used NullOutput()
return new Response($content);
}
}
Ref: Call a Command from a Controller
Disclaimer before someone points it out; Injection the kernel/conatiner is not considered "best pratice", but can be a solution.
Option 2
You could also write you own console command that extends Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand that just calls the clear cache command.
Ref ; Call Command from Another
Option 3
This answer also gives you another option

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;
[...]
}
[...]
}

Categories