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; } }