Call a command within a listener - symfony

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

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

Unittesting a Symfony 4.2 process runs infinite loop (than times out), wihout unittest it works fine

Lets say I have the following Symfony 4 command:
class Command1 extends Command {
protected static $defaultName = 'app:command1';
protected function execute(InputInterface $input, OutputInterface $output){
$process = new Process('bin/console list', getcwd());
$process->start(); // or even $process->run() does not matter if its async or not
// ... where we handle if its finished, etc...
}
}
If I simply call bin/console app:command1 it will return the expected command list. Basically works as I expect.
But if I have a phpunit test which uses the Symfony\Component\Console\Application::run() to start this command, I end up in an "infinite loop" (well, actually not, it times out after 60 sec) in the Symfony\Component\Process::wait() in the
do {
$this->checkTimeout();
$running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
} while ($running);
where the $this->processPipes->areOpen() will be always open.
It seems to me, if I use any Symfony console command in a Process through phpunit, there will be always two pipes open like these:
1 = {resource} resource id='x' type='stream'
2 = {resource} resource id='z' type='stream'
but I don't know what are these actually. I also saw in htop, that the start()'s proc_open actually starts up a new process, but it just hangs (does absolutely nothing, cant even debug it), until times out. Nothing in error logs (other than the timeout).

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.

Symfony kernel.terminate event run command or service asynchronously

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.

There are no commands defined with command into controller symfony

I want to launch command into a controller symfony. I wrote method and it run correctly command mechanism because I have a return message.
But All command available in my symfony prompt shell command (php bin/console) is not recognize in my launcher command controller method.
I have all the time the same error message into outputBuffered object :
"There are no commands defined in the "XXXX" namespace"
Just one command seems to be run correctly : "help"
But "list" command doesn't run.
My controller method which launch command component :
public function executeDoctrineSchemaAction()
{
\Symfony\Component\VarDumper\VarDumper::dump('here 1');
$kernel = $this->get('kernel');
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput(['command' => 'debug:router']);
$output = new BufferedOutput();
$retval = $application->run($input, $output);
$content = $output->fetch();
\Symfony\Component\VarDumper\VarDumper::dump($content);
return true;
}
Can you help me to understand why command run perfectly into my prompt application admin but not in my controller ?
EDIT : I use this class in my file :
use Symfony\Component\Console\Application;
There is no similar with this kind of solution
The error is catched because it seems that not found namespace

Resources