Skip to content

Commit 91e614e

Browse files
authored
Decorate errors to help find origin (#11)
1 parent 7551754 commit 91e614e

File tree

2 files changed

+121
-4
lines changed

2 files changed

+121
-4
lines changed

src/Collection.php

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
namespace olvlvl\ComposerAttributeCollector;
1111

12+
use RuntimeException;
13+
use Throwable;
14+
1215
use function array_map;
1316

1417
/**
@@ -40,11 +43,32 @@ public function __construct(
4043
public function findTargetClasses(string $attribute): array
4144
{
4245
return array_map(
43-
fn(array $a) => new TargetClass(new $attribute(...$a[0]), $a[1]),
46+
fn(array $a) => new TargetClass(self::createClassAttribute($attribute, ...$a), $a[1]),
4447
$this->targetClasses[$attribute] ?? []
4548
);
4649
}
4750

51+
/**
52+
* @template T of object
53+
*
54+
* @param class-string<T> $attribute
55+
* @param array<int|string, mixed> $arguments
56+
* @param class-string $class
57+
*
58+
* @return T
59+
*/
60+
private static function createClassAttribute(string $attribute, array $arguments, string $class): object
61+
{
62+
try {
63+
return new $attribute(...$arguments);
64+
} catch (Throwable $e) {
65+
throw new RuntimeException(
66+
"An error occurred while instantiating attribute $attribute on class $class",
67+
previous: $e
68+
);
69+
}
70+
}
71+
4872
/**
4973
* @template T of object
5074
*
@@ -55,11 +79,36 @@ public function findTargetClasses(string $attribute): array
5579
public function findTargetMethods(string $attribute): array
5680
{
5781
return array_map(
58-
fn(array $a) => new TargetMethod(new $attribute(...$a[0]), $a[1], $a[2]),
82+
fn(array $a) => new TargetMethod(self::createMethodAttribute($attribute, ...$a), $a[1], $a[2]),
5983
$this->targetMethods[$attribute] ?? []
6084
);
6185
}
6286

87+
/**
88+
* @template T of object
89+
*
90+
* @param class-string<T> $attribute
91+
* @param array<int|string, mixed> $arguments
92+
* @param class-string $class
93+
*
94+
* @return T
95+
*/
96+
private static function createMethodAttribute(
97+
string $attribute,
98+
array $arguments,
99+
string $class,
100+
string $method
101+
): object {
102+
try {
103+
return new $attribute(...$arguments);
104+
} catch (Throwable $e) {
105+
throw new RuntimeException(
106+
"An error occurred while instantiating attribute $attribute on method $class::$method",
107+
previous: $e
108+
);
109+
}
110+
}
111+
63112
/**
64113
* @param class-string $class
65114
*
@@ -75,7 +124,7 @@ public function forClass(string $class): ForClass
75124
continue;
76125
}
77126

78-
$classAttributes[] = new $attribute(...$arguments);
127+
$classAttributes[] = self::createClassAttribute($attribute, $arguments, $class);
79128
}
80129
}
81130

@@ -87,7 +136,12 @@ public function forClass(string $class): ForClass
87136
continue;
88137
}
89138

90-
$methodAttributes[$targetMethod][] = new $attribute(...$arguments);
139+
$methodAttributes[$targetMethod][] = self::createMethodAttribute(
140+
$attribute,
141+
$arguments,
142+
$class,
143+
$targetMethod
144+
);
91145
}
92146
}
93147

tests/CollectionTest.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace tests\olvlvl\ComposerAttributeCollector;
4+
5+
use Closure;
6+
use olvlvl\ComposerAttributeCollector\Collection;
7+
use PHPUnit\Framework\TestCase;
8+
use RuntimeException;
9+
10+
final class CollectionTest extends TestCase
11+
{
12+
/**
13+
* @dataProvider provideInstantiationErrorIsDecorated
14+
*
15+
* @param Closure(Collection):void $act
16+
*/
17+
public function testInstantiationErrorIsDecorated(string $expectedMessage, Closure $act): void
18+
{
19+
$collection = new Collection(
20+
[
21+
\Acme\Attribute\Permission::class => [
22+
[ [ 'Permission' => 'is_admin' ], \Acme\PSR4\DeleteMenu::class ],
23+
]
24+
],
25+
[
26+
\Acme\Attribute\Route::class => [
27+
[ [ 'Method' => 'GET' ], \Acme\PSR4\Presentation\ArticleController::class, 'list' ],
28+
]
29+
],
30+
);
31+
32+
$this->expectException(RuntimeException::class);
33+
$this->expectExceptionMessage($expectedMessage);
34+
$act($collection);
35+
}
36+
37+
/**
38+
* @return array<array{ string, Closure }>
39+
*/
40+
public static function provideInstantiationErrorIsDecorated(): array
41+
{
42+
return [
43+
44+
[
45+
"An error occurred while instantiating attribute Acme\Attribute\Permission on class Acme\PSR4\DeleteMenu",
46+
fn(Collection $c) => $c->findTargetClasses(\Acme\Attribute\Permission::class),
47+
],
48+
[
49+
"An error occurred while instantiating attribute Acme\Attribute\Route on method Acme\PSR4\Presentation\ArticleController::list",
50+
fn(Collection $c) => $c->findTargetMethods(\Acme\Attribute\Route::class),
51+
],
52+
[
53+
"An error occurred while instantiating attribute Acme\Attribute\Permission on class Acme\PSR4\DeleteMenu",
54+
fn(Collection $c) => $c->forClass(\Acme\PSR4\DeleteMenu::class),
55+
],
56+
[
57+
"An error occurred while instantiating attribute Acme\Attribute\Route on method Acme\PSR4\Presentation\ArticleController::list",
58+
fn(Collection $c) => $c->forClass(\Acme\PSR4\Presentation\ArticleController::class),
59+
],
60+
61+
];
62+
}
63+
}

0 commit comments

Comments
 (0)