slickness's avatar

Use function from Model in Notifications

How can I use functions of the model in the notifications of laravel?

i want to use this function:

     public function isFollowed() 
        {
            return $this->followers()
                ->where('follower_id', auth()->id())->exists();
        }

i get the follow error: Call to a member function isFollowed() on array This is my current code:

    <a class="@if($notification->read_at)@else unread-notification @endif min-height-60" href="#">
            <img class="rounded mt-5" src="{{ Storage::url( $notification->data['user']['avatar'] ) }}" width="45" height="45" alt="" />
            <small class="float-right" style="color:#666">{{$notification->created_at->diffForHumans()}}</small>
            <h5 class="fs-11 text-black mt-10 mb-0"><b>@if( Auth::id() == $notification->data['user']['id'] OR $notification->data['profile']['privacy'] == 1 OR $notification->data['profile']['privacy'] == 2 OR $notification->data['user']->isFollowed(Auth::id()) ){{$notification->data['user']['username']}}@else AC @endif</b> hat dein Post kommentiert: <b>{!! $notification->data['comment']['body'] !!}</b></h5>
        </a>

if i use

    $notification->data['user']['id']->isFollowed(Auth::id())

Then i get the follow error:

    Call to a member function isFollowed() on integer

This is the NotifyComment:

class NotifyComment extends Notification
{
    use Queueable;

    protected $comment;
    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($comment)
    {
        $this->comment = $comment;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['database'];
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toDatabase($notifiable)
    {
        return [
            'comment' => $this->comment,
            'user' => User::find($this->comment->user_id),
            'post' => Post::find($this->comment->post_id),
            'profile' => Profile::find($this->comment->user_id)
        ];
    }
}
0 likes
10 replies
D9705996's avatar

You should pass your data into the notifable in the custuctor

use App\Notifications\InvoicePaid;

$user->notify(new InvoicePaid($invoice));

You can the pass your data to a view as below

public function toMail($notifiable)
{
    return (new MailMessage)->view(
        'emails.name', ['invoice' => $this->invoice]
    );
}

The above examples are from

https://laravel.com/docs/5.7/notifications#formatting-mail-messages

You should just need to change go meet your needs

1 like
Roni's avatar

there's alot missing to answer this question, however,


return $this->followers()
                ->where('follower_id', auth()->id())->exists();

is returning a boolean based on if the data exists in the DB.

  1. from the code shown above, isFollowed doesn't take a parameter. And if you are only using this function for the authorized user, it doesn't need to so don't add it in your call.

  2. guessing only from the view that


$notification->data['user']['id']

is an integer, it's the user's id not the user object. Is notification->data a collection of users? An array?

If for example it was a collection or array of user objects you could instead use"


foreach (notification->data['user'] as $user) {
    //then do something with the user
    <div>{{ $user->isFollowed() }}</div> 
}


//you could extract the specific user stuff to a to a view partial or use some functional helpers

$notification->data->pluck('user')->map( function ($user) {
    @include('path.to.user.display')
}


Hope that helps.

slickness's avatar

@Roni thanks for your help. Thank you for your help. But how can I integrate this into my if statement?

@if( Auth::id() == $notification->data['user']['id'] OR $notification->data['profile']['privacy'] == 1 OR $notification->data['profile']['privacy'] == 2 OR $notification->data['user']->isFollowed(Auth::id()) ){{$notification->data['user']['username']}}@else AC @endif ```
Roni's avatar

@slickness, a suggestion. You look like you are coming from a procedural style background (me too) where you first look at blade and think awesome and hack it apart with heavy logic.

My first suggestion is actually to read up a bit on what @D9705996 mentioned.

But if you want to go this route of heavy logic in a blade file, maybe turn your Notification into a view model of sorts, and abstract this behavior behind a semantic function.

//1. Add some constants to create a more readable request


//these are made up guesses 

//maybe in App\PrivacySetting.php

const PUBLIC_VISIBLE = 1;
const GROUP_VISIBLE = 2;
...

const CAN_SEE_USERNAME = [
    self::PUBLIC_VISIBLE,
    self::GROUP_VISIBLE
];


// etc...

then in your notify comment




use App\PrivacySetting; // import constants


//change your constructor

protected $comment;
protected $author; //only to clarify your understanding a bit

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($comment)
    {
        $this->comment = $comment;
        $this->author = $comment->user; //you probably have that relationship on your model
    
        // actually you should name it better
        $this->author = $comment->author; //much more clear
    }

//NOW ADD A HELPER THAT VIEW MODEL CONCEPT

public function displayAuthorName(){


    if( auth()->id() == $this->author->id ||
         in_array($this->author->privacy, PrivacySetting::CAN_SEE_USERNAME)||
         $this->author->isFollowed()) {
    
        return $this->author->username;

    }
    return 'AC';
     
}

Then maybe in your view just render it instead of that whole pile of stuff


{{ $notification->displayAuthorName() }}


Please consider separation of concerns as you grow as a developer. You want your designers designing, and your backend easily upgradable. Look at spatie view models here is a link when you for when you bloat your models and controllers.

https://github.com/spatie/laravel-view-models

in fact look at spatie everything.

Cheers

-Roni

1 like
slickness's avatar

@roni thanks for your help. Unfortunately it does not work. I have added the helper function to my NotifyComment. But I get the following error message:

Call to undefined method Illuminate\Notifications\DatabaseNotification::displayAuthorName() 
Roni's avatar

ok @slickness I'm trying to help as much as I can but you aren't giving me a whole lot to help you with. Do you want to post it to a public repo? Can you tell me the steps you actually took?

Where exactly you put what? It's probably an easy fix, but I can't address it other than in generalities because all I can see is the comments and not the code.

That error tells me you put the function somewhere, and it's not where it needed to be. So... either I don't understand your file structure. Or you didn't understand my instructions. Both are ok.

When you make a notification, the content of the notification class that is public to the class can be accessed by the mail or view. you can move that function to the comment class itself if you like, and then call it directly from the class.

You still need to know oop php scope resolution to know where you have visibility and where you don't. I don't have time to guess at it. If its a private repo, then good luck or post actual files with explanations.

If it's a public repo link it here, and I'll clone it and check it.

Good luck either way.

2 likes
slickness's avatar

@Roni

You're right, it's certainly not easy to help me with so little information. Thank you for your patience and your good help. I hope, we find a solution. The repo is not public. I will post the code here. This is my current code:

View:

Header

<li class="quick-cart" id="notification-button">
          <a class="m-0" href="#">
            @if (auth()->user()->unreadNotifications->count())<span class="badge badge-green btn-sm badge-corner mt-10">{{auth()->user()->unreadNotifications->count()}}</span>@endif
            <i class="fa fa-globe fs-25" @if(auth()->user()->unreadNotifications->count()) style="color:#9c3;" @else style="color:#245580;"  @endif></i>
          </a>

          <div class="quick-cart-box">
            <h4><i class="fa fa-globe"></i>  {{__('header.2')}}</h4>
            <div class="quick-cart-wrapper">
              @if (auth()->user()->notifications->count() == 0)
                <div class="text-center text-black mt-15">
                  <i class="glyphicon glyphicon-warning-sign fa-2x"></i>
                  <h5 class="fs-11 mb-15 mt-5">Du hast noch keine Benachrichtigungen erhalten.</h5>
                </div>
              @else
                <div class="infinite-scroll">
                  @foreach(auth()->user()->notifications()->paginate(10) as $notification)
                    @include('elements.notifications.'.snake_case(class_basename($notification->type)))
                  @endforeach
                  {{auth()->user()->notifications()->paginate(10)->links()}}
                </div>
              @endif
            </div>
          </div>
        </li>

notify_comment.blade.php

<a class="@if($notification->read_at)@else unread-notification @endif min-height-60" href="#">
    <img class="rounded mt-5" src="@if(Auth::id() == $notification->data['user']['id'] OR $notification->data['profile']['privacy'] == 1 OR $notification->data['profile']['privacy'] == 2) {{ Storage::url($notification->data['user']['avatar']) }} @else {{Storage::url($user->AnonymAvatar) }} @endif" width="45" height="45" alt="" />
    <small class="float-right" style="color:#666">{{$notification->created_at->diffForHumans()}}</small>
    <h5 class="fs-11 text-black mt-10 mb-0"><b>@if( Auth::id() == $notification->data['user']['id'] OR $notification->data['profile']['privacy'] == 1 OR $notification->data['profile']['privacy'] == 2){{$notification->data['user']['username']}}@else AC @endif</b> hat dein Post kommentiert: <b>{!! $notification->data['comment']['body'] !!}</b></h5>
</a>

NotifyComment.php


class NotifyComment extends Notification
{
    use Queueable;

    protected $comment;
    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($comment)
    {
        $this->comment = $comment;
        $this->author = $comment->user;
    }

   public function displayAuthorName(){


       if( auth()->id() == $this->author->id ||
            in_array($this->author->privacy, PrivacySetting::CAN_SEE_USERNAME)||
            $this->author->isFollowed()) {
    
           return $this->author->username;

       }
       return 'AC';
     
   }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['database'];

    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toDatabase($notifiable)
    {
        return [
            'comment' => $this->comment,
            'user' => User::find($this->comment->user_id),
            'post' => Post::find($this->comment->post_id),
            'profile' => Profile::find($this->comment->user_id),
        ];

    }
}

In the header view I load the appropriate view for the notification type. For example, if it is a comment notification, then notify_comment.blade.php is loaded. If it is a comment review, it will load notify_comment_activity.blade.php.

Roni's avatar

ok, I'm missing the "connective tissue" where are you creating the notification. In notify_comment.blade.php what is the $notification? How are you passing it to the view? Please share that or (and worse in my opinion)

move the following function to the comment class.

// remember you'll have to reset what
// $this->author is in the new class.
// and import PrivacySetting


public function displayAuthorName(){


       if( auth()->id() == $this->author->id ||
            in_array($this->author->privacy, PrivacySetting::CAN_SEE_USERNAME)||
            $this->author->isFollowed()) {
    
           return $this->author->username;

       }
       return 'AC';
     
   }



then you should be able to access it here


{{ $notification->data['comment']->displayAuthorName() }}

I'm not against using a base model to detail how its data should be look, however, I think it should be on some middle ground area. I used to use view models (I'm reaching into the past for terminology there, I think I forgot what they were called) there are lots of lessons here on laracasts. But I look at that spatie package. Consider some helper classes that do all your formatting.

If I have a ton of UI options like that I will always defer to a helper class, all you have to really do is pass in your relevant model or models, and then make your logical functions with all the ugly display logic.

I have a couple of HUGE projects (for me that is) and the thought of going back through all the forms and making UX changes in 100+ places would make me cry.

When people advise deferring that to a wrapper function it's not just giving you an opinion of meaningless A vs meaningless B is better. It's the horrible pain of correcting the mistake that they wish someone had passed on to them before they got into the mess.

Sorry I'm on my phone watching a kids tae kwon-do class so I got talkative.

So find whats driving that notification, it's not the NotifyComment Object or you would already have had access to it. Then look at your blade flies and pull out those conditionals.

if you get stuck you can always try a dd(get_class($notification)); Next year when you upgrade, you'll be thankful you did.

And post that route controller where you are actually passing around the data please if either of those refactors won't work.

slickness's avatar

@Roni I have to agree with you. It is better if I write the displylogic in the models. Thanks for the note :)

But with this code i get the follow error:

Call to a member function displayAuthorName() on array 

I would like to give you a better insight, maybe you can then help me to find a solution.

database

Schema::create('notifications', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->string('type');
            $table->morphs('notifiable');
            $table->text('data');
            $table->timestamp('read_at')->nullable();
            $table->timestamps();
        });

save the notify:

//store notify for user in database
        $postn = Post::find($request->post_id);

        if($postn->user_id != Auth::id()) {
            User::find($postn->user->id)->notify(new NotifyComment($comment));
        }

Example entry in the database:

id: 03b0ede2-395f-408f-88f8-ab5e6c8e5023
type: App\Notifications\NotifyComment
notifiable_id: 2
notofiable_type: App\User
data: {"comment":{"body":"testcomment","user_id":1,"post_id":9,"privacy":3,"updated_at":"2018-10-16 19:02:31","created_at":"2018-10-16 19:02:31","id":74,"formatted_time":"1 second ago"},"user":{"id":1,"name":"name","team_id":1,"invited_from_id":null,"username":"testuser","email":"[email protected]","phone":null,"avatar":"/1/avatar/1537189269.jpg","slug":"testuser","birthday":"1980-01-30 00:00:00","gender":1,"created_at":"2018-09-16 19:20:16","updated_at":"2018-09-17 13:01:09","active":1,"activation_token":null},"post":{"id":9,"body":"testpost","titel":null,"articleText":null,"image":null,"image_thumbnail":null,"type":1,"status":1,"privacy":1,"user_id":2,"created_at":"2018-10-15 15:43:50","updated_at":"2018-10-15 15:43:50"},"profile":{"id":1,"user_id":1,"about":null,"webpage":null,"cover":"public/1/cover/jClNRrMhbfdQge205j0SucQSG6ZPGMkvHmPFvwVC.png","thumbnail":"/1/cover/thumb/1537353515thumbnail.png","privacy":3,"created_at":"2018-09-16 19:20:16","updated_at":"2018-09-19 10:38:35"}}

My next problem with the notifications is: How can I delete the notifications from the comment if the comment is deleted?

Please or to participate in this conversation.