I started to take a look Iron.io as service for my queue process. With the easy set up in laravel I make it work in a couple of minutes but there is something that is not clear to me.
I subscribed a new queue called resizer using the artisan command as the following:
php artisan queue:subscribe resizer http://mywebsite.com/queue/resizer
On the settings in the queue.php file I have to give the name on the key queue of the queue created in this case resizer
'iron' => array(
'driver' => 'iron',
'host' => 'mq-aws-us-east-1.iron.io',
'token' => 'xxxxxx',
'project' => 'xxxx',
'queue' => 'resizer',
'encrypt' => true,
),
But for sure I will have others kind of queues. This resizer queue is responsible to resize images, but I will have to set up another one for send email maybe called email.
Now let's say that I want implement the email queue and also have the resizer well i thought just subscribe another service.
php artisan queue:subscribe email http://mywebsite.com/queue/email
my routes:
Route::post('queue/resizer', function()
{
Queue::marshal();
});
Route::post('queue/email', function()
{
Queue::marshal();
});
Problem:
When I Hit the route queue/email Iron.io fire the resizer instead the email process adding 1 more message to that queue because on the settings I set up resizer.
So how can I have different tasks / queue to assign to Iron.io each one for differents needs?
You can use pushRaw function
pushRaw($payload, $queue = null, array $options = array())
Example:
Queue::pushRaw("This is Hello World payload", "email");
Related
I'm trying to send an email and I get this error
Typed property Symfony\Component\Mailer\Transport\AbstractTransport::$dispatcher must not be accessed before initialization
use Illuminate\Support\Facades\Mail; use App\Mail\Signups as MailSignup;
Route::get('/test_mail', function (){
Mail::to('test#mail.com')->send(new MailSignup(array('field' => null), 'Test subject'));
});
This is the stack trace, it uses just Laravel packages, no custom code beside the route.
It broke after I updated with composer update and composer upgrade
You need to initialize MailSignup before calling send() in route
Route::get('/test_mail', function (){
$mailsignup = new MailSignup(array('field' => null), 'Test subject');
Mail::to('test#mail.com')->send($mailsignup);
});
I add job on queue on page load controller and invoke laravel event and broadcast it via socket io to front end. Problem is since this is done on page load, the job is executed before the page fully loaded. As a result, I can see the response appended for a short time while the page loads and disappeared when its fully loaded. Why is that so?
I doubted if the connection is on sync instead of redis. Upon checking .env and config/queue.php uses redis as default.
DispatchNow is working fine but dispatch is not. Is it due to this the response sent immediately before the page settled down?
In front end, I added the code to connect with socket inside document ready to ensure it's done after the dom is loaded. But doesnt help, it behaves the same.
I tried other workaround where I fire an ajax call to the queue job once the specific DOM element is visible, and it works fine.
But I want it to be called on the page controller itself instead of a separate ajax.
In controller:
$sellings = curl(...some call to external url);
SendOrder::dispatchNow($sellings, Auth::id());
return view('home');
In SendOrder job:
public function handle()
{
// Allow only 2 emails every 1 second
Redis::throttle('any_key')->allow(2)->every(1)->then(function () {
event(new DashboardEvent('job1', $this->order, $this->user));
Log::info('job 1done');
}, function () {
// Could not obtain lock; this job will be re-queued
return $this->release(2);
});
}
.env:
BROADCAST_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
config/queue.php
'default' => env('QUEUE_CONNECTION', 'redis'),
........
.........
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
],
You could simply delay the dispatching with something like:
SendOrder::dispatch()->delay(now()->addMinutes(1))
Also, if you what to dispatch a job after the response is sent back to the client, you could use dispatchAfterResponse() method too.
This method makes the job run after the response is sent and before closing the connection. It simply registers a terminating callback that the application runs before it’s done with the request.
However, this is only useful to dispatch a short job instantly instead of sending it to a queue system. Since sending emails isn't exactly a short job, this might not be for you.
I have 2 balanced servers for my Laravel app, but each of them is logging in it's own target folder (we use a daily file logging): is there any way to have a centralized location to save logs to?
I already arranged the logging configurations to include server name in the output, but i can't figure out how to handle this situation.
Maybe an object storage? or what?
Non third party solutions will be better, we would like to keep everything inside laravel and our servers, but if it's no choice we'll arrange an external solution.
Now there probably isn't a "best" way to do this and Laravel offers a lot of flexibility on how to do this if you check the docs. In fact since NewRelic is supported this can be a decent way to solve this issue if you are using NewRelic already.
We use the database to log. We can do this because the database is hosted outside the servers but in the same VPC of AWS which means the latency is very low. We have a table with id, message, detail, created_at, severity here's a simple way to do it:
In your EventServiceProvider (any service provider will work really)
Event::listen(MessageLogged::class, function (MessageLogged $messageLogged) {
[ $level, $message, $context ] = [ $messageLogged->level, $messageLogged->message, $messageLogged->context ];
if (!\Log::getLogger()->isHandling(Logger::toMonologLevel($level))) {
return; // Don't log if the logger isn't actually handling this level
}
if (($message instanceof \Exception || $message instanceof \Throwable)) {
$context = [$message];
$message = $message->getMessage();
}
DB::table('log')->insert([
'message' => $message
'detail' => implode(PHP_EOL, $context ?: []),
'created_at' => Carbon::now(),
'severity' => $level
]);
});
If performance is an issue you can also create a task that will queue the processing of the log event, but that means you have to queue it with the time the event happened rather than the time it was handled.
Email sending works fine when I have a global 'to' set in config/mail.php
'to' => [
'address' => 'someone#example.com',
'name' => 'Someone',
however as soon as this is removed and email is supposedly sent to the actual email address, nothing happens. No errors, everything appears fine, except the email is never received.
I've checked spam folders, I've tried sending to different email addresses, I've tried setting the notifiable route (even though 'email' exists on the model), I've cleared cache and config cache, I've tried listening to message sending event and dumping results - I'm lost.
solved.
When sending a MailableObject (as apposed to a MailMessage) you need to chain the 'to' command like so:
public function toMail($notifiable)
{
return (new MailObject(blah blah))->to($notifiable);
}
In my application each user can use his own SMTP server. Therefor the config must be provided. I'm using Laravel Notifications to send the emails. If I'm using no queue (that means sync), there is no problem.
I made a CustomNotifiable Trait:
config([
'mail.host' => $setting->smtp_host,
'mail.port' => $setting->smtp_port,
'mail.username' => $setting->smtp_username,
'mail.password' => $setting->smtp_password,
'mail.encryption' => $setting->smtp_encryption,
'mail.from.address' => $setting->smtp_from_address,
'mail.from.name' => $setting->smtp_from_name,
]);
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
After that, I restore the original config:
config([
'mail' => $originalMailConfig
]);
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
No problem until now.
But if it's queued, just the first config after starting the queue worker will be taken for all further emails, even if any other SMTP config is provided. The default config from config/mail.php will be overridden. But this only works the first time.
I've made in the AppServiceProvider::boot method (the SMTP config is stored at the notification):
Queue::before(function (JobProcessing $event) {
// Handle queued notifications before they get executed
if (isset($event->job->payload()['data']['command']))
{
$payload = $event->job->payload();
$command = unserialize($payload['data']['command']);
// setting dynamic SMTP data if required
if (isset($command->notification->setting))
{
config([
'mail.host' => $command->notification->setting->smtp_host,
'mail.port' => $command->notification->setting->smtp_port,
'mail.username' => $command->notification->setting->smtp_username,
'mail.password' => $command->notification->setting->smtp_password,
'mail.encryption' => $command->notification->setting->smtp_encryption,
'mail.from.address' => $command->notification->setting->smtp_from_address,
'mail.from.name' => $command->notification->setting->smtp_from_name,
]);
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
}
}
});
Of course, the original config get restored:
Queue::after(function (JobProcessed $event) use ($originalMailConfig) {
$payload = $event->job->payload();
$command = unserialize($payload['data']['command']);
// restore global mail settings
if (isset($command->notification->setting))
{
config([
'mail' => $originalMailConfig
]);
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
}
});
It seems, as the Swift Mailer has a cache or something like that. I registered a new MailServiceProvider, which should simply replace the old one. So if I set the config with the new SMTP data, the new registered provider should take them. Logging the config shows even in the TransportManager, that the correct SMTP data were set, right before sending the mail, but the mail was sent with the first set config.
I found this thread and tried the linked solution, but with the same result: How to set dynamic SMTP details laravel
So I need a way to override the Services / ServiceProvider / SMTP config. Even if the Supervisor restarts the queue, there is a chance that multiple emails with different configs should be send at the same time.
In Laravel 5.4+, as I see that the Mailer Class is a singleton that hold a MailTransport Class, which is responsible for the config of SMTP mail and is a singleton,too; I just have to override the config using the following approach:
First, I setup a trait so I can just turn this feature on some Mails:
trait MailSenderChangeable
{
/**
* #param array $settings
*/
public function changeMailSender($settings)
{
$mailTransport = app()->make('mailer')->getSwiftMailer()->getTransport();
if ($mailTransport instanceof \Swift_SmtpTransport) {
/** #var \Swift_SmtpTransport $mailTransport */
$mailTransport->setUsername($settings['email']);
$mailTransport->setPassword($settings['password']);
}
}
}
Then, in the build() method of your mail class, you can utilize the above trait and call:
$this->changeMailSender([
'email'=>$this->company->email,
'password'=>$this->company->email_password,
]);
Boom, let the Laravel do the rest.
After a lot of researching I stumbled upon the different queue commands. I tried queue:listen (which is not described in the Laravel 5.4 docs) instead of queue:work and the problems are gone.
Of course, this doesn't really explain the described behavior, but fortunately it doesn't matter, because I can live with this solution/workaround.
Another strange behavior is, that from time to time the queue worker throws an exception because the database was locked. No idea, when or why this happened.
This post explained a little bit, why things can happen: What is the difference between queue:work --daemon and queue:listen
In a nutshell, queue:listen solved my problem and another very strange db lock problem as well.