-1

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.

1
  • Why should the message itself contain the service? That is a class like an entity, which you instantiate using new and now through the service container
    – Nico Haase
    Commented Oct 2 at 14:31

1 Answer 1

2

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

  1. Send a blank message. There is nothing wrong with that.
  2. Autowire the 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.
  3. I would move the logic for querying in the OnlineReceiptRepository
public function getErroredReceipts(): Collection 
{
   $expressionBuilder = Criteria::expr();
   $expression = $expressionBuilder->notIn('status', ['SUCCESS', 'ERROR']);

   return $this->matching($expression);
}
      
  1. Then just call the repository method from your MessageHandler

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
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.