Skip to content

Commit

Permalink
Can suppress Turbo Stream broadcasts on models
Browse files Browse the repository at this point in the history
  • Loading branch information
tonysm committed Nov 5, 2023
1 parent d6ebd42 commit 06f32e5
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/Broadcasting/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function broadcastRefresh(Channel|Model|Collection|array|string $channel
action: 'refresh',
channel: $channel,
attributes: array_filter(['request-id' => $requestId = Turbo::currentRequestId()]),
)->cancelIf(fn (PendingBroadcast $broadcast) => (
)->lazyCancelIf(fn (PendingBroadcast $broadcast) => (
$this->shouldLimitPageRefreshesOn($broadcast->channels, $requestId)
));
}
Expand Down
33 changes: 31 additions & 2 deletions src/Broadcasting/PendingBroadcast.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ class PendingBroadcast
*/
protected $recorder = null;

/**
* These cancel callbacks will run right before the broadcasting is fired on __destruct.
*
* @var array<callable>
*/
protected array $deferredCancelCallbacks = [];

public function __construct(array $channels, string $action, Rendering $rendering, string $target = null, string $targets = null, array $attributes = [])
{
$this->action = $action;
Expand Down Expand Up @@ -168,7 +175,14 @@ public function cancel()

public function cancelIf($condition)
{
$this->wasCancelled = boolval(value($condition, $this));
$this->wasCancelled = $this->wasCancelled || boolval(value($condition, $this));

return $this;
}

public function lazyCancelIf(callable $condition)
{
$this->deferredCancelCallbacks[] = $condition;

return $this;
}
Expand Down Expand Up @@ -200,7 +214,7 @@ public function render(): HtmlString

public function __destruct()
{
if ($this->wasCancelled) {
if ($this->shouldBeCancelled()) {
return;
}

Expand Down Expand Up @@ -231,6 +245,21 @@ public function __destruct()
);
}

protected function shouldBeCancelled(): bool
{
if ($this->wasCancelled) {
return true;
}

foreach ($this->deferredCancelCallbacks as $condition) {
if (value($condition, $this)) {
return true;
}
}

return false;
}

protected function normalizeChannels($channel, $channelClass)
{
if ($channel instanceof Channel) {
Expand Down
36 changes: 34 additions & 2 deletions src/Models/Broadcasts.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,42 @@
*/
trait Broadcasts
{
protected static $ignoreTurboStreamBroadcastsOn = [];

public static function bootBroadcasts()
{
static::observe(new ModelObserver());
}

public static function withoutTurboStreamBroadcasts(callable $callback)
{
return static::withoutTurboStreamBroadcastsOn([static::class], $callback);
}

public static function withoutTurboStreamBroadcastsOn(array $models, callable $callback)
{
static::$ignoreTurboStreamBroadcastsOn = array_values(array_merge(static::$ignoreTurboStreamBroadcastsOn, $models));

try {
return $callback();
} finally {
static::$ignoreTurboStreamBroadcastsOn = array_values(array_diff(static::$ignoreTurboStreamBroadcastsOn, $models));
}
}

public static function isIgnoringTurboStreamBroadcasts($class = null)
{
$class = $class ?: static::class;

foreach (static::$ignoreTurboStreamBroadcastsOn as $ignoredClass) {
if ($class === $ignoredClass || is_subclass_of($class, $ignoredClass)) {
return true;
}
}

return false;
}

public function broadcastAppend(): PendingBroadcast
{
return $this->broadcastAppendTo(
Expand Down Expand Up @@ -130,7 +161,8 @@ public function broadcastRemoveTo($streamable): PendingBroadcast

public function broadcastRefreshTo($streamable): PendingBroadcast
{
return TurboStream::broadcastRefresh($this->toChannels(Collection::wrap($streamable)));
return TurboStream::broadcastRefresh($this->toChannels(Collection::wrap($streamable)))
->cancelIf(fn () => static::isIgnoringTurboStreamBroadcasts());
}

public function asTurboStreamBroadcastingChannel()
Expand All @@ -146,7 +178,7 @@ protected function broadcastActionTo($streamables, string $action, Rendering $re
targets: null,
channel: $this->toChannels(Collection::wrap($streamables)),
content: $rendering,
);
)->cancelIf(static::isIgnoringTurboStreamBroadcasts());
}

protected function broadcastDefaultStreamablesForRefresh()
Expand Down
62 changes: 62 additions & 0 deletions tests/Models/BroadcastsModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -655,4 +655,66 @@ public function auto_broadcast_refreshes_to_on_create_debouncing()
return true;
}, times: 1);
}

/** @test */
public function can_disable_manual_broadcasts()
{
/** @var Board $board */
$board = Board::withoutEvents(fn () => BoardFactory::new()->create());

TurboStream::assertNothingWasBroadcasted();

Board::withoutTurboStreamBroadcasts(fn () => (
$board->broadcastAppend()
));

TurboStream::assertNothingWasBroadcasted();

$board->broadcastAppend();

TurboStream::assertBroadcastedTimes(function (PendingBroadcast $broadcast) use ($board) {
$this->assertCount(1, $broadcast->channels);
$this->assertEquals('private-boards', $broadcast->channels[0]->name);
$this->assertEquals('boards', $broadcast->target);
$this->assertEquals('append', $broadcast->action);
$this->assertEquals('boards._board', $broadcast->partialView);
$this->assertEquals(['board' => $board], $broadcast->partialData);
$this->assertNull($broadcast->targets);

return true;
}, times: 1);
}

/** @test */
public function can_disable_auto_broadcasts()
{
$board = Board::withoutEvents(fn () => BoardFactory::new()->create())->fresh();

TurboStream::assertNothingWasBroadcasted();

Task::withoutTurboStreamBroadcasts(fn () => (
TaskFactory::new()
->for($board)
->create()
));

TurboStream::assertNothingWasBroadcasted();

TaskFactory::new()
->for($board)
->create();

TurboStream::assertBroadcastedTimes(function (PendingBroadcast $broadcast) use ($board) {
$this->assertCount(1, $broadcast->channels);
$this->assertSame('private-'.$board->broadcastChannel(), $broadcast->channels[0]->name);
$this->assertEquals('refresh', $broadcast->action);
$this->assertNull($broadcast->target);
$this->assertNull($broadcast->partialView);
$this->assertEmpty($broadcast->partialData);
$this->assertNull($broadcast->targets);
$this->assertEmpty($broadcast->attributes);

return true;
}, times: 1);
}
}

0 comments on commit 06f32e5

Please sign in to comment.