205 lines
5.7 KiB
PHP
205 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace Illuminate\Broadcasting\Broadcasters;
|
|
|
|
use ReflectionFunction;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Container\Container;
|
|
use Illuminate\Contracts\Routing\UrlRoutable;
|
|
use Illuminate\Contracts\Routing\BindingRegistrar;
|
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
|
|
|
|
abstract class Broadcaster implements BroadcasterContract
|
|
{
|
|
/**
|
|
* The registered channel authenticators.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $channels = [];
|
|
|
|
/**
|
|
* The binding registrar instance.
|
|
*
|
|
* @var BindingRegistrar
|
|
*/
|
|
protected $bindingRegistrar;
|
|
|
|
/**
|
|
* Register a channel authenticator.
|
|
*
|
|
* @param string $channel
|
|
* @param callable $callback
|
|
* @return $this
|
|
*/
|
|
public function channel($channel, callable $callback)
|
|
{
|
|
$this->channels[$channel] = $callback;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Authenticate the incoming request for a given channel.
|
|
*
|
|
* @param \Illuminate\Http\Request $request
|
|
* @param string $channel
|
|
* @return mixed
|
|
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
|
*/
|
|
protected function verifyUserCanAccessChannel($request, $channel)
|
|
{
|
|
foreach ($this->channels as $pattern => $callback) {
|
|
if (! Str::is(preg_replace('/\{(.*?)\}/', '*', $pattern), $channel)) {
|
|
continue;
|
|
}
|
|
|
|
$parameters = $this->extractAuthParameters($pattern, $channel, $callback);
|
|
|
|
if ($result = $callback($request->user(), ...$parameters)) {
|
|
return $this->validAuthenticationResponse($request, $result);
|
|
}
|
|
}
|
|
|
|
throw new AccessDeniedHttpException;
|
|
}
|
|
|
|
/**
|
|
* Extract the parameters from the given pattern and channel.
|
|
*
|
|
* @param string $pattern
|
|
* @param string $channel
|
|
* @param callable $callback
|
|
* @return array
|
|
*/
|
|
protected function extractAuthParameters($pattern, $channel, $callback)
|
|
{
|
|
$callbackParameters = (new ReflectionFunction($callback))->getParameters();
|
|
|
|
return collect($this->extractChannelKeys($pattern, $channel))->reject(function ($value, $key) {
|
|
return is_numeric($key);
|
|
})->map(function ($value, $key) use ($callbackParameters) {
|
|
return $this->resolveBinding($key, $value, $callbackParameters);
|
|
})->values()->all();
|
|
}
|
|
|
|
/**
|
|
* Extract the channel keys from the incoming channel name.
|
|
*
|
|
* @param string $pattern
|
|
* @param string $channel
|
|
* @return array
|
|
*/
|
|
protected function extractChannelKeys($pattern, $channel)
|
|
{
|
|
preg_match('/^'.preg_replace('/\{(.*?)\}/', '(?<$1>[^\.]+)', $pattern).'/', $channel, $keys);
|
|
|
|
return $keys;
|
|
}
|
|
|
|
/**
|
|
* Resolve the given parameter binding.
|
|
*
|
|
* @param string $key
|
|
* @param string $value
|
|
* @param array $callbackParameters
|
|
* @return mixed
|
|
*/
|
|
protected function resolveBinding($key, $value, $callbackParameters)
|
|
{
|
|
$newValue = $this->resolveExplicitBindingIfPossible($key, $value);
|
|
|
|
return $newValue === $value ? $this->resolveImplicitBindingIfPossible(
|
|
$key, $value, $callbackParameters
|
|
) : $newValue;
|
|
}
|
|
|
|
/**
|
|
* Resolve an explicit parameter binding if applicable.
|
|
*
|
|
* @param string $key
|
|
* @param mixed $value
|
|
* @return mixed
|
|
*/
|
|
protected function resolveExplicitBindingIfPossible($key, $value)
|
|
{
|
|
$binder = $this->binder();
|
|
|
|
if ($binder && $binder->getBindingCallback($key)) {
|
|
return call_user_func($binder->getBindingCallback($key), $value);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Resolve an implicit parameter binding if applicable.
|
|
*
|
|
* @param string $key
|
|
* @param mixed $value
|
|
* @param array $callbackParameters
|
|
* @return mixed
|
|
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
|
*/
|
|
protected function resolveImplicitBindingIfPossible($key, $value, $callbackParameters)
|
|
{
|
|
foreach ($callbackParameters as $parameter) {
|
|
if (! $this->isImplicitlyBindable($key, $parameter)) {
|
|
continue;
|
|
}
|
|
|
|
$instance = $parameter->getClass()->newInstance();
|
|
|
|
if (! $model = $instance->resolveRouteBinding($value)) {
|
|
throw new AccessDeniedHttpException;
|
|
}
|
|
|
|
return $model;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Determine if a given key and parameter is implicitly bindable.
|
|
*
|
|
* @param string $key
|
|
* @param \ReflectionParameter $parameter
|
|
* @return bool
|
|
*/
|
|
protected function isImplicitlyBindable($key, $parameter)
|
|
{
|
|
return $parameter->name === $key && $parameter->getClass() &&
|
|
$parameter->getClass()->isSubclassOf(UrlRoutable::class);
|
|
}
|
|
|
|
/**
|
|
* 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 (string) $channel;
|
|
}, $channels);
|
|
}
|
|
|
|
/**
|
|
* Get the model binding registrar instance.
|
|
*
|
|
* @return \Illuminate\Contracts\Routing\BindingRegistrar
|
|
*/
|
|
protected function binder()
|
|
{
|
|
if (! $this->bindingRegistrar) {
|
|
$this->bindingRegistrar = Container::getInstance()->bound(BindingRegistrar::class)
|
|
? Container::getInstance()->make(BindingRegistrar::class) : null;
|
|
}
|
|
|
|
return $this->bindingRegistrar;
|
|
}
|
|
}
|