Symfony kernel.terminate event run command or service asynchronously - symfony

I want to execute my service on kernel.terminate event to make it asynchronously .
What i am doing in my controller before returning response is this
$this->eventDispatcher->addListener(KernelEvents::TERMINATE, function (PostResponseEvent $event) use ($vat) {
// Launch the job
$command = new WebDriverCommand();
$command->setContainer($this->container);
$input = new ArrayInput(array('id' => $vat->getId()));
$output = new NullOutput();
$command->run($input, $output);
});
My question is : Is there a difference between running the command that executes my service AND calling the service directly without command ?
Thanks in advance.

Absolutely no difference. Your command must not have any logic anyway. Both event listeners and your commands should invoke a service anyway - be kind of a glue between the framework and your domain logic.
Your classes will in effect be easier to unit test and maintain.

Related

Reset Database before each Test has problems with authentication in functional tests

I am implementing functional tests for my REST-Api. The Api is protected by authorization. For this I chose the json_login provider. So far, so good. Authentication works when accessing in the normal environment via Insomnia.
Now I want functional tests. For that, I create an user via the configured User-class and persist it in the database. Works as expected.
But of course the test only works once as the user already exists in the following tests.
So I tried hautelook/alice-bundle with ResetDatabaseTrait or ReloadDatabaseTrait as well as dmaicher/doctrine-test-bundle.
Both show the same behaviour: The authenticator can not find the newly created user. (EntityUserProvider::loadUserByUsername finds no user)
Apparently the EntityUserProvider seems to use a different "connection" into the database that can not look into the transaction those libraries started.
The entity-manager in my test that is responsible for persisting my user is created either with
protected function setUp(): void {
$kernel = self::bootKernel();
$this->em = $kernel->getContainer()
->get('doctrine')
->getManager();
}
or directly before creating the user with
$em = self::$container->get('doctrine')->getManager();
which seems correct for me. But in any case I get the same result -> "Invalid credentials" because the user can not be found.
Maybe someone out there can point me into the right direction?
After a refreshing break I remembered a detail when I was creating my tests. All the examples did not need a setUp-Method with self:bootKernel() in it. But without it the self::$container property was empty, so I added that to my test-class. Maybe there was the solution to the problem?
I was right!
I am using the Api-Platform package. Therefore my test-class is based in ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase. That class does not have a setUp Method, but inspecting createClient() I noticed that there the kernel is created by calling bootKernel() which also stops any running kernel.
So my setUp() method created a kernel. With that kernel I created my user.
Then I called createClient() to create the test-client for the requests. This killed my initial kernel and creates a new one which then leads to the problems.
Rearranging the statements - first create the client, then get the EntityManager from the now created container and create the User after creating the client solved the problem.
After two days , hooh
when you want to call multiple request, for example if you want at first request you get token and the second you call with this token and check auth, in during this calls if you use use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait trait your data base rest after first call, you have token but database is empty, and second call fail.
So, read again this important part of documentation :
There is one caveat though: in some tests, it is necessary to perform multiple requests in one test, for example when creating a user via the API and checking that a subsequent login using the same password works. However, the client will by default reboot the kernel, which will reset the database. You can prevent this by adding $client->disableReboot(); to such tests. Writing Functional Tests
I, know we are lazy developer and first read code, not document :-)
$client = static::createClient();
$client->disableReboot();
$manager = self::getContainer()->get('doctrine')->getManager();
$user = new User();
$user->setEmail('user#example.com');
$user->setPassword(
self::getContainer()->get('security.user_password_hasher')->hashPassword($user, $password = 'pass1234')
);
$manager->persist($user);
$manager->flush();
$response = $client->request('POST', '/authentication-token', [
'headers' => ['Content-Type' => 'application/json'],
'json' => [
'email' => $user->getEmail(),
'password' => $password ,
],
]);
$token = $response->toArray()['token'] ?? null;
$client->request('GET', '/profile', [
'auth_bearer' => $token
]);
self::assertResponseIsSuccessful();

Executing Symfony 4 Custom Command in fixtures

I have a custom command in my symfony project to populate the database with the default data that the application need to work in both dev and prod environments.
For the dev environment I have a fixture script that depends on these default common data.
I'm trying to call my custom Symfony command in the fixture script so that I'm sure to have the required data to properly load my fixtures.
This is my custom command app:db:populate in "pseudo script", just creating a bunch of entities, persit & flush. My custom command works fine when I call it through php bin/console app:db:populate
protected function execute(InputInterface $input, OutputInterface $output)
{
// Creating a bunch of default entities, persist them and flush
$data = new MyDefaultEntity();
// ...
$this->manager->persist($data);
// ...
$this->manager->flush();
}
Then, in my fixture script, I want to call app:db:populate first, because fixtures depends on these data. So I tried to use the Process class to execute my script this way :
public function load(ObjectManager $manager)
{
// Execute the custom command
$cmd = 'php bin/console app:db:populate';
$process = new Process($cmd);
$process->run(function ($type, $buffer) {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
// Then load the fixtures !
// ...
}
The custom command seems to execute well until the $this->manager->flush();
I have the following error in my console (Data is obfuscated for the post):
In AbstractMySQLDriver.php line 36:
An exception occurred while executing 'INSERT INTO ....(..., ..., ...) VALUES (?, ?, ?)' with params ["...", "...", "..."]:
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction
I don't know what to do regarding this error ... Why the command is working normally when used through a classic console call and why it is not working in a Process?
So, the short answer is
Quoting Symfony documentation :
You may have the need to execute some function that is only available in a console command. Usually, you should refactor the command and move some logic into a service that can be reused in the controller.
I ended up making a service class that handles all the app:db:populate logic (read a json file and insert basic app entities in the database). Then I call this service in both app:db:populate execute methods and AppFixtures load methods.
Hope this will help someone.

Is there a way to prevent multiple executions of controller method in Symfony 4?

I have a service which I use both from a custom command and an HTML page. I want to prevent multiple executions of the the service in parallel. For the command there is the Lock component that does that. But is it possible to achieve the same thing for a controller method ?
The lock component doesn't work if the service is called from a controller:
$store = new FlockStore(sys_get_temp_dir());
$factory = new Factory($store);
$lock = $factory->createLock('MY_SERVICE');
I wanted to avoid calling the command from the controller (that's why I created a service) mainly because the service doesn't have the same output for the HTML page and the CLI.
Inject the lock Factory into your service directly instead of creating the lock in the command AND in the controller.
First you have to install Lock Component:
composer require symfony/lock
Then, for example, you can declare your service like this:
use Symfony\Component\Lock\Factory as LockFactory;
class MyService {
private $lock;
public function __construct(LockFactory $lockFactory) {
$this->lock = $lockFactory->createLock('LOCK_KEY');
}
public function doWork() {
$this->lock->acquire();
try {
// DO THINGS
} finally {
$this->lock->release();
}
}
}
I said:
The lock component doesn't work if the service is called from a controller:
Actually the issue I had was the Symfony built-in dev server which is single-threaded, so requests can't be executed in parallel, while the CLI PHP is multi-threaded. I couldn't run the script in parallel through the dev server, request were queued, service script was never locked.
The lock component is working the same whether it's called from a command or a controller.
Using the lock like this in the service works fine:
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Store\FlockStore;
$store = new FlockStore(sys_get_temp_dir());
$factory = new Factory($store);
$lock = $factory->createLock('LOCK_KEY');
if ($lock->acquire()) {
//some locked code
$lock->release();
}

Call a command within a listener

I have a pretty complex command (not written by me) in my symfony API to send notifications, and I would like to have it run everytime a PostPersist event happens.
For this, I've set up a listener triggered by the PostPersist event and this part works perfectly. However, I can't manage to launch the command. I first tried to launch it like I did in a controller with the following piece of code:
$kernel = $this->get('kernel');
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput(array(
'command' => 'acme:send-notifications',
));
// You can use NullOutput() if you don't need the output
$output = new NullOutput();
$application->run($input, $output);
return new Response("");
but of course, it doesn't work in a Listener, since I can't get the kernel
So I tried to add the command to the services:
command.send-notifications:
class: WD\ApiBundle\Command\SendNotificationsCommand
tags:
- { name: 'console.command', command: 'acme:send-notifications' }
and then call it this way:
$output = new NullOutput();
$this->sendNotifCommand->execute(null, $output);
but then I get the following error message:
The container cannot be retrieved as the application instance is not yet set.
I have to admit I don't quite understand what it means. Also, I must confess this part of symfony (listeners, commands) are quite new to me and I don't even know if I'm doing it the right way, or if there is a better system to send notifications everytime a persist happens in a specific entity...
Never tried it in a listener but you can try to launch your command with a Process :
$process = new Process("cd " . $this->projectDir . "; php bin/console YOUR_COMMAND_NAME");
$process->start();

Symfony 2 Service Error on unit Testing

I make some functional test with Symfony 2 and phpunit.
But i've some trouble with a Service.
Let me explain.
During my run test, i want to use some service used by the application. So i juste set my setUp function for setting the kernel :
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->objectFactory = static::$kernel->getContainer()->get('some.application.objectfactory');
So i've this and in my function i need to used a service that return an object so i call my service like that
$var = $this->objectFactory->getObject($id);
and obviously in my tearDown function i just :
protected function tearDown()
{
$this->client->restart();
unset($this->client, $this->objectFactory);
}
So my problem is when i run a test i've this message :
Symfony\Component\DependencyInjection\Exception\InactiveScopeException: You cannot create a service ("request") of an inactive scope ("request").
And i can't find a way to solve this.
Did someone have any idea ??
My version of Symfony is 2.2.1 and my version of phpunit is 3.7.19
If someone can help me, i could be very happy.
I'm sorry if my English isn't so good.
EDIT
Maybe it could help someone, in the service i used that : :
$request = $this->container->get('request');
It seems to be the reason why it dosen't work, when i remove it, it doesn't say the error, but they still doesn't work.
EDIT
#Cyprian
According to you have change my code for what i want.
So i just add to my service, in the function that i want, the client (Client web test case), and then inside the function i just add this :
if (isset($client)) {
$request = $client->getRequest();
} else {
$request = $this->container->get('request');
}
So in my function where i call the service i've just this :
public function getObject($id)
{
//Get the service from the kernel
$service = static::$kernel->getContainer()->get('service');
$object = $service->getObject($id, $this->client);
}
and it works fine like this
#nifr
Your idea doesn't work for me, but i think your idea wasn't wrong, they just not works in my case
However Thanks for your help, i'm happy i works now, and i expect that post could help someone else
Try get request from the client, not service container:
$request = $this->client->getRequest();
In that way you can also get kernel and/or container:
$kernel = $this->client->getKernel();
$container = $this->client->getContainer();
One more useful tip: kernel from the client is rebooted between each two requests. So, for example if you pass your mock to client's container and do some request, in next request (after the first one) the container will not contain your mock.
There is no request available in phpUnit as long as you don't construct one.
If you want to test a request. create it like this:
use Symfony\Component\HttpFoundation\Request;
protected $request;
public function setUp()
{
// ...
$this->request = new Request();
// ... modify your request acccording to your needs
}
and add/call a setter in your Service using the request.
$service = $this->kernel->getContainer()->get('your_service')
$service->setRequest($this->request);
or create a Functional Test with WebtestCase.

Resources