Merge pull request #1931 from flarum/dk/1869-queue-notifications

Notifications into the queue
This commit is contained in:
Franz Liedke 2020-03-27 23:06:36 +01:00 committed by GitHub
commit f47a4879c4
8 changed files with 202 additions and 75 deletions

View File

@ -9,6 +9,9 @@
namespace Flarum\Notification\Blueprint;
use Flarum\Database\AbstractModel;
use Flarum\User\User;
/**
* A notification BlueprintInterface, when instantiated, represents a notification about
* something. The blueprint is used by the NotificationSyncer to commit the
@ -19,24 +22,35 @@ interface BlueprintInterface
/**
* Get the user that sent the notification.
*
* @return \Flarum\User\User|null
* @deprecated Will be removed for beta.14
* @return User|null
*/
public function getFromUser();
/**
* Get the model that is the subject of this activity.
*
* @return \Flarum\Database\AbstractModel|null
* @deprecated Will be removed for beta.14
* @return AbstractModel|null
*/
public function getSubject();
/**
* Get the data to be stored in the notification.
*
* @deprecated Will be removed for beta.14
* @return array|null
*/
public function getData();
/**
* Get the attributes that uniquely identify a notification, plus metadata.
* TODO: Uncomment this for beta.14.
*
* @return array
*/
//public function getAttributes(): array;
/**
* Get the serialized type of this activity.
*

View File

@ -51,6 +51,19 @@ class DiscussionRenamedBlueprint implements BlueprintInterface
return ['postNumber' => (int) $this->post->number];
}
/**
* {@inheritdoc}
*/
public function getAttributes(): array
{
return [
'type' => static::getType(),
'from_user_id' => $this->post->user ? $this->post->user->id : null,
'subject_id' => $this->post->discussion ? $this->post->discussion->id : null,
'data' => json_encode(['postNumber' => (int) $this->post->number]),
];
}
/**
* {@inheritdoc}
*/

View File

@ -0,0 +1,36 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Notification;
use Flarum\Notification\Blueprint\BlueprintInterface;
/**
* A backwards compatibility layer for notification blueprints.
*
* Will be removed for Beta 14 in favor of BlueprintInterface::getAttributes().
*
* @deprecated
*/
class BlueprintBC
{
public static function getAttributes(BlueprintInterface $blueprint): array
{
if (method_exists($blueprint, 'getAttributes')) {
return $blueprint->getAttributes();
} else {
return [
'type' => $blueprint::getType(),
'from_user_id' => ($fromUser = $blueprint->getFromUser()) ? $fromUser->id : null,
'subject_id' => ($subject = $blueprint->getSubject()) ? $subject->id : null,
'data' => ($data = $blueprint->getData()) ? json_encode($data) : null
];
}
}
}

View File

@ -23,7 +23,7 @@ class Sending
/**
* The users that the notification will be sent to.
*
* @var array
* @var array|int[]
*/
public $users;

View File

@ -0,0 +1,39 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Notification\Job;
use Flarum\Notification\MailableInterface;
use Flarum\Notification\NotificationMailer;
use Flarum\Queue\AbstractJob;
use Flarum\User\User;
class SendEmailNotificationJob extends AbstractJob
{
/**
* @var MailableInterface
*/
private $blueprint;
/**
* @var User
*/
private $recipient;
public function __construct(MailableInterface $blueprint, User $recipient)
{
$this->blueprint = $blueprint;
$this->recipient = $recipient;
}
public function handle(NotificationMailer $mailer)
{
$mailer->send($this->blueprint, $this->recipient);
}
}

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Notification\Job;
use Carbon\Carbon;
use Flarum\Notification\Blueprint\BlueprintInterface;
use Flarum\Notification\BlueprintBC;
use Flarum\Notification\Event\Sending;
use Flarum\Notification\Notification;
use Flarum\Queue\AbstractJob;
use Flarum\User\User;
class SendNotificationsJob extends AbstractJob
{
/**
* @var BlueprintInterface
*/
private $blueprint;
/**
* @var User[]
*/
private $recipients;
public function __construct(BlueprintInterface $blueprint, array $recipients = [])
{
$this->blueprint = $blueprint;
$this->recipients = $recipients;
}
public function handle()
{
$now = Carbon::now('utc')->toDateTimeString();
event(new Sending($this->blueprint, $this->recipients));
$attributes = BlueprintBC::getAttributes($this->blueprint);
Notification::insert(
array_map(function (User $user) use ($attributes, $now) {
return $attributes + [
'user_id' => $user->id,
'created_at' => $now
];
}, $this->recipients)
);
}
}

View File

@ -9,10 +9,11 @@
namespace Flarum\Notification;
use Carbon\Carbon;
use Flarum\Notification\Blueprint\BlueprintInterface;
use Flarum\Notification\Event\Sending;
use Flarum\Notification\Job\SendEmailNotificationJob;
use Flarum\Notification\Job\SendNotificationsJob;
use Flarum\User\User;
use Illuminate\Contracts\Queue\Queue;
/**
* The Notification Syncer commits notification blueprints to the database, and
@ -37,25 +38,13 @@ class NotificationSyncer
protected static $sentTo = [];
/**
* @var NotificationRepository
* @var Queue
*/
protected $notifications;
protected $queue;
/**
* @var NotificationMailer
*/
protected $mailer;
/**
* @param NotificationRepository $notifications
* @param NotificationMailer $mailer
*/
public function __construct(
NotificationRepository $notifications,
NotificationMailer $mailer
) {
$this->notifications = $notifications;
$this->mailer = $mailer;
public function __construct(Queue $queue)
{
$this->queue = $queue;
}
/**
@ -69,7 +58,7 @@ class NotificationSyncer
*/
public function sync(Blueprint\BlueprintInterface $blueprint, array $users)
{
$attributes = $this->getAttributes($blueprint);
$attributes = BlueprintBC::getAttributes($blueprint);
// Find all existing notification records in the database matching this
// blueprint. We will begin by assuming that they all need to be
@ -87,7 +76,7 @@ class NotificationSyncer
continue;
}
$existing = $toDelete->first(function ($notification, $i) use ($user) {
$existing = $toDelete->first(function ($notification) use ($user) {
return $notification->user_id === $user->id;
});
@ -113,9 +102,14 @@ class NotificationSyncer
// Create a notification record, and send an email, for all users
// receiving this notification for the first time (we know because they
// didn't have a record in the database).
// didn't have a record in the database). As both operations can be
// intensive on resources (database and mail server), we queue them.
if (count($newRecipients)) {
$this->sendNotifications($blueprint, $newRecipients);
$this->queue->push(new SendNotificationsJob($blueprint, $newRecipients));
}
if ($blueprint instanceof MailableInterface) {
$this->mailNotifications($blueprint, $newRecipients);
}
}
@ -127,7 +121,7 @@ class NotificationSyncer
*/
public function delete(BlueprintInterface $blueprint)
{
Notification::where($this->getAttributes($blueprint))->update(['is_deleted' => true]);
Notification::where(BlueprintBC::getAttributes($blueprint))->update(['is_deleted' => true]);
}
/**
@ -138,7 +132,7 @@ class NotificationSyncer
*/
public function restore(BlueprintInterface $blueprint)
{
Notification::where($this->getAttributes($blueprint))->update(['is_deleted' => false]);
Notification::where(BlueprintBC::getAttributes($blueprint))->update(['is_deleted' => false]);
}
/**
@ -158,35 +152,6 @@ class NotificationSyncer
static::$onePerUser = false;
}
/**
* Create a notification record and send an email (depending on user
* preference) from a blueprint to a list of recipients.
*
* @param \Flarum\Notification\Blueprint\BlueprintInterface $blueprint
* @param User[] $recipients
*/
protected function sendNotifications(Blueprint\BlueprintInterface $blueprint, array $recipients)
{
$now = Carbon::now('utc')->toDateTimeString();
event(new Sending($blueprint, $recipients));
$attributes = $this->getAttributes($blueprint);
Notification::insert(
array_map(function (User $user) use ($attributes, $now) {
return $attributes + [
'user_id' => $user->id,
'created_at' => $now
];
}, $recipients)
);
if ($blueprint instanceof MailableInterface) {
$this->mailNotifications($blueprint, $recipients);
}
}
/**
* Mail a notification to a list of users.
*
@ -197,7 +162,7 @@ class NotificationSyncer
{
foreach ($recipients as $user) {
if ($user->shouldEmail($blueprint::getType())) {
$this->mailer->send($blueprint, $user);
$this->queue->push(new SendEmailNotificationJob($blueprint, $user));
}
}
}
@ -212,21 +177,4 @@ class NotificationSyncer
{
Notification::whereIn('id', $ids)->update(['is_deleted' => $isDeleted]);
}
/**
* Construct an array of attributes to be stored in a notification record in
* the database, given a notification blueprint.
*
* @param \Flarum\Notification\Blueprint\BlueprintInterface $blueprint
* @return array
*/
protected function getAttributes(Blueprint\BlueprintInterface $blueprint)
{
return [
'type' => $blueprint::getType(),
'from_user_id' => ($fromUser = $blueprint->getFromUser()) ? $fromUser->id : null,
'subject_id' => ($subject = $blueprint->getSubject()) ? $subject->id : null,
'data' => ($data = $blueprint->getData()) ? json_encode($data) : null
];
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Queue;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class AbstractJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
use SerializesModels;
}