skip to Main Content
  • Laravel Version: 8.78.1
  • PHP Version: 8.0.10

I’ve created a custom command to run on a schedule and email a notification.

My Command class handle method:

public function handle()
{
    $sql = "SELECT * FROM Licences WHERE (Expired = 1)";
    $list = DB::select($sql);

    return (new NotifyExpiredLicences($list))->toMail('[email protected]');
}

My notification method:

public function toMail($notifiable)
{
    return (new MailMessage)
            ->subject('Clients with Expired Licences')
            ->markdown('vendor/notifications/expiredlicences', 
                ['clients' => $this->list, 'toname' => 'Me']);
}

Whenever I test this by running it manually with php artisan email:expired-licences I get the following error Object of class IlluminateNotificationsMessagesMailMessage could not be converted to int from my command class in the handle method.

However, the preview of my email works fine & displays as expected:

Route::get('/notification', function () {
    return (new SendExpiredLicences())->handle();
});

If I remove the return statement from my handle() method, then although I get no errors, neither in my console or in storagelogs, also the preview stops working.

At this point I’m sure I’ve missed something important from the way this is supposed to be done, but after going through the Laravel docs and looking at online tutorials/examples, I’ve no idea what.

2

Answers


  1. Chosen as BEST ANSWER

    I've got everything working - though not entirely sure it's the "Laravel way". If anyone's got suggestions for improving it - add a comment or new answer and I'll try it out.

    ConsoleKernel.php:

    protected function schedule(Schedule $schedule) 
    {
        $schedule->command('email:expired-licences')
                    ->weekdays()
                    ->at('08:30');
    }
    

    AppConsoleCommandsSendExpiredLicences.php:

    class SendExpiredLicences extends Command
    {
      protected $signature = 'email:expired-licences';
      protected $description = 'Email a list of expired licences to Admin';
      private $mail;
    
      public function _construct()
      {
          $clients = DB::select("[Insert SQL here]");
          $this->mail = (new NotifyExpiredLicences($clients))->toMail('[email protected]');
          parent::__construct();
      }
    
      public function handle()
      {
        Mail::to('[email protected]')->send($this->mail);
        return 0;
      }
    
      public function preview()
      {
        return $this->mail;
      }
    }
    

    AppNotificationsNotifyExpiredLicences.php:

    class NotifyExpiredLicences extends Notification
    {
        public function __construct(protected $clients)
        {
        }
    
        public function via($notifiable)
        {
            return ['mail'];
        }
    
        public function toMail($notifiable)
        {
            return (new Mailable($this->clients));
        }
    }
    

    AppMailExpiredLicences.php:

    class ExpiredLicences extends Mailable
    {
      public function __construct(private $clients)
      {
      }
    
      public function build()
      {
        return $this
                ->subject('Clients with Expired Licences')
                ->markdown('emails/expiredlicences', 
                    ['clients' => $this->clients, 'toname' => 'Admin']);
      }
    }
    

    resourcesviewsemailsexpiredlicences.blade.php:

    @component('mail::message')
    
      # Hi {!! $toname !!},
    
    @component('mail::table')
        | Client        | Expired  |
        | ------------- | --------:|
        @foreach ($clients as $client)
        |{!! $client->CompanyName !!} | {!! $client->Expired !!}|
        @endforeach
    @endcomponent
    
    <hr />
    Thanks, {!! config('app.name') !!}
    @endcomponent
    

    For previewing with the browser routesweb.php:

      Route::get('/notification', function () {
          return (new SendExpiredLicences())->preview();
      });
    

  2. Ok just to save more commenting, here’s what I’d recommend doing. This is all based on the Laravel docs, but there are multiple ways of doing it, including what you’ve used above. I don’t really think of them as "right and wrong," more "common and uncommon."

    ConsoleKernel.php: I’d keep this mostly as-is, but pass the email to the command from a config file, rather than having it fixed in the command.

    use AppConsoleCommandsSendExpiredLicences;
    …
    protected function schedule(Schedule $schedule) 
    {
        $recipient = config('myapp.expired.recipient');
        $schedule->command(SendExpiredLicences::class, [$recipient])
            ->weekdays()
            ->at('08:30');
    }
    

    config/myapp.php:

    <?php
    
    return [
        'expired' => [
            'recipient' => '[email protected]',
        ],
    ];
    

    AppConsoleCommandsSendExpiredLicences.php: update the command to accept the email address as an argument, use on-demand notifications, and get rid of preview() method. Neither the command or the notification need to know about the client list, so don’t build it yet.

    <?php
    namespace AppConsoleCommands;
    
    use AppConsoleCommand;
    use AppNotificationsNotifyExpiredLicences;
    use IlluminateSupportFacadeNotification;
    
    class SendExpiredLicences extends Command
    {
      protected $signature = 'email:expired-licences {recipient}';
      protected $description = 'Email a list of expired licences to the given address';
    
      public function handle()
      {
          $recip = $this->argument('recipient');
          Notification::route('email', $recip)->notify(new NotifyExpiredLicences());
      }
    }
    

    AppNotificationsNotifyExpiredLicences.php: the toMail() method should pass the notifiable object (i.e. the user getting notified) along, because the mailable will be responsible for adding the To address before the thing is sent.

    <?php
    namespace AppNotifications;
    
    use AppMailExpiredLicenses;
    use IlluminateNotificationsNotification;
    
    class NotifyExpiredLicences extends Notification
    {
        public function via($notifiable)
        {
            return ['mail'];
        }
    
        public function toMail($notifiable)
        {
            return (new ExpiredLicenses($notifiable));
        }
    }
    

    AppMailExpiredLicences.php: since the mail message actually needs the list of clients, this is where we build it. We get the recipient here, either from the user’s email or the anonymous object.

    <?php
    namespace AppMail;
    
    use AppModelsClient;
    use IlluminateNotificationsAnonymousNotifiable;
    
    class ExpiredLicences extends Mailable
    {
        private $email;
    
        public function __construct(private $notifiable)
        {
            // this allows the notification to be sent to normal users
            // not just on-demand
            $this->email = $notifiable instanceof AnonymousNotifiable
                ? $notifiable->routeNotificationFor('mail')
                : $notifiable->email;
        }
    
        public function build()
        {
            // or whatever your object is
            $clients = Client::whereHas('licenses', fn($q)=>$q->whereExpired(1));
    
            return $this
                ->subject('Clients with Expired Licences')
                ->markdown(
                    'emails.expiredlicences', 
                    ['clients' => $clients, 'toname' => $this->notifiable->name ?? 'Admin']
                )
                ->to($this->email);
        }
    }
    

    For previewing with the browser routesweb.php:

    Route::get('/notification', function () {
        // create a dummy AnonymousNotifiable object for preview
        $anon = Notification::route('email', '[email protected]');
        return (new ExpiredLicencesNotification())
            ->toMail($anon);
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search