Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] More Convenient Model Broadcasting #37491

Merged
merged 13 commits into from
Jun 1, 2021
8 changes: 7 additions & 1 deletion src/Illuminate/Broadcasting/BroadcastEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,14 @@ public function handle(Broadcaster $broadcaster)
$name = method_exists($this->event, 'broadcastAs')
? $this->event->broadcastAs() : get_class($this->event);

$channels = Arr::wrap($this->event->broadcastOn());

if (empty($channels)) {
return;
}

$broadcaster->broadcast(
Arr::wrap($this->event->broadcastOn()), $name,
$channels, $name,
$this->getPayloadFromEvent($this->event)
);
}
Expand Down
9 changes: 8 additions & 1 deletion src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Exception;
use Illuminate\Container\Container;
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
use Illuminate\Contracts\Routing\BindingRegistrar;
use Illuminate\Contracts\Routing\UrlRoutable;
use Illuminate\Support\Arr;
Expand Down Expand Up @@ -40,13 +41,19 @@ abstract class Broadcaster implements BroadcasterContract
/**
* Register a channel authenticator.
*
* @param string $channel
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $channel
* @param callable|string $callback
* @param array $options
* @return $this
*/
public function channel($channel, $callback, $options = [])
{
if ($channel instanceof HasBroadcastChannel) {
$channel = $channel->broadcastChannelRoute();
} elseif (is_string($channel) && class_exists($channel) && is_a($channel, HasBroadcastChannel::class, true)) {
$channel = (new $channel)->broadcastChannelRoute();
}

$this->channels[$channel] = $callback;

$this->channelOptions[$channel] = $options;
Expand Down
6 changes: 4 additions & 2 deletions src/Illuminate/Broadcasting/Channel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Illuminate\Broadcasting;

use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;

class Channel
{
/**
Expand All @@ -14,12 +16,12 @@ class Channel
/**
* Create a new channel instance.
*
* @param string $name
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $name
* @return void
*/
public function __construct($name)
{
$this->name = $name;
$this->name = $name instanceof HasBroadcastChannel ? $name->broadcastChannel() : $name;
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/Illuminate/Broadcasting/PrivateChannel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@

namespace Illuminate\Broadcasting;

use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;

class PrivateChannel extends Channel
{
/**
* Create a new channel instance.
*
* @param string $name
* @param \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string $name
* @return void
*/
public function __construct($name)
{
$name = $name instanceof HasBroadcastChannel ? $name->broadcastChannel() : $name;

parent::__construct('private-'.$name);
}
}
20 changes: 20 additions & 0 deletions src/Illuminate/Contracts/Broadcasting/HasBroadcastChannel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Illuminate\Contracts\Broadcasting;

interface HasBroadcastChannel
{
/**
* Get the broadcast channel route definition that is associated with the given entity.
*
* @return string
*/
public function broadcastChannelRoute();

/**
* Get the broadcast channel name that is associated with the given entity.
*
* @return string
*/
public function broadcastChannel();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Illuminate\Database\Eloquent;

use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class BroadcastableModelEventOccurred implements ShouldBroadcast
{
use InteractsWithSockets, SerializesModels;

/**
* The model instance corresponding to the event.
*
* @var \Illuminate\Database\Eloquent\Model
*/
public $model;

/**
* The event name (created, updated, etc.).
*
* @var string
*/
protected $event;

/**
* Create a new event instance.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $event
* @return void
*/
public function __construct($model, $event)
{
$this->model = $model;
$this->event = $event;
}

/**
* The channels the event should broadcast on.
*
* @return array
*/
public function broadcastOn()
{
return collect($this->model->broadcastOn($this->event) ?: [])->map(function ($channel) {
return $channel instanceof Model ? new PrivateChannel($channel) : $channel;
})->all();
}

/**
* The name the event should broadcast as.
*
* @return string
*/
public function broadcastAs()
{
return class_basename($this->model).ucfirst($this->event);
}
}
132 changes: 132 additions & 0 deletions src/Illuminate/Database/Eloquent/BroadcastsEvents.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

namespace Illuminate\Database\Eloquent;

trait BroadcastsEvents
{
/**
* Boot the event broadcasting trait.
*
* @return void
*/
public static function bootBroadcastsEvents()
{
static::created(function ($model) {
$model->broadcastCreated();
});

static::updated(function ($model) {
$model->broadcastUpdated();
});

if (method_exists(static::class, 'bootSoftDeletes')) {
static::trashed(function ($model) {
$model->broadcastTrashed();
});

static::restored(function ($model) {
$model->broadcastRestored();
});
}

static::deleted(function ($model) {
$model->broadcastDeleted();
});
}

/**
* Broadcast that the model was created.
*
* @return \Illuminate\Broadcasting\PendingBroadcast
*/
public function broadcastCreated()
{
return $this->broadcastIfBroadcastChannelsExistForEvent(
$this->newBroadcastableModelEvent('created'), 'created'
);
}

/**
* Broadcast that the model was updated.
*
* @return \Illuminate\Broadcasting\PendingBroadcast
*/
public function broadcastUpdated()
{
return $this->broadcastIfBroadcastChannelsExistForEvent(
$this->newBroadcastableModelEvent('updated'), 'updated'
);
}

/**
* Broadcast that the model was trashed.
*
* @return \Illuminate\Broadcasting\PendingBroadcast
*/
public function broadcastTrashed()
{
return $this->broadcastIfBroadcastChannelsExistForEvent(
$this->newBroadcastableModelEvent('trashed'), 'trashed'
);
}

/**
* Broadcast that the model was restored.
*
* @return \Illuminate\Broadcasting\PendingBroadcast
*/
public function broadcastRestored()
{
return $this->broadcastIfBroadcastChannelsExistForEvent(
$this->newBroadcastableModelEvent('restored'), 'restored'
);
}

/**
* Broadcast that the model was deleted.
*
* @return \Illuminate\Broadcasting\PendingBroadcast
*/
public function broadcastDeleted()
{
return $this->broadcastIfBroadcastChannelsExistForEvent(
$this->newBroadcastableModelEvent('deleted'), 'deleted'
);
}

/**
* Broadcast the given event instance if channels are configured for the model event.
*
* @param mixed $instance
* @param string $event
* @return \Illuminate\Broadcasting\PendingBroadcast|null
*/
protected function broadcastIfBroadcastChannelsExistForEvent($instance, $event)
Copy link
Contributor

@joelbutcher joelbutcher May 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@taylorotwell rather than having this (relatively) long method name, what would you think to having a method shouldBroadcastEvent($event) and then using this in bootBroadcastsEvents? E.g.

    /**
  * Boot the event broadcasting trait.
  *
  * @return void
  */
    public static function bootBroadcastsEvents()
    {
        // code…

        static::created(function ($model) {
            if ($this->shouldBroadcastEvent(‘created’)) {
                $this->broadcastCreated();
            }
        });

        // more code…
    }

{
if (! empty($this->broadcastOn($event))) {
return broadcast($instance);
}
}

/**
* Get the channels that model events should broadcast on.
*
* @param string $event
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn($event)
{
return [$this];
}

/**
* Create a new broadcastable model event event.
*
* @param string $event
* @return mixed
*/
public function newBroadcastableModelEvent($event)
{
return new BroadcastableModelEventOccurred($this, $event);
}
}
23 changes: 22 additions & 1 deletion src/Illuminate/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Illuminate\Database\Eloquent;

use ArrayAccess;
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
use Illuminate\Contracts\Queue\QueueableCollection;
use Illuminate\Contracts\Queue\QueueableEntity;
use Illuminate\Contracts\Routing\UrlRoutable;
Expand All @@ -21,7 +22,7 @@
use JsonSerializable;
use LogicException;

abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
abstract class Model implements Arrayable, ArrayAccess, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
use Concerns\HasAttributes,
Concerns\HasEvents,
Expand Down Expand Up @@ -1842,6 +1843,26 @@ public static function preventsLazyLoading()
return static::$modelsShouldPreventLazyLoading;
}

/**
* Get the broadcast channel route definition that is associated with the given entity.
*
* @return string
*/
public function broadcastChannelRoute()
{
return str_replace('\\', '.', get_class($this)).'.{'.$this->getKeyName().'}';
taylorotwell marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Get the broadcast channel name that is associated with the given entity.
*
* @return string
*/
public function broadcastChannel()
{
return str_replace('\\', '.', get_class($this)).'.'.$this->getKey();
}

/**
* Dynamically retrieve attributes on the model.
*
Expand Down