Laravel: customize or extend notifications - database model
Asked Answered
R

6

23

IMHO, the current Database channel for saving notifications in Laravel is really bad design:

  • You can't use foreign key cascades on items for cleaning up notifications of a deleted item for example
  • Searching custom attributes in the data column (casted to Array) is not optimal

How would you go about extending the DatabaseNotification Model in vendor package?

I would like to add columns event_id, question_id, user_id (the user that created the notification) etc... to the default laravel notifications table

How do you override the send function to include more columns?

In:

vendor/laravel/framework/src/Illuminate/Notifications/Channels/DatabaseChannel.php

The code:

class DatabaseChannel
{
 /**
  * Send the given notification.
  *
  * @param  mixed  $notifiable
  * @param  \Illuminate\Notifications\Notification  $notification
  * @return \Illuminate\Database\Eloquent\Model
  */
 public function send($notifiable, Notification $notification)
 {
    return $notifiable->routeNotificationFor('database')->create([
        'id' => $notification->id,
        'type' => get_class($notification),

      \\I want to add these
        'user_id' => \Auth::user()->id,
        'event_id' => $notification->type =='event' ? $notification->id : null, 
        'question_id' => $notification->type =='question' ? $notification->id : null,
      \\End adding new columns

        'data' => $this->getData($notifiable, $notification),
        'read_at' => null,
    ]);
 }
}
Reedy answered 26/4, 2017 at 23:17 Comment(0)
R
45

To create a custom Notification Channel:

First, create a Class in App\Notifications for example:

<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;

class CustomDbChannel 
{

  public function send($notifiable, Notification $notification)
  {
    $data = $notification->toDatabase($notifiable);

    return $notifiable->routeNotificationFor('database')->create([
        'id' => $notification->id,

        //customize here
        'answer_id' => $data['answer_id'], //<-- comes from toDatabase() Method below
        'user_id'=> \Auth::user()->id,

        'type' => get_class($notification),
        'data' => $data,
        'read_at' => null,
    ]);
  }

}

Second, use this channel in the via method in the Notification class:

<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;

use App\Notifications\CustomDbChannel;

class NewAnswerPosted extends Notification
{
  private $answer;

  public function __construct($answer)
  {
    $this->answer = $answer;
  }

  public function via($notifiable)
  {
    return [CustomDbChannel::class]; //<-- important custom Channel defined here
  }

  public function toDatabase($notifiable)
  {
    return [
      'type' => 'some data',
      'title' => 'other data',
      'url' => 'other data',
      'answer_id' => $this->answer->id //<-- send the id here
    ];
  }
}
Reedy answered 27/4, 2017 at 13:3 Comment(5)
how to get user information from notification variable?Emlynn
In my case, for some reasons, native notifications table still in use...Rovit
Driver [App\Notifications\CustomDatabaseChannel] not supportedAlpheus
For me instead toDatabase(), toArray() workedMithras
How would you select what table is used. For example for certain notifications I would like them to be saved in table that is in a different schema? is this possible?Salvador
I
13

Create and use your own Notification model and Notifiable trait and then use your own Notifiable trait in your (User) models.

App\Notifiable.php:

namespace App;

use Illuminate\Notifications\Notifiable as BaseNotifiable;

trait Notifiable
{
    use BaseNotifiable;

    /**
     * Get the entity's notifications.
     */
    public function notifications()
    {
        return $this->morphMany(Notification::class, 'notifiable')
                            ->orderBy('created_at', 'desc');
    }
}

App\Notification.php:

namespace App;

use Illuminate\Notifications\DatabaseNotification;

class Notification extends DatabaseNotification
{
    // ...
}

App\User.php:

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    // ...
}
Ihs answered 17/4, 2018 at 17:51 Comment(4)
I extended DatabaseNotification class and created notifications method with my own Notification model. and created custom Notifiable trait too. but after extending notifications uuid is not workng its giving Duplicate entry '' for key 'PRIMARY' error and its storing id as '' blank value. I tried protected $keyType = 'string'; and public $incrementing = false; on Notification model alsoCohberg
@NikhilRadadiya Make sure you use the Illuminate\Notifications\Notifiable trait in your own trait as shown in the example.Ihs
Yes, I did that wayCohberg
This worked perfectly! I had issue where notifications and notifiable models were on 2 separate mssql databases. And I easily fixed that by this example. Thanks!Cobelligerent
Y
8

An example for @cweiske response.

If you really need extends the Illuminate\Notifications\Channels\DatabaseChannel not creating a new Channel you can:

Extends the channel:

<?php

namespace App\Notifications;

use Illuminate\Notifications\Channels\DatabaseChannel as BaseDatabaseChannel;
use Illuminate\Notifications\Notification;

class MyDatabaseChannel extends BaseDatabaseChannel
{
    /**
     * Send the given notification.
     *
     * @param  mixed  $notifiable
     * @param  \Illuminate\Notifications\Notification  $notification
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function send($notifiable, Notification $notification)
    {
        $adminNotificationId = null;
        if (method_exists($notification, 'getAdminNotificationId')) {
            $adminNotificationId = $notification->getAdminNotificationId();
        }

        return $notifiable->routeNotificationFor('database')->create([
            'id' => $notification->id,
            'type' => get_class($notification),
            'data' => $this->getData($notifiable, $notification),

            // ** New custom field **
            'admin_notification_id' => $adminNotificationId,

            'read_at' => null,
        ]);
    }
}

And register the Illuminate\Notifications\Channels\DatabaseChannel on application container again:

app\Providers\AppServiceProvider.php

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(
            Illuminate\Notifications\Channels\DatabaseChannel::class,
            App\Notifications\MyDatabaseChannel::class
        );
    }
}

Now when the Illuminate\Notifications\ChannelManager try createDatabaseDriver will return your registered database driver.

More one option to solve this problem!

Yablon answered 8/6, 2019 at 14:13 Comment(2)
The register part didn't work for me, but I found this article medium.com/@alouinimedamin/… and got it working by passing $this->app->make(..Mailer class..) and $this->app->make(..Markdown class..)for my override mail channel classCollier
What I wanted to change is how timestamps are stored in notifications table in SQL Server, and this has been the point. By adding 'created_at' => Carbon::now()->format('Y-d-m H:i:s.0') and 'updated_at' => Carbon::now()->format('Y-d-m H:i:s.0') to the create function, it has been solved! Laravel documentation many times doesn't cover all situations, so... Thank you all!!Benzol
E
4

Unlike "Bassem El Hachem", I wanted to keep the database keyword in the via() methods.

So in addition to a custom DatabaseChannel, I also wrote my own ChannelManager that returns my own DatabaseChannel in the createDatabaseDriver() method.

In my apps' ServiceProvider::register() method, I overwrote the singleton for the original ChannelManager class to return my custom manager.

Editorial answered 28/9, 2018 at 5:54 Comment(0)
G
0

You can inject new columns' values on the model level by listening to the creating event.

  1. Set your notification.
namespace App\Notifications;

use Illuminate\Notifications\Notification;

class YourNotification extends Notification
{
    // ...

    public function toArray($notifiable): array
    {
        return [
            'new_column' => 'value',
            'serializable_data' => $serializable_data,
        ];
    }
}
  1. Create your own notification model and edit what is being inserted.
namespace App\Models;

use Illuminate\Notifications\DatabaseNotification;

class Notification extends DatabaseNotification
{
    protected static function booted(): void
    {
        static::creating(function (Notification $notification) {
            $data = $notification->data;

            $notification->new_column = $data['new_column'];
            unset($data['new_column']);

            $notification->data = $data;
        });
    }
}
  1. Override the relationship on the user model.
    public function notifications()
    {
        return $this->morphMany(Notification::class, 'notifiable')->orderBy('created_at', 'desc');
    }
Gloucestershire answered 13/3, 2023 at 19:8 Comment(0)
T
-1

I solved a similar problem by customizing notification class:

create the class for this action:

artisan make:notification NewQuestion

inside it:

public function __construct($user,$question)
    {
        $this->user=$user;
        $this->question=$question;
    }


...

    public function toDatabase($notifiable){
        $data=[
            'question'=>$this->(array)$this->question->getAttributes(),
            'user'=>$this->(array)$this->user->getAttributes()
        ];

        return $data;
    }

then you can access proper data in view or controller like this:

@if($notification->type=='App\Notifications\UserRegistered')
<a href="{!!route('question.show',$notification->data['question']['id'])!!}">New question from {{$notification->data['user']['name']}}</a>
@endif
Thomson answered 4/7, 2017 at 14:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.