Skip to content

Commit 396c1c2

Browse files
committed
support union types on closure callbacks and exception callbacks
1 parent 8cbdf8f commit 396c1c2

File tree

6 files changed

+119
-25
lines changed

6 files changed

+119
-25
lines changed

src/Illuminate/Events/Dispatcher.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,15 @@ public function __construct(ContainerContract $container = null)
7777
public function listen($events, $listener = null)
7878
{
7979
if ($events instanceof Closure) {
80-
return $this->listen($this->firstClosureParameterType($events), $events);
80+
return collect($this->firstClosureParameterTypes($events))
81+
->each(function ($event) use ($events) {
82+
$this->listen($event, $events);
83+
});
8184
} elseif ($events instanceof QueuedClosure) {
82-
return $this->listen($this->firstClosureParameterType($events->closure), $events->resolve());
85+
return collect($this->firstClosureParameterTypes($events->closure))
86+
->each(function ($event) use ($events) {
87+
$this->listen($event, $events->resolve());
88+
});
8389
} elseif ($listener instanceof QueuedClosure) {
8490
$listener = $listener->resolve();
8591
}

src/Illuminate/Foundation/Exceptions/Handler.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -332,11 +332,13 @@ public function render($request, Throwable $e)
332332
$e = $this->prepareException($this->mapException($e));
333333

334334
foreach ($this->renderCallbacks as $renderCallback) {
335-
if (is_a($e, $this->firstClosureParameterType($renderCallback))) {
336-
$response = $renderCallback($e, $request);
335+
foreach ($this->firstClosureParameterTypes($renderCallback) as $type) {
336+
if (is_a($e, $type)) {
337+
$response = $renderCallback($e, $request);
337338

338-
if (! is_null($response)) {
339-
return $response;
339+
if (! is_null($response)) {
340+
return $response;
341+
}
340342
}
341343
}
342344
}

src/Illuminate/Foundation/Exceptions/ReportableHandler.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,13 @@ public function __invoke(Throwable $e)
5959
*/
6060
public function handles(Throwable $e)
6161
{
62-
return is_a($e, $this->firstClosureParameterType($this->callback));
62+
foreach ($this->firstClosureParameterTypes($this->callback) as $type) {
63+
if (is_a($e, $type)) {
64+
return true;
65+
}
66+
}
67+
68+
return false;
6369
}
6470

6571
/**

src/Illuminate/Support/Reflector.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public static function getParameterClassNames($parameter)
8484
$type = $parameter->getType();
8585

8686
if (! $type instanceof ReflectionUnionType) {
87-
return [static::getParameterClassName($parameter)];
87+
return array_filter([static::getParameterClassName($parameter)]);
8888
}
8989

9090
$unionTypes = [];
@@ -97,7 +97,7 @@ public static function getParameterClassNames($parameter)
9797
$unionTypes[] = static::getTypeName($parameter, $listedType);
9898
}
9999

100-
return $unionTypes;
100+
return array_filter($unionTypes);
101101
}
102102

103103
/**

src/Illuminate/Support/Traits/ReflectsClosures.php

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,79 @@
1010
trait ReflectsClosures
1111
{
1212
/**
13-
* Get the class names / types of the parameters of the given Closure.
13+
* Get the class name of the first parameter of the given Closure.
1414
*
1515
* @param \Closure $closure
16-
* @return array
16+
* @return string
1717
*
1818
* @throws \ReflectionException
19+
* @throws \RuntimeException
1920
*/
20-
protected function closureParameterTypes(Closure $closure)
21+
protected function firstClosureParameterType(Closure $closure)
2122
{
22-
$reflection = new ReflectionFunction($closure);
23+
$types = array_values($this->closureParameterTypes($closure));
2324

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

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

3336
/**
34-
* Get the class name of the first parameter of the given Closure.
37+
* Get the class names of the first parameter of the given Closure, including union types.
3538
*
3639
* @param \Closure $closure
37-
* @return string
40+
* @return array
3841
*
3942
* @throws \ReflectionException
4043
* @throws \RuntimeException
4144
*/
42-
protected function firstClosureParameterType(Closure $closure)
45+
protected function firstClosureParameterTypes(Closure $closure)
4346
{
44-
$types = array_values($this->closureParameterTypes($closure));
47+
$reflection = new ReflectionFunction($closure);
4548

46-
if (! $types) {
49+
$types = collect($reflection->getParameters())->mapWithKeys(function ($parameter) {
50+
if ($parameter->isVariadic()) {
51+
return [$parameter->getName() => null];
52+
}
53+
54+
return [$parameter->getName() => Reflector::getParameterClassNames($parameter)];
55+
})->filter()->values()->all();
56+
57+
if (empty($types)) {
4758
throw new RuntimeException('The given Closure has no parameters.');
4859
}
4960

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

5465
return $types[0];
5566
}
67+
68+
/**
69+
* Get the class names / types of the parameters of the given Closure.
70+
*
71+
* @param \Closure $closure
72+
* @return array
73+
*
74+
* @throws \ReflectionException
75+
*/
76+
protected function closureParameterTypes(Closure $closure)
77+
{
78+
$reflection = new ReflectionFunction($closure);
79+
80+
return collect($reflection->getParameters())->mapWithKeys(function ($parameter) {
81+
if ($parameter->isVariadic()) {
82+
return [$parameter->getName() => null];
83+
}
84+
85+
return [$parameter->getName() => Reflector::getParameterClassName($parameter)];
86+
})->all();
87+
}
5688
}

tests/Support/SupportReflectsClosuresTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,44 @@ public function testItThrowsWhenNoFirstParameterType()
6464
});
6565
}
6666

67+
public function testItWorksWithUnionTypes()
68+
{
69+
$types = ReflectsClosuresClass::reflectFirstAll(function (ExampleParameter $a, $b) {
70+
//
71+
});
72+
73+
$this->assertEquals([
74+
ExampleParameter::class,
75+
], $types);
76+
77+
$types = ReflectsClosuresClass::reflectFirstAll(function (ExampleParameter|AnotherExampleParameter $a, $b) {
78+
//
79+
});
80+
81+
$this->assertEquals([
82+
ExampleParameter::class,
83+
AnotherExampleParameter::class,
84+
], $types);
85+
}
86+
87+
public function testItWorksWithUnionTypesWithNoTypeHints()
88+
{
89+
$this->expectException(RuntimeException::class);
90+
91+
$types = ReflectsClosuresClass::reflectFirstAll(function ($a, $b) {
92+
//
93+
});
94+
}
95+
96+
public function testItWorksWithUnionTypesWithNoArguments()
97+
{
98+
$this->expectException(RuntimeException::class);
99+
100+
$types = ReflectsClosuresClass::reflectFirstAll(function () {
101+
//
102+
});
103+
}
104+
67105
private function assertParameterTypes($expected, $closure)
68106
{
69107
$types = ReflectsClosuresClass::reflect($closure);
@@ -85,9 +123,19 @@ public static function reflectFirst($closure)
85123
{
86124
return (new static)->firstClosureParameterType($closure);
87125
}
126+
127+
public static function reflectFirstAll($closure)
128+
{
129+
return (new static)->firstClosureParameterTypes($closure);
130+
}
88131
}
89132

90133
class ExampleParameter
91134
{
92135
//
93136
}
137+
138+
class AnotherExampleParameter
139+
{
140+
//
141+
}

0 commit comments

Comments
 (0)