24

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,
    ]);
 }
}

6 Answers 6

46

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
    ];
  }
}
Sign up to request clarification or add additional context in comments.

5 Comments

how to get user information from notification variable?
In my case, for some reasons, native notifications table still in use...
Driver [App\Notifications\CustomDatabaseChannel] not supported
For me instead toDatabase(), toArray() worked
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?
14

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;

    // ...
}

4 Comments

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 also
@NikhilRadadiya Make sure you use the Illuminate\Notifications\Notifiable trait in your own trait as shown in the example.
Yes, I did that way
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!
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!

2 Comments

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 class
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!!
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.

Comments

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');
    }

Comments

-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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.