Is there a way to pass service/containers in child process?

Viewed 275

I am testing spatie's async project. I created a task as such.

use Spatie\Async\Task;

class ServiceTask extends Task
{
    protected $accountService;
    protected $serviceFactory;

    public function __construct(ServiceFactory $serviceFactory)
    {
        $this->serviceFactory = $serviceFactory;
    }
    public function configure()
    {
        $this->accountService = $this->serviceFactory->getAccountService();
    }

    public function run()
    {
        //accounting tasks
    }
}

And for the pool:

$pool = Pool::create();

foreach ($transactions as $transaction) {
    $pool->add(new ServiceTask($serviceFactory))
        // handlers
    ;
}

$pool->wait();

When I run the above code, I simply get

Serialization of 'Closure' is not allowed

I know that we cannot simply serialize a closure, I tried the same code above with a simple plain Data Transfer Object, it worked fine. But when passing a service, or a container class from symfony I get above error. Is there a work around for this?

2 Answers

Short answer: no

Longer answer: Spatie Async serializes task before adding it to the pool, so you might need an alternative solution.

Why Async needs a serialized task

See relevant code to understand what's going on: Pool::add > ParentRuntime::createProcess > ParentRuntime::encodeTask.

For more discussion, see this or this issue in spatie/async's issue list.

Alternative: Symfony Messenger

There are many alternatives to this problem. Since you're using Symfony, you might be interested in Symfony Messenger to send messages to a handler. Those handlers can use dependency injection:

class DefaultController extends AbstractController
{
    public function index(MessageBusInterface $bus)
    {
        $bus->dispatch(new ServiceTask('Look! I created a message!'));
    }
}

class ServiceTaskHandler implements MessageHandlerInterface
{
    protected $accountService;
    protected $serviceFactory;

    public function __construct(ServiceFactory $serviceFactory)
    {
        $this->serviceFactory = $serviceFactory;
        $this->accountService = $this->serviceFactory->getAccountService();
    }

    public function __invoke(ServiceTask $task)
    {
         $this->accountService->handle($task);            
    }
}

Be aware that a Task (ServiceTask in this example) should (like spatie/async's task) be serializable as well. So you might send an ID as a message, and look up that ID in your MessageHandler

Serialization of 'Closure' is not allowed is an error that is formed when passing a cued event as an argument. Likely the issue has to do with creating the object in a factory then passing it as part of the constructor.

Related