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

[11.x] Adds anonymous broadcasting #51082

Merged
merged 9 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions src/Illuminate/Broadcasting/AnonymousEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

namespace Illuminate\Broadcasting;

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Support\Arr;

class AnonymousEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithBroadcasting, InteractsWithSockets;

/**
* The connection the event should be broadcast on.
*/
protected ?string $connection = null;

/**
* The name the event should be broadcast as.
*/
protected ?string $name = null;

/**
* The payload the event should be broadcast with.
*/
protected array $payload = [];

/**
* Should the broadcast include the current user.
*/
protected bool $includeCurrentUser = true;

/**
* Indicates if the event should be broadcast synchronously.
*/
protected bool $shouldBroadcastNow = false;

/**
* Create a new anonymous broadcastable event instance.
*
* @return void
*/
public function __construct(protected Channel|array|string $channels)
{
$this->channels = Arr::wrap($channels);
}

/**
* Set the connection the event should be broadcast on.
*/
public function via(string $connection): static
{
$this->connection = $connection;

return $this;
}

/**
* Set the name the event should be broadcast as.
*/
public function as(string $name): static
{
$this->name = $name;

return $this;
}

/**
* Set the payload the event should be broadcast with.
*/
public function with(Arrayable|array $payload): static
{
$this->payload = $payload instanceof Arrayable
? $payload->toArray()
: collect($payload)->map(
fn ($p) => $p instanceof Arrayable ? $p->toArray() : $p
)->all();

return $this;
}

/**
* Broadcast the event to everyone except the current user.
*/
public function toOthers(): static
{
$this->includeCurrentUser = false;

return $this;
}

/**
* Broadcast the event.
*/
public function sendNow(): void
{
$this->shouldBroadcastNow = true;

$this->send();
}

/**
* Broadcast the event.
*/
public function send(): void
{
$broadcast = broadcast($this)->via($this->connection);

if (! $this->includeCurrentUser) {
$broadcast->toOthers();
}
}

/**
* Get the name the event should broadcast as.
*/
public function broadcastAs(): string
{
return $this->name ?: class_basename($this);

return $this;
}

/**
* Get the payload the event should broadcast with.
*
* @return array<string, mixed>
*/
public function broadcastWith(): array
{
return $this->payload;
}

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

/**
* Determine if the event should be broadcast synchronously.
*/
public function shouldBroadcastNow(): bool
{
return $this->shouldBroadcastNow;
}
}
24 changes: 24 additions & 0 deletions src/Illuminate/Broadcasting/BroadcastManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,30 @@ public function socket($request = null)
return $request->header('X-Socket-ID');
}

/**
* Begin sending an anonymous broadcast to the given channels.
*/
public function on(Channel|string|array $channels): AnonymousEvent
{
return new AnonymousEvent($channels);
}

/**
* Begin sending an anonymous broadcast to the given private channels.
*/
public function private(string $channel): AnonymousEvent
{
return $this->on(new PrivateChannel($channel));
}

/**
* Begin sending an anonymous broadcast to the given presence channels.
*/
public function presence(string $channel): AnonymousEvent
{
return $this->on(new PresenceChannel($channel));
}

/**
* Begin broadcasting an event.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php

namespace Illuminate\Tests\Integration\Broadcasting;

use Illuminate\Broadcasting\AnonymousEvent;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Support\Facades\Broadcast as BroadcastFacade;
use Illuminate\Support\Facades\Event as EventFacade;
use Orchestra\Testbench\TestCase;
use ReflectionClass;

class SendingBroadcastsViaAnonymousEventTest extends TestCase
{
public function testBroadcastIsSent()
{
EventFacade::fake();

BroadcastFacade::on('test-channel')
->with(['some' => 'data'])
->as('test-event')
->send();

EventFacade::assertDispatched(AnonymousEvent::class, function ($event) {
return (new ReflectionClass($event))->getProperty('connection')->getValue($event) === null &&
$event->broadcastOn() === ['test-channel'] &&
$event->broadcastAs() === 'test-event' &&
$event->broadcastWith() === ['some' => 'data'];
});
}

public function testBroadcastIsSentNow()
{
EventFacade::fake();

BroadcastFacade::on('test-channel')
->with(['some' => 'data'])
->as('test-event')
->sendNow();

EventFacade::assertDispatched(AnonymousEvent::class, function ($event) {
return (new ReflectionClass($event))->getProperty('connection')->getValue($event) === null &&
$event->shouldBroadcastNow();
});
}

public function testDefaultNameIsSet()
{
EventFacade::fake();

BroadcastFacade::on('test-channel')
->with(['some' => 'data'])
->send();

EventFacade::assertDispatched(AnonymousEvent::class, function ($event) {
return $event->broadcastAs() === 'AnonymousEvent';
});
}

public function testDefaultPayloadIsSet()
{
EventFacade::fake();

BroadcastFacade::on('test-channel')->send();

EventFacade::assertDispatched(AnonymousEvent::class, function ($event) {
return $event->broadcastWith() === [];
});
}

public function testSendToMultipleChannels()
{
EventFacade::fake();

BroadcastFacade::on([
'test-channel',
new PrivateChannel('test-channel'),
'presence-test-channel',
])->send();

EventFacade::assertDispatched(AnonymousEvent::class, function ($event) {
[$one, $two, $three] = $event->broadcastOn();

return $one === 'test-channel' &&
$two instanceof PrivateChannel &&
$two->name === 'private-test-channel' &&
$three === 'presence-test-channel';
});
}

public function testSendViaANonDefaultConnection()
{
EventFacade::fake();

BroadcastFacade::on('test-channel')
->via('pusher')
->send();

EventFacade::assertDispatched(AnonymousEvent::class, function ($event) {
return (new ReflectionClass($event))->getProperty('connection')->getValue($event) === 'pusher';
});
}

public function testSendToOthersOnly()
{
EventFacade::fake();

$this->app['request']->headers->add(['X-Socket-ID' => '12345']);

BroadcastFacade::on('test-channel')->send();

EventFacade::assertDispatched(AnonymousEvent::class, function ($event) {
return $event->socket === null;
});

BroadcastFacade::on('test-channel')
->toOthers()
->send();

EventFacade::assertDispatched(AnonymousEvent::class, function ($event) {
return $event->socket = '12345';
});
}

public function testSendToPrivateChannel()
{
EventFacade::fake();

BroadcastFacade::private('test-channel')->send();

EventFacade::assertDispatched(AnonymousEvent::class, function ($event) {
$channel = $event->broadcastOn()[0];

return $channel instanceof PrivateChannel && $channel->name === 'private-test-channel';
});
}

public function testSendToPresenceChannel()
{
EventFacade::fake();

BroadcastFacade::presence('test-channel')->send();

EventFacade::assertDispatched(AnonymousEvent::class, function ($event) {
$channel = $event->broadcastOn()[0];

return $channel instanceof PresenceChannel && $channel->name === 'presence-test-channel';
});
}
}