Today i decided to use a queue in order to send the emails of a website i am playing with.
I have used this queue component: https://github.com/wayhood/yii2-queue which works great (given yii doesn’t have an official working queue component yet)
and my job class, looks like:
<?php
namespace app\jobs;
use \app\models\User;
class Users
{
public function accountCreated($job, $data)
{
$user = User::findOne([
'id' => (int)$data['id'],
'status' => User::STATUS_INACTIVE
]);
if (empty($user)) {
return;
}
// send the email here
}
}
Which is pretty standard. This class gets serialized and placed into redis till it’s processed.
Now, the interesting part is that if you have 10 jobs to process at the same time, the mailer component will fail to delivery any email except the first one.
Reason is simple, the mail component, just like other components, is a singleton, so the transport object of the mail component, that is, the connection, remains open after sending first email, so successive jobs will try to use same connection resulting in one huge mess, various error types.
Now, in order to avoid all this and spare some hours of your precious life, the workaround is to clone the mailer object and destroy the transport so that Yii can recreate it.
Here’s what i mean:
public function accountCreated($job, $data)
{
$user = User::findOne([
'id' => (int)$data['id'],
'status' => User::STATUS_INACTIVE
]);
if (empty($user)) {
return;
}
// clone the mailer to avoid concurrency issues accessing same transport.
$mailer = clone \Yii::$app->mailer;
// make sure the transport is gets a reset every time.
$mailer->setTransport([]);
// compose and send.
$mailer->compose('account-created', [
'user' => $user,
'baseUrl' => \Yii::getAlias('@websiteUrl'),
])
->setFrom(app_param('fromEmail'))
->setTo($user->email)
->setSubject('Your account has been created, please verify it!')
->send();
}
I am not sure how correct this approach is, but gets the job done.
If there’s a better way, let me know.
Thanks.