From 6f761aa5d833120432ea3b8a7e138432c2fb1dbe Mon Sep 17 00:00:00 2001 From: Mohamed Said Date: Wed, 26 Oct 2016 23:35:40 +0200 Subject: [PATCH] Model binding in broadcasting channel definitions --- .../Broadcasting/Broadcasters/Broadcaster.php | 51 +++++++-- tests/Broadcasting/BroadcasterTest.php | 102 ++++++++++++++++++ 2 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 tests/Broadcasting/BroadcasterTest.php diff --git a/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php index d41ed40fadfc..ed557b794d27 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php @@ -2,7 +2,10 @@ namespace Illuminate\Broadcasting\Broadcasters; +use ReflectionFunction; +use ReflectionParameter; use Illuminate\Support\Str; +use Illuminate\Database\Eloquent\Model; use Symfony\Component\HttpKernel\Exception\HttpException; use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract; @@ -39,11 +42,11 @@ public function channel($channel, callable $callback) protected function verifyUserCanAccessChannel($request, $channel) { foreach ($this->channels as $pattern => $callback) { - if (! Str::is($pattern, $channel)) { + if (! Str::is(preg_replace('/\{(.*?)\}/', '*', $pattern), $channel)) { continue; } - $parameters = $this->extractAuthParameters($pattern, $channel); + $parameters = $this->extractAuthParameters($pattern, $channel, $callback); if ($result = $callback($request->user(), ...$parameters)) { return $this->validAuthenticationResponse($request, $result); @@ -58,23 +61,29 @@ protected function verifyUserCanAccessChannel($request, $channel) * * @param string $pattern * @param string $channel + * @param callable $callback * @return array */ - protected function extractAuthParameters($pattern, $channel) + protected function extractAuthParameters($pattern, $channel, $callback) { - if (! Str::contains($pattern, '*')) { - return []; - } + $parameters = []; + + $pattern = preg_replace('/\{(.*?)\}/', '([^\.]+)', $pattern); - $pattern = str_replace('\*', '([^\.]+)', preg_quote($pattern)); + preg_match('/^'.$pattern.'/', $channel, $keys); - if (preg_match('/^'.$pattern.'/', $channel, $keys)) { - array_shift($keys); + $callbackParameters = (new ReflectionFunction($callback))->getParameters(); - return $keys; + foreach ($callbackParameters as $parameter) { + if ($parameter->getPosition() === 0) { + continue; + } + + $parameters[] = ! isset($keys[$parameter->getPosition()]) + ? null : $this->getAuthParameterFromKeys($parameter, $keys); } - return []; + return $parameters; } /** @@ -89,4 +98,24 @@ protected function formatChannels(array $channels) return (string) $channel; }, $channels); } + + /** + * Extract a parameter from the given keys. + * + * @param ReflectionParameter $parameter + * @param array $keys + * @return mixed + */ + protected function getAuthParameterFromKeys($parameter, $keys) + { + $key = $keys[$parameter->getPosition()]; + + if ($parameter->getClass() && $parameter->getClass()->isSubclassOf(Model::class)) { + $model = $parameter->getClass()->newInstance(); + + return $model->where($model->getRouteKeyName(), $key)->first(); + } + + return $key; + } } diff --git a/tests/Broadcasting/BroadcasterTest.php b/tests/Broadcasting/BroadcasterTest.php new file mode 100644 index 000000000000..bd90c8fea3e4 --- /dev/null +++ b/tests/Broadcasting/BroadcasterTest.php @@ -0,0 +1,102 @@ +extractAuthParameters('asd.{model}.{nonModel}', 'asd.1.something', $callback); + $this->assertEquals(['model.1.instance', 'something'], $parameters); + + $callback = function ($user, BroadcasterTestEloquentModelStub $model, BroadcasterTestEloquentModelStub $model2, $something) { + }; + $parameters = $broadcaster->extractAuthParameters('asd.{model}.{model}.{nonModel}', 'asd.1.uid.something', $callback); + $this->assertEquals(['model.1.instance', 'model.uid.instance', 'something'], $parameters); + + $callback = function ($user) { + }; + $parameters = $broadcaster->extractAuthParameters('asd', 'asd', $callback); + $this->assertEquals([], $parameters); + + $callback = function ($user, $something) { + }; + $parameters = $broadcaster->extractAuthParameters('asd', 'asd', $callback); + $this->assertEquals([null], $parameters); + + $callback = function ($user, BroadcasterTestEloquentModelNotFoundStub $model) { + }; + $parameters = $broadcaster->extractAuthParameters('asd.{model}', 'asd.1', $callback); + $this->assertEquals([null], $parameters); + } +} + +class FakeBroadcaster extends Broadcaster +{ + public function auth($request) + { + } + + public function validAuthenticationResponse($request, $result) + { + } + + public function broadcast(array $channels, $event, array $payload = []) + { + } + + public function extractAuthParameters($pattern, $channel, $callback) + { + return parent::extractAuthParameters($pattern, $channel, $callback); + } +} + +class BroadcasterTestEloquentModelStub extends Model +{ + public function getRouteKeyName() + { + return 'id'; + } + + public function where($key, $value) + { + $this->value = $value; + + return $this; + } + + public function first() + { + return "model.{$this->value}.instance"; + } +} + +class BroadcasterTestEloquentModelNotFoundStub extends Model +{ + public function getRouteKeyName() + { + return 'id'; + } + + public function where($key, $value) + { + $this->value = $value; + + return $this; + } + + public function first() + { + } +}