master

laravel/framework

Last updated at: 29/12/2023 09:20

RedisBroadcaster.php

TLDR

The file RedisBroadcaster.php is a part of the Illuminate Broadcasting library in the Demo Projects project. It contains the RedisBroadcaster class, which is responsible for broadcasting events using Redis as the underlying broadcasting driver. The class extends the Broadcaster class and implements methods for authentication, broadcasting, and formatting channels.

Methods

auth

This method is used to authenticate an incoming request for a given channel. It checks if the channel is guarded and if the user has access to it. If the channel is empty or the user doesn't have access, it throws an AccessDeniedHttpException. Otherwise, it calls the verifyUserCanAccessChannel method of the parent class to further verify user access.

validAuthenticationResponse

This method returns the valid authentication response for a given request and result. If the result is a boolean, it returns the result as a JSON-encoded string. Otherwise, it retrieves the user for the channel and generates a JSON-encoded response containing the user identifier and additional user information.

broadcast

This method is used to broadcast an event to multiple channels. It first checks if the array of channels is empty and if so, returns early. Then, it retrieves the Redis connection based on the specified connection (or uses the default connection). It converts the payload (event data) into a JSON-encoded string and invokes a Lua script to broadcast the event to all the specified channels. If a Redis connection or Lua script error occurs, it throws a BroadcastException with the corresponding error message.

broadcastMultipleChannelsScript

This protected method returns the Lua script used for broadcasting to multiple channels. The script receives the payload (event data) as the first argument and the channel names as the remaining arguments. It iterates through the channel names and publishes the payload to each channel using Redis.

formatChannels

This protected method formats the channel array by adding the configured prefix to each channel name. It delegates the formatting to the formatChannels method of the parent class and then appends the prefix to each channel name.

Classes

RedisBroadcaster

The RedisBroadcaster class extends the Broadcaster class and represents a broadcasting driver that uses Redis. It implements methods for authentication and broadcasting events. The class also includes properties for the Redis instance, connection name, and key prefix.

<?php

namespace Illuminate\Broadcasting\Broadcasters;

use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Contracts\Redis\Factory as Redis;
use Illuminate\Support\Arr;
use Predis\Connection\ConnectionException;
use RedisException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

class RedisBroadcaster extends Broadcaster
{
    use UsePusherChannelConventions;

    /**
     * The Redis instance.
     *
     * @var \Illuminate\Contracts\Redis\Factory
     */
    protected $redis;

    /**
     * The Redis connection to use for broadcasting.
     *
     * @var string|null
     */
    protected $connection = null;

    /**
     * The Redis key prefix.
     *
     * @var string
     */
    protected $prefix = '';

    /**
     * Create a new broadcaster instance.
     *
     * @param  \Illuminate\Contracts\Redis\Factory  $redis
     * @param  string|null  $connection
     * @param  string  $prefix
     * @return void
     */
    public function __construct(Redis $redis, $connection = null, $prefix = '')
    {
        $this->redis = $redis;
        $this->prefix = $prefix;
        $this->connection = $connection;
    }

    /**
     * Authenticate the incoming request for a given channel.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     *
     * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
     */
    public function auth($request)
    {
        $channelName = $this->normalizeChannelName(
            str_replace($this->prefix, '', $request->channel_name)
        );

        if (empty($request->channel_name) ||
            ($this->isGuardedChannel($request->channel_name) &&
            ! $this->retrieveUser($request, $channelName))) {
            throw new AccessDeniedHttpException;
        }

        return parent::verifyUserCanAccessChannel(
            $request, $channelName
        );
    }

    /**
     * Return the valid authentication response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $result
     * @return mixed
     */
    public function validAuthenticationResponse($request, $result)
    {
        if (is_bool($result)) {
            return json_encode($result);
        }

        $channelName = $this->normalizeChannelName($request->channel_name);

        $user = $this->retrieveUser($request, $channelName);

        $broadcastIdentifier = method_exists($user, 'getAuthIdentifierForBroadcasting')
                        ? $user->getAuthIdentifierForBroadcasting()
                        : $user->getAuthIdentifier();

        return json_encode(['channel_data' => [
            'user_id' => $broadcastIdentifier,
            'user_info' => $result,
        ]]);
    }

    /**
     * Broadcast the given event.
     *
     * @param  array  $channels
     * @param  string  $event
     * @param  array  $payload
     * @return void
     *
     * @throws \Illuminate\Broadcasting\BroadcastException
     */
    public function broadcast(array $channels, $event, array $payload = [])
    {
        if (empty($channels)) {
            return;
        }

        $connection = $this->redis->connection($this->connection);

        $payload = json_encode([
            'event' => $event,
            'data' => $payload,
            'socket' => Arr::pull($payload, 'socket'),
        ]);

        try {
            $connection->eval(
                $this->broadcastMultipleChannelsScript(),
                0, $payload, ...$this->formatChannels($channels)
            );
        } catch (ConnectionException|RedisException $e) {
            throw new BroadcastException(
                sprintf('Redis error: %s.', $e->getMessage())
            );
        }
    }

    /**
     * Get the Lua script for broadcasting to multiple channels.
     *
     * ARGV[1] - The payload
     * ARGV[2...] - The channels
     *
     * @return string
     */
    protected function broadcastMultipleChannelsScript()
    {
        return <<<'LUA'
for i = 2, #ARGV do
  redis.call('publish', ARGV[i], ARGV[1])
end
LUA;
    }

    /**
     * Format the channel array into an array of strings.
     *
     * @param  array  $channels
     * @return array
     */
    protected function formatChannels(array $channels)
    {
        return array_map(function ($channel) {
            return $this->prefix.$channel;
        }, parent::formatChannels($channels));
    }
}