Skip to content

Commit

Permalink
support union types on closure callbacks and exception callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
taylorotwell committed Aug 14, 2021
1 parent 8cbdf8f commit 396c1c2
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 25 deletions.
10 changes: 8 additions & 2 deletions src/Illuminate/Events/Dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,15 @@ public function __construct(ContainerContract $container = null)
public function listen($events, $listener = null)
{
if ($events instanceof Closure) {
return $this->listen($this->firstClosureParameterType($events), $events);
return collect($this->firstClosureParameterTypes($events))
->each(function ($event) use ($events) {
$this->listen($event, $events);
});
} elseif ($events instanceof QueuedClosure) {
return $this->listen($this->firstClosureParameterType($events->closure), $events->resolve());
return collect($this->firstClosureParameterTypes($events->closure))
->each(function ($event) use ($events) {
$this->listen($event, $events->resolve());
});
} elseif ($listener instanceof QueuedClosure) {
$listener = $listener->resolve();
}
Expand Down
10 changes: 6 additions & 4 deletions src/Illuminate/Foundation/Exceptions/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -332,11 +332,13 @@ public function render($request, Throwable $e)
$e = $this->prepareException($this->mapException($e));

foreach ($this->renderCallbacks as $renderCallback) {
if (is_a($e, $this->firstClosureParameterType($renderCallback))) {
$response = $renderCallback($e, $request);
foreach ($this->firstClosureParameterTypes($renderCallback) as $type) {
if (is_a($e, $type)) {
$response = $renderCallback($e, $request);

if (! is_null($response)) {
return $response;
if (! is_null($response)) {
return $response;
}
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/Illuminate/Foundation/Exceptions/ReportableHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ public function __invoke(Throwable $e)
*/
public function handles(Throwable $e)
{
return is_a($e, $this->firstClosureParameterType($this->callback));
foreach ($this->firstClosureParameterTypes($this->callback) as $type) {
if (is_a($e, $type)) {
return true;
}
}

return false;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/Illuminate/Support/Reflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static function getParameterClassNames($parameter)
$type = $parameter->getType();

if (! $type instanceof ReflectionUnionType) {
return [static::getParameterClassName($parameter)];
return array_filter([static::getParameterClassName($parameter)]);
}

$unionTypes = [];
Expand All @@ -97,7 +97,7 @@ public static function getParameterClassNames($parameter)
$unionTypes[] = static::getTypeName($parameter, $listedType);
}

return $unionTypes;
return array_filter($unionTypes);
}

/**
Expand Down
64 changes: 48 additions & 16 deletions src/Illuminate/Support/Traits/ReflectsClosures.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,79 @@
trait ReflectsClosures
{
/**
* Get the class names / types of the parameters of the given Closure.
* Get the class name of the first parameter of the given Closure.
*
* @param \Closure $closure
* @return array
* @return string
*
* @throws \ReflectionException
* @throws \RuntimeException
*/
protected function closureParameterTypes(Closure $closure)
protected function firstClosureParameterType(Closure $closure)
{
$reflection = new ReflectionFunction($closure);
$types = array_values($this->closureParameterTypes($closure));

return collect($reflection->getParameters())->mapWithKeys(function ($parameter) {
if ($parameter->isVariadic()) {
return [$parameter->getName() => null];
}
if (! $types) {
throw new RuntimeException('The given Closure has no parameters.');
}

return [$parameter->getName() => Reflector::getParameterClassName($parameter)];
})->all();
if ($types[0] === null) {
throw new RuntimeException('The first parameter of the given Closure is missing a type hint.');
}

return $types[0];
}

/**
* Get the class name of the first parameter of the given Closure.
* Get the class names of the first parameter of the given Closure, including union types.
*
* @param \Closure $closure
* @return string
* @return array
*
* @throws \ReflectionException
* @throws \RuntimeException
*/
protected function firstClosureParameterType(Closure $closure)
protected function firstClosureParameterTypes(Closure $closure)
{
$types = array_values($this->closureParameterTypes($closure));
$reflection = new ReflectionFunction($closure);

if (! $types) {
$types = collect($reflection->getParameters())->mapWithKeys(function ($parameter) {
if ($parameter->isVariadic()) {
return [$parameter->getName() => null];
}

return [$parameter->getName() => Reflector::getParameterClassNames($parameter)];
})->filter()->values()->all();

if (empty($types)) {
throw new RuntimeException('The given Closure has no parameters.');
}

if ($types[0] === null) {
if (isset($types[0]) && empty($types[0])) {
throw new RuntimeException('The first parameter of the given Closure is missing a type hint.');
}

return $types[0];
}

/**
* Get the class names / types of the parameters of the given Closure.
*
* @param \Closure $closure
* @return array
*
* @throws \ReflectionException
*/
protected function closureParameterTypes(Closure $closure)
{
$reflection = new ReflectionFunction($closure);

return collect($reflection->getParameters())->mapWithKeys(function ($parameter) {
if ($parameter->isVariadic()) {
return [$parameter->getName() => null];
}

return [$parameter->getName() => Reflector::getParameterClassName($parameter)];
})->all();
}
}
48 changes: 48 additions & 0 deletions tests/Support/SupportReflectsClosuresTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,44 @@ public function testItThrowsWhenNoFirstParameterType()
});
}

public function testItWorksWithUnionTypes()
{
$types = ReflectsClosuresClass::reflectFirstAll(function (ExampleParameter $a, $b) {
//
});

$this->assertEquals([
ExampleParameter::class,
], $types);

$types = ReflectsClosuresClass::reflectFirstAll(function (ExampleParameter|AnotherExampleParameter $a, $b) {
//
});

$this->assertEquals([
ExampleParameter::class,
AnotherExampleParameter::class,
], $types);
}

public function testItWorksWithUnionTypesWithNoTypeHints()
{
$this->expectException(RuntimeException::class);

$types = ReflectsClosuresClass::reflectFirstAll(function ($a, $b) {
//
});
}

public function testItWorksWithUnionTypesWithNoArguments()
{
$this->expectException(RuntimeException::class);

$types = ReflectsClosuresClass::reflectFirstAll(function () {
//
});
}

private function assertParameterTypes($expected, $closure)
{
$types = ReflectsClosuresClass::reflect($closure);
Expand All @@ -85,9 +123,19 @@ public static function reflectFirst($closure)
{
return (new static)->firstClosureParameterType($closure);
}

public static function reflectFirstAll($closure)
{
return (new static)->firstClosureParameterTypes($closure);
}
}

class ExampleParameter
{
//
}

class AnotherExampleParameter
{
//
}

0 comments on commit 396c1c2

Please sign in to comment.