Override TranslationsCacheWarmer service - symfony

I have an application that contains a lot of translation resources for a lot of different languages. The warmup process takes a long time because of this.
I only support the translation of my site in a few languages, so I'd like to avoid generating catalogues for all the languages that I don't support.
What I did:
I overrode the TranslationsCacheWarmer to use my own translator. This is a custom translator that decorates the default translator but overrides the warmup method to only warmup files that are part of the locales that I support.
The problem is that the default warmer still runs generating files for all the locales.
This is the code that contains the custom translator: https://gist.github.com/marcosdsanchez/e8e2cd19031a2fbcd894
and here's how I'm defining the services:
<service id="web.translation.public_languages_translator" class="X\Translation\PublicLanguagesTranslator" public="false">
<argument type="service" id="translator.default" />
<argument type="collection">%chess.translation.public_languages%</argument>
</service>
<service id="translation.warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer" public="false">
<argument type="service" id="web.translation.public_languages_translator" />
<tag name="kernel.cache_warmer" />
</service>
I'm using symfony 2.7.3

I ended up doing something different to get the same result. Instead of trying to create a custom CacheWarmer, I created a compiler pass and modified the definition of the 'options' argument. In this compiler pass, I removed all the files that don't have the locale or language code.
Code:
<?php
namespace X\DependencyInjection\Compiler;
use X\Entity\I18nLanguage;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class TranslatorCompilerPass implements CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
*
* #param ContainerBuilder $container
*
* #api
*/
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('translator.default');
$options = $definition->getArgument(3);
$keys = array_keys($options['resource_files']);
$locales = I18nLanguage::PUBLIC_LOCALES;
$langCodes = array();
foreach (I18nLanguage::PUBLIC_LOCALES as $locale) {
$langCodes[] = substr($locale, 0, strpos($locale, '_'));
}
$localesAndLangCodes = array_merge($locales, $langCodes);
foreach ($keys as $key) {
if (!in_array($key, $localesAndLangCodes, true)) {
unset($options['resource_files'][$key]);
}
}
$arguments = $definition->getArguments();
$definition->setArguments(array($arguments[0], $arguments[1], $arguments[2], $options));
}
}
That did the trick for me and I can also apply other optimizations like the removal of loaders, etc.

Related

How to add a new command line option to symfony console

I am not an expert in Symfony, I need to add a new console option --country=XX to the symfony console.
This isnt a command, its an option which changes how whatever command is run, executes by selecting a different database to operate on by building the doctrine.dbal.dbname parameter such as api_fr, api_de, api_es, etc.
I have tried to search for a way to do this, but unfortunately everything comes back to adding commands, which is not what I want to do, I want to add an option.
I am building an API which part of it works with Symfony 2.8 and another part is using Symfony 3.x. I suppose the answer might be the same in both versions, but if you know how to do this in both versions and they are separate, please let me know.
You can add an EventListener like this exemple:
use Symfony\Component\Console\Input\InputOption;
class YourOptionEventListener
{
public function onConsoleCommand(ConsoleCommandEvent $event)
{
$inputDefinition = $event->getCommand()->getApplication()->getDefinition();
// add the option to the application's input definition
$inputDefinition->addOption(
new InputOption('yourOption', null, InputOption::VALUE_OPTIONAL, 'Description of the option', null)
);
}
}
Then add it as a service:
<?xml version="1.0" ?>
<container ...>
<services>
<service id="app_yourOption.console_event_listener"
class="App\YourOptionBundle\EventListener\YourOptionEventListener">
<tag name="kernel.event_listener" event="console.command" method="onConsoleCommand" />
</service>
</services>
</container>
You can check this documentation, in "Add a global command option" chapter, you can find what you need:
http://php-and-symfony.matthiasnoback.nl/2013/11/symfony2-add-a-global-option-to-console-commands-and-generate-pid-file/
The best practice of 2018 and Symfony 3+ is to extend Symfony Application:
<?php
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputOption;
final class SomeApplication extends Application
{
protected function getDefaultInputDefinition()
{
$definition = parent::getDefaultInputDefinition();
$definition->addOption(new InputOption(
'country',
null,
InputOption::VALUE_REQUIRED,
'Country to use'
));
return $definition;
}
}
Then anywhere in your command or service with Symfony\Component\Console\Input\InputInterace service present, just call:
$country = $input->getOption('country');
Do you want to know more?
I've extended the answer in the 4 Ways to Add Global Option or Argument to Symfony Console Application post.

Symfony2: How do I clear all of Symfony's (and all 3rd party plugins) cache in 1 command?

so I've been testing Doctrine queries and other Symfony code and I have to run several commands just to clear Doctrine/Symfony caches. I was searching for the net and came across another command to clear Assetic assets/etc.
From what I've read
php app/console cache:clear
will only clear Symfony cache. it won't include Doctrine and perhaps more.
I know I can create a bash script to clear all my caches but this obviously means I know all the "clear cache" commands. I only found out about the Assetic clear cache/assets by accident. What about those I don't know?
So is there 1 "clear cache" command that can do it for me? This will have to include Symfony/Doctrine/Assetic/Twig and whatever plugins I have installed.
Thanks a lot
What you are looking for is highly dependent on the developer of the bundle that uses the cache. Not even doctrine, that comes with the standard version of symfony has its cache clear command integrated. But what you can do is extend the default symfony command with a listener that runs all the cache clear command you want like this:
<?php
namespace DefaultBundle\Event\Listener;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Process\Process;
class CacheClearListener implements CacheClearerInterface
{
private $environment;
/**
* #return array
*/
private static function getCommands()
{
return array(
'php ./app/console doctrine:cache:clear-metadata --no-debug --flush',
'php ./app/console doctrine:cache:clear-query --no-debug --flush',
'php ./app/console doctrine:cache:clear-result --no-debug --flush'
);
}
public function clear($cacheDir)
{
$output = new ConsoleOutput();
$output->writeln('');
$output->writeln('<info>Clearing Doctrine cache</info>');
foreach (self::getCommands() as $command) {
$command .= ' --env='.$this->environment;
$success = $this->executeCommand($command, $output);
if (!$success) {
$output->writeln(sprintf('<info>An error occurs when running: %s!</info>', $command));
exit(1);
}
}
}
/**
* #param string $command
* #param ConsoleOutput $output
*
* #return bool
*/
private function executeCommand($command, ConsoleOutput $output)
{
$p = new Process($command);
$p->setTimeout(null);
$p->run(
function ($type, $data) use ($output) {
$output->write($data, false, OutputInterface::OUTPUT_RAW);
}
);
if (!$p->isSuccessful()) {
return false;
}
$output->writeln('');
return true;
}
/**
* #param Kernel $kernel
*/
public function setKernel(Kernel $kernel)
{
$this->environment = $kernel->getEnvironment();
}
}
Register the listener like this:
<service id="cache_clear_listener" class="DefaultBundle\Event\Listener\CacheClearListener">
<call method="setKernel">
<argument type="service" id="kernel"/>
</call>
<tag name="kernel.cache_clearer" priority="254" />
</service>
And that is all. Now all you need to do is keep adding your new cache clear command to the getCommands() method. In order to find this commands you can run something like
php app/console | grep cache
to see all available commands that contain the word "cache" in them
After your listener is set, every time you run php app/console cache:clear it will trigger all the command that you listed in the getCommands() method of your listener.
Hope this helps,
Alexandru

Phpunit integration testing symfony app - how to inject services into test?

I want to test a class by checking the real database data after function is executed.
I do not understand, how can I inject services which I need, for example some repository class.
So far I have written this:
namespace Tests\integration\Service\JourneyRunner\EmailConditionCheck;
use PHPUnit_Framework_TestCase;
use NG\Model\Journey\EmailConditionCheckRepositoryInterface;
class EmailConditionCheckServiceTest extends PHPUnit_Framework_TestCase
{
private $emailConditionCheckQueueRepository;
public function __construct(
EmailConditionCheckQueueRepositoryInterface $emailConditionCheckQueueRepository
) {
$this->emailConditionCheckQueueRepository;
parent::__construct();
}
public function testPrepareEmailContentForSending()
{
echo 'aaa';
$this->assertEquals(1, 1);
}
}
I added the test service to services.xml
<parameter key="tests.integration.service.journey_runner.email_condition_check.email_condition_check_service_test.class">tests\integration\Service\JourneyRunner\EmailConditionCheck\EmailConditionCheckServiceTest</parameter>
<service id="tests.integration.service.journey_runner.email_condition_check.email_condition_check_service_test" class="%tests.integration.service.journey_runner.email_condition_check.email_condition_check_service_test%">
<argument type="service" id="ng.infrastructure.persistence.time_trigger_queue_repository" />
</service>
I know that argument is wrong currently, but from the error I see that it does not get even the wrong argument - it gets nothing.
PHP Fatal error: Uncaught TypeError: Argument 1 passed to Tests\integration\Service\JourneyRunner\EmailConditionCheck\EmailConditionCheckServiceTest::__construct() must be an instance of Tests\integration\Service\JourneyRunner\EmailConditionCheck\EmailConditionCheckQueueRepositoryInterface, none given, called in /var/www/api.notification.guru/ng-api-service/vendor/phpunit/phpunit/src/Framework/TestSuite.php on line 475 and defined in /var/www/api.notification.guru/ng-api-service/tests/integration/Service/JourneyRunner/EmailConditionCheck/EmailConditionCheckServiceTest.php:12
I tried to search for info, but I cannot find.
Found out how, not inject, but geting a container gives result I want:
<?php
namespace Tests\integration\Service\JourneyRunner\EmailConditionCheck;
use PHPUnit_Framework_TestCase;
use NG\Model\Journey\EmailConditionCheckRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use NG\Model\Uuid;
class EmailConditionCheckServiceTest extends KernelTestCase
{
private $emailConditionCheckQueueRepository;
protected function setUp()
{
self::bootKernel();
$this->container = static::$kernel->getContainer();
$this->emailConditionCheckQueueRepository = $this->container->get('ng.infrastructure.persistence.email_condition_check_repository');
}
public function testPrepareEmailContentForSending()
{
$this->emailConditionCheckQueueRepository->get(Uuid::fromString('1'));
$this->assertEquals(1, 1);
}
}
Also needed to create phpunit.xml - set the app directory. App directory is the one where AppKernel.php lies in your project as I understood.
<?xml version="1.0" ?>
<phpunit>
<php>
<server name="KERNEL_DIR" value="app/" />
</php>
</phpunit>
And pass to phpunit command a parameter
--configuration=phpunit.xml

Why I always get the message "You cannot dump a container with parameters" in symfony console?

In Terminal when I try to run my created command I get the following error:
[Symfony\Component\DependencyInjection\Exception\InvalidArgumentException]]
You cannot dump a container with parameters that contain references to other services (reference to service "old_sound_rabbit_mq.split_file_producer" found in "/rabbit").
This is what happens when I run my newly created console command:
$this->getContainer()->get('split_file')->process();
I don't know why it says that You cannot dump! I don't dump anything in the project.
Is there something I'm unaware of?
EDIT
A part of my services.yml:
<parameters>
<parameter key="file_path">/var/www/path/file.xml</parameter>
<parameter key="rabbit" type="service" id="old_sound_rabbit_mq.split_file_producer" />
</parameters>
<service id="split_file" class="Acme\DemoBundle\SplitFile">
<argument>%file_path%</argument>
<argument>%rabbit%</argument>
</service>
And this is my console command class:
namespace Acme\DemoBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ReadFileCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('reader:read-file');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->getContainer()->get('split_file')->process();
}
}
Parameters only have a key value. You can't add service etc to a parameter.
<service id="split_file" class="Acme\DemoBundle\SplitFile">
<argument>%file_path%</argument>
<argument type="service" id="old_sound_rabbit_mq.split_file_producer"/>
</service>
http://symfony.com/doc/current/book/service_container.html
I'd suggest switching to yml as it's less verbose
You could create an alias for old rabbit if you wanted.

Questions about Symfony 2 service container parameters

1. Passing parameters into the service?
If not already defined in the services.xml (or yaml), is the only way to pass parameter(s) into service is:
$container->setParameter('loader', $loader);
$container->get('myservice');
I suppose this way loader will be available to ALL services, not just "myservice"?
2. Passing an array of objects into the service?
The Template/DelegatingEngine class takes an array of engine object into the constructor, and I dont know how should I define that in the xml file:
public function __construct(array $engines = array())
{
$this->engines = array();
foreach ($engines as $engine) {
$this->addEngine($engine);
}
}
What should I put into the
<service id="myCustomeFramework.TemplateEngine" class="path\to\DelegateEngine" scope="prototype">
<argument>how can i pass an array of engines here?</argument>
</service>
Answer 1
Yes, it will be available for all services that uses that parameter and that are called after setting the parameter.
Answer 2
For passing an array as an argument to a service using xml you have to do it in this way:
<service id="myCustomeFramework.TemplateEngine" class="path\to\DelegateEngine" scope="prototype">
<argument type="collection">
<argument key="key">value</argument>
<argument key="key">value</argument>
<argument key="key">value</argument>
</argument>
</service>

Resources