diff --git a/src/Notification/Blueprint/BlueprintInterface.php b/src/Notification/Blueprint/BlueprintInterface.php index 68b1d8553..422ba1784 100644 --- a/src/Notification/Blueprint/BlueprintInterface.php +++ b/src/Notification/Blueprint/BlueprintInterface.php @@ -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. * diff --git a/src/Notification/Blueprint/DiscussionRenamedBlueprint.php b/src/Notification/Blueprint/DiscussionRenamedBlueprint.php index 181d298ad..7e569490a 100644 --- a/src/Notification/Blueprint/DiscussionRenamedBlueprint.php +++ b/src/Notification/Blueprint/DiscussionRenamedBlueprint.php @@ -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} */ diff --git a/src/Notification/BlueprintBC.php b/src/Notification/BlueprintBC.php new file mode 100644 index 000000000..b3a4209d0 --- /dev/null +++ b/src/Notification/BlueprintBC.php @@ -0,0 +1,36 @@ +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 + ]; + } + } +} diff --git a/src/Notification/Event/Sending.php b/src/Notification/Event/Sending.php index 4484cddc2..de2e1ca6e 100644 --- a/src/Notification/Event/Sending.php +++ b/src/Notification/Event/Sending.php @@ -23,7 +23,7 @@ class Sending /** * The users that the notification will be sent to. * - * @var array + * @var array|int[] */ public $users; diff --git a/src/Notification/Job/SendEmailNotificationJob.php b/src/Notification/Job/SendEmailNotificationJob.php new file mode 100644 index 000000000..68bffeb17 --- /dev/null +++ b/src/Notification/Job/SendEmailNotificationJob.php @@ -0,0 +1,39 @@ +blueprint = $blueprint; + $this->recipient = $recipient; + } + + public function handle(NotificationMailer $mailer) + { + $mailer->send($this->blueprint, $this->recipient); + } +} diff --git a/src/Notification/Job/SendNotificationsJob.php b/src/Notification/Job/SendNotificationsJob.php new file mode 100644 index 000000000..93a08b8fd --- /dev/null +++ b/src/Notification/Job/SendNotificationsJob.php @@ -0,0 +1,55 @@ +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) + ); + } +} diff --git a/src/Notification/NotificationSyncer.php b/src/Notification/NotificationSyncer.php index 12a987fec..9ae014588 100644 --- a/src/Notification/NotificationSyncer.php +++ b/src/Notification/NotificationSyncer.php @@ -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 - ]; - } } diff --git a/src/Queue/AbstractJob.php b/src/Queue/AbstractJob.php new file mode 100644 index 000000000..6ec8235a8 --- /dev/null +++ b/src/Queue/AbstractJob.php @@ -0,0 +1,22 @@ +