Rework notifications architecture

- The recipient(s) are the concern of the notifier/sender, not the
notification itself
- Allow “retraction” of notifications (e.g. if a discussion is
stickied, but then it is unstickied)
- Misc. cleanup
This commit is contained in:
Toby Zerner 2015-05-14 22:39:57 +09:30
parent 6517b1ec3e
commit 3925e5892c
11 changed files with 73 additions and 78 deletions

View File

@ -27,10 +27,16 @@ class DiscussionRenamedNotifier
{ {
$post = $this->createPost($event); $post = $this->createPost($event);
$event->discussion->addPost($post); $post = $event->discussion->addPost($post);
if ($event->discussion->start_user_id !== $event->user->id) { if ($event->discussion->start_user_id !== $event->user->id) {
$this->sendNotification($event, $post); $notification = new DiscussionRenamedNotification($event->discussion, $post->user, $post);
if ($post->exists) {
$this->notifier->send($notification, [$post->discussion->startUser]);
} else {
$this->notifier->retract($notification);
}
} }
} }
@ -43,16 +49,4 @@ class DiscussionRenamedNotifier
$event->discussion->title $event->discussion->title
); );
} }
protected function sendNotification(DiscussionWasRenamed $event, DiscussionRenamedPost $post)
{
$notification = new DiscussionRenamedNotification(
$event->discussion->startUser,
$event->user,
$post,
$event->oldTitle
);
$this->notifier->send($notification);
}
} }

View File

@ -60,7 +60,7 @@ class Notification extends Model
*/ */
public function setDataAttribute($value) public function setDataAttribute($value)
{ {
$this->attributes['data'] = json_encode($value); $this->attributes['data'] = $value ? json_encode($value) : null;
} }
/** /**

View File

@ -1,6 +1,7 @@
<?php namespace Flarum\Core\Notifications; <?php namespace Flarum\Core\Notifications;
use Flarum\Core\Notifications\Types\Notification; use Flarum\Core\Notifications\Types\Notification;
use Flarum\Core\Notifications\Senders\RetractableSender;
use Flarum\Core\Models\Notification as NotificationModel; use Flarum\Core\Models\Notification as NotificationModel;
use Flarum\Core\Models\User; use Flarum\Core\Models\User;
use Illuminate\Container\Container; use Illuminate\Container\Container;
@ -18,12 +19,28 @@ class Notifier
$this->container = $container; $this->container = $container;
} }
public function send(Notification $notification) public function send(Notification $notification, array $users)
{ {
foreach ($this->methods as $method => $sender) { foreach ($this->methods as $method => $sender) {
$sender = $this->container->make($sender); $sender = $this->container->make($sender);
if ($notification->getRecipient()->shouldNotify($notification::getType(), $method) && $sender::compatibleWith($notification)) {
$sender->send($notification); if ($sender::compatibleWith($notification)) {
foreach ($users as $user) {
if ($user->shouldNotify($notification::getType(), $method)) {
$sender->send($notification, $user);
}
}
}
}
}
public function retract(Notification $notification)
{
foreach ($this->methods as $method => $sender) {
$sender = $this->container->make($sender);
if ($sender instanceof RetractableSender && $sender::compatibleWith($notification)) {
$sender->retract($notification);
} }
} }
} }

View File

@ -2,22 +2,31 @@
use Flarum\Core\Notifications\Types\Notification; use Flarum\Core\Notifications\Types\Notification;
use Flarum\Core\Models\Notification as NotificationModel; use Flarum\Core\Models\Notification as NotificationModel;
use Flarum\Core\Models\User;
use ReflectionClass; use ReflectionClass;
class NotificationAlerter implements NotificationSender class NotificationAlerter implements NotificationSender, RetractableSender
{ {
public function send(Notification $notification) public function send(Notification $notification, User $user)
{ {
$model = NotificationModel::alert( $model = NotificationModel::alert(
$notification->getRecipient()->id, $user->id,
$notification::getType(), $notification::getType(),
$notification->getSender()->id, $notification->getSender()->id,
$notification->getSubject()->id, $notification->getSubject()->id,
$notification->getAlertData() $notification->getAlertData()
); );
$model->save(); $model->save();
} }
public function retract(Notification $notification)
{
$models = NotificationModel::where('type', $notification::getType())
->where('subject_id', $notification->getSubject()->id)
->delete();
}
public static function compatibleWith($className) public static function compatibleWith($className)
{ {
return (new ReflectionClass($className))->implementsInterface('Flarum\Core\Notifications\Types\AlertableNotification'); return (new ReflectionClass($className))->implementsInterface('Flarum\Core\Notifications\Types\AlertableNotification');

View File

@ -1,6 +1,7 @@
<?php namespace Flarum\Core\Notifications\Senders; <?php namespace Flarum\Core\Notifications\Senders;
use Flarum\Core\Notifications\Types\Notification; use Flarum\Core\Notifications\Types\Notification;
use Flarum\Core\Models\User;
use Flarum\Core\Models\Forum; use Flarum\Core\Models\Forum;
use Illuminate\Mail\Mailer; use Illuminate\Mail\Mailer;
use ReflectionClass; use ReflectionClass;
@ -13,10 +14,9 @@ class NotificationEmailer implements NotificationSender
$this->forum = $forum; $this->forum = $forum;
} }
public function send(Notification $notification) public function send(Notification $notification, User $user)
{ {
$this->mailer->send($notification->getEmailView(), ['notification' => $notification], function ($message) use ($notification) { $this->mailer->send($notification->getEmailView(), ['notification' => $notification], function ($message) use ($notification, $recipient) {
$recipient = $notification->getRecipient();
$message->to($recipient->email, $recipient->username) $message->to($recipient->email, $recipient->username)
->subject('['.$this->forum->title.'] '.$notification->getEmailSubject()); ->subject('['.$this->forum->title.'] '.$notification->getEmailSubject());
}); });

View File

@ -1,10 +1,11 @@
<?php namespace Flarum\Core\Notifications\Senders; <?php namespace Flarum\Core\Notifications\Senders;
use Flarum\Core\Notifications\Types\Notification; use Flarum\Core\Notifications\Types\Notification;
use Flarum\Core\Models\User;
interface NotificationSender interface NotificationSender
{ {
public function send(Notification $notification); public function send(Notification $notification, User $user);
public static function compatibleWith($class); public static function compatibleWith($class);
} }

View File

@ -0,0 +1,8 @@
<?php namespace Flarum\Core\Notifications\Senders;
use Flarum\Core\Notifications\Types\Notification;
interface RetractableSender
{
public function retract(Notification $notification);
}

View File

@ -1,8 +0,0 @@
<?php namespace Flarum\Core\Notifications\Types;
interface AlertableNotification
{
public function getAlertData();
public static function getType();
}

View File

@ -1,33 +1,37 @@
<?php namespace Flarum\Core\Notifications\Types; <?php namespace Flarum\Core\Notifications\Types;
use Flarum\Core\Models\User; use Flarum\Core\Models\User;
use Flarum\Core\Models\Discussion;
use Flarum\Core\Models\DiscussionRenamedPost; use Flarum\Core\Models\DiscussionRenamedPost;
class DiscussionRenamedNotification extends Notification implements AlertableNotification class DiscussionRenamedNotification extends Notification implements AlertableNotification
{ {
public $post; protected $discussion;
public $oldTitle; protected $sender;
public function __construct(User $recipient, User $sender, DiscussionRenamedPost $post, $oldTitle) protected $post;
public function __construct(Discussion $discussion, User $sender, DiscussionRenamedPost $post = null)
{ {
$this->discussion = $discussion;
$this->sender = $sender;
$this->post = $post; $this->post = $post;
$this->oldTitle = $oldTitle;
parent::__construct($recipient, $sender);
} }
public function getSubject() public function getSubject()
{ {
return $this->post->discussion; return $this->discussion;
}
public function getSender()
{
return $this->sender;
} }
public function getAlertData() public function getAlertData()
{ {
return [ return ['postNumber' => $this->post->number];
'number' => $this->post->number,
'oldTitle' => $this->oldTitle
];
} }
public static function getType() public static function getType()

View File

@ -1,36 +1,6 @@
<?php namespace Flarum\Core\Notifications\Types; <?php namespace Flarum\Core\Notifications\Types;
use Flarum\Core\Models\User;
abstract class Notification abstract class Notification
{ {
protected $recipient; abstract public static function getType();
protected $sender;
public function __construct(User $recipient, User $sender)
{
$this->recipient = $recipient;
$this->sender = $sender;
}
public function getRecipient()
{
return $this->recipient;
}
public function getSender()
{
return $this->sender;
}
public static function getType()
{
return null;
}
public static function getSubjectModel()
{
return null;
}
} }

View File

@ -5,15 +5,15 @@ use DB;
class EloquentNotificationRepository implements NotificationRepositoryInterface class EloquentNotificationRepository implements NotificationRepositoryInterface
{ {
public function findByUser($userId, $count = null, $start = 0) public function findByUser($userId, $limit = null, $offset = 0)
{ {
$primaries = Notification::select(DB::raw('MAX(id) AS id'), DB::raw('SUM(is_read = 0) AS unread_count')) $primaries = Notification::select(DB::raw('MAX(id) AS id'), DB::raw('SUM(is_read = 0) AS unread_count'))
->where('user_id', $userId) ->where('user_id', $userId)
->whereIn('type', array_keys(Notification::getTypes())) ->whereIn('type', array_keys(Notification::getTypes()))
->groupBy('type', 'subject_id') ->groupBy('type', 'subject_id')
->orderBy('time', 'desc') ->orderBy('time', 'desc')
->skip($start) ->skip($offset)
->take($count); ->take($limit);
return Notification::with('subject') return Notification::with('subject')
->select('notifications.*', 'p.unread_count') ->select('notifications.*', 'p.unread_count')