There comes a time in all of our lives where we want to deal with a heap of notifications to present to a user on a dashboard.
This seems like it could be a bit of a trouble at first approach, but I have a possible pattern to suggest to attack the issue head on.
I want to give a special thanks to Chris Fidao for helping me out with proofing this article and making it more awesome! If you are interested in learning more about pragmatic Laravel application design, check out his book
Note: You should be relatively familiar with Illuminate/Eloquent and the different relationship types as well as some experience with Inheritance
The Goal
The goal is to eloquently (ha, puns!) display all notifications for a user when they login into their account.
Additionally, there may be different types of notifications. For instance, there might be a notification for a new message and a notification for an event. These may have different formats.
The Plan of Attack
To attack this, I want a single call to something like Notification::allForUser($userId)
instead of having to do some crazy weird queries that would have to be added each time I create a new type of notification.
Also, notifications should be pretty similar: we don't want a user approval notifications to look too drastically different from our message notifications.
For this exercise, we'll copy Facebook a bit an have two fake notification types: Messages and Posts.
We'll do the following:
- Create a base Eloquent model we can use for all future notification types
- Implement some sample Notification models for messages and posts
- Use a View composer to attach notifications to each dashboard view
Easy Notification Grabbing
To create an easy way to get all the notifications for a user, we could go ahead and create a repository. But, this would cause some issues whenever we wanted to create a new Notification for games or whatever new Notification comes into scope. Alternatively, we could store all of our notification information in a JSON document or serialized format in our database, but this would be a bit awkward to work with and a single Notification class would have to know about every type of notification that ever would occur in our application.
Instead, let's take advantage of Eloquent's MorphToOne and create a parent Notification
model that relates notifications to our user.
Then we can just use the magic methods to pass all interactions to our child notification.
class Notification extends Eloquent
{
public function notification()
{
return $this->morphTo();
}
public function user()
{
return $this->belongsTo('User');
}
public function __get($key)
{
// We need to check if there is a valid
// attribute on this model before passing things off
return parent::__get($key) ?: $this->notification->__get($key);
}
}
A Standardized Contract
When thinking of notifications for my dashboard, I think that I'd like a UI with a description, approve button, and a decline button. I don't want my views to do too much, so I'm going to go ahead and create a little abstract class to make sure I follow the contract to make this easier to build up. And we will also declare our parent notification.
namespace Notifications;
abstract class NotifiableModel extends \Eloquent
{
// Will return the message we want
// to show in our notifications
abstract public function getMessageAttribute();
// Will return a link to an route
// that handles positive reaction to the notification
abstract public function getAcceptLinkAttribute();
// Will return a link to an route
// that handles negative reaction to the notification
abstract public function getDeclineLinkAttribute();
// Gives us the ability to reference
// back to the parent Notification model
public function notification()
{
return $this->morphOne('Notification');
}
}
Our Notification Classes
Let's look at our PostNotification, I want it to say something like "Josh posted on your wall" and it needs to have a reference to the post that it is alerting us about. A MessageNotification model would look very similar to this with of course some changes to fit messages instead of posts.
namespace Notifications;
class PostNotification extends NotifiableModel
{
// I choose to namespace my notification
// tables to mimic the resources that they point to
protected $table = 'notifications_posts';
public function getMessageAttribute()
{
// Assuming that our post can give us
// a username
$username = $this->post->sender->name;
return "{$username} posted on your wall";
}
public function getAcceptLinkAttribute()
{
// This route will handle deleting the
// notification and redirecting us to the post
$route = 'notifications.posts.accept';
$title = 'View Post';
$parameters = array(
$this->getAttribute('id')
);
return link_to_route($route, $title, $parameters);
}
public function getDeclineLinkAttribute()
{
// This route will handle deleting the
// notification and redirecting us back
// to where we were before
$route = 'notifications.posts.decline';
$title = 'Ignore';
$parameters = array(
$this->getAttribute('id')
);
return link_to_route($route, $title, $parameters);
}
// Relationship for the post we want to alert
public function post()
{
return $this->belongsTo('Post');
}
}
Our Schema
Though this is just a quick outline of a pattern of how to handle some notifications, I wanted to touch on what the Schema for this example would look like.
notifications
id - integer
notification_id - integer
notification_type - string
timestamps
notifications_posts
id - integer
post_id - integer
timestamps
notifications_messages
id - integer
message_id - integer
timestamps
Our Admin Dashboard View
So, we want our notifications available for all of our views that include our notificationCenter
view so we will use a view composer to make sure we never forget to send our view the notifications for a user.
View::composer('notificationCenter', function($view)
{
$user = Auth::user();
$notifications = Notification::where('user_id', $user->id)->get();
$view->with('notifications', $notifications);
});
And now our notificationCenter
view partial could look something like this:
<ul>
<legend>Notifications</legend>
<?php foreach ($notifications as $notification) : ?>
<li>
<span class="message">
<?= $notification->message ?>
</span>
<div class="btn primary">
<?= $notification->acceptLink ?>
</div>
<div class="btn info">
<?= $notification->declineLink ?>
</div>
</li>
<?php endforeach ?>
</ul>
What Did We Do?
A ton is going on here, and there is still some stuff that wasn't covered. We looked at creating a pattern to handle working with notifications for our admin panels.
- We created a parent Notification model that allows us to grab all of our different kinds of notifications from one location
- We created a contract for our Notification types by using an abstract class that we called
NotifiableModel
- We created an implementation of a
PostNotification
- Finally we used a View Composer to make our
notificationCenter
view much easier to use by always fetching the logged in user's notifications.
We didn't look at creating our notifications and attaching them to the parent Notification container, but this is similar to any Polymorphic-One-To-One relationship.
We also didn't look at the implementations of the notifications.posts.accept
and notifications.posts.decline
routes, although this starts to get rather application specific.
In the case of our post example, the accept
route would delete the notification and direct to the post, while the decline
route would simply delete the notification.