I'm trying to avoid cron and do the job with symfony scheduler. I'm almost there (tested writing some text to a file every 20 seconds), but I'm having hard time with understanding where to put the db queries.
I thought that it would be reasonable to fetch the db results in the message by autowiring the Entity Manager
//src/Scheduler/Message/CheckReceiptStatus.php
namespace App\Scheduler\Message;
use App\Entity\OnlineReceipt;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Common\Collections\Criteria;
class CheckReceiptStatus
{
public function __construct(
private EntityManagerInterface $entityManager,
) {}
public function getErroredReceipts()
{
$expressionBuilder = Criteria::expr();
$expression = $expressionBuilder->notIn('status', ['SUCCESS', 'ERROR']);
$receipts_with_no_status = $this->entityManager->getRepository(OnlineReceipt::class)->matching(new Criteria($expression));
return $receipts_with_no_status;
}
}
Then in the handler I planned to make some db inserts. Instead of writing into file (tested the scheduler/message code that way) I would have some doctrine queries.
// src/Scheduler/Handler/CheckReceiptStatusHandler.php
namespace App\Scheduler\Handler;
use App\Scheduler\Message\CheckReceiptStatus;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
class CheckReceiptStatusHandler
{
public function __construct(
private readonly KernelInterface $kernel,
){}
public function __invoke(CheckReceiptStatus $status) : void
{
$path = $this->kernel->getProjectDir().'/var/log/cron_test.log';
file_put_contents($path, $status->getErroredReceipts(), FILE_APPEND);
}
}
However, I see an error notice in my scheduler code that reads that new CheckReceiptStatus()
expects 1 argument.
// src/Scheduler/ReceiptTaskScheduler.php
namespace App\Scheduler;
use App\Scheduler\Message\CheckReceiptStatus;
use Symfony\Component\Scheduler\Attribute\AsSchedule;
use Symfony\Component\Scheduler\RecurringMessage;
use Symfony\Component\Scheduler\Schedule;
use Symfony\Component\Scheduler\ScheduleProviderInterface;
#[AsSchedule('default')]
class ReceiptTaskScheduler implements ScheduleProviderInterface
{
public function getSchedule(): Schedule
{
$schedule = new Schedule();
$schedule->add(RecurringMessage::every("20 seconds", new CheckReceiptStatus()));
return $schedule;
}
}
Since I don't pass anything to the message class (as I plan to get results there) I am not sure what argument is expected. There is definitely something wrong with my autowiring and logic.
Message is something, that only carries dumb data to let the handler know, what to do (i.e. id of an entity, some string, array of primitive types...). In symfony documentation, you can notice, that the only requirement for a message class is that it is serializable. That is not your case, because how would you serialize EntityManagerInterface
? The handler on the other hand should do all the hard lifting and so you can inject services, repositories or other dependencies into it.
Here are suggestions on how to improve your code
OnlineReceiptRepository
to your handler. Autowiring this instead of EntityManagerInterface
is beneficial for read operations, because you get full type hint without the need of PHPDoc. When you decide to insert, you can autowire the EntityManagerInterface
, or make some adjustments to the repository class.OnlineReceiptRepository
public function getErroredReceipts(): Collection
{
$expressionBuilder = Criteria::expr();
$expression = $expressionBuilder->notIn('status', ['SUCCESS', 'ERROR']);
return $this->matching($expression);
}
PS: This is taste of personal preference, but you can autowire parameters defined by the kernel in a less cumbersome way using the #[Autowire(param: 'kernel.project_dir')]
. Since you are using Scheduler, this should work on your Symfony version :)
Your handler would then look something like this (imports and namespaces ommited):
#[AsMessageHandler]
class CheckReceiptStatusHandler
{
private const FILE_NAME = '/var/log/cron_test.log';
public function __construct(
private readonly OnlineReceiptRepository $receiptRepository,
#[Autowire(param: 'kernel.project_dir')]
private readonly string $projectDir,
){}
public function __invoke(CheckReceiptStatus $status) : void
{
$erroredReceipts = $this->receiptRepository->getErroredReceipts();
// Process errored receipts below
}
}