Skip to content

Commit 2bc9774

Browse files
vjikroxblnfksamdark
authored
Introduce UserException attribute (#153)
Co-authored-by: Aleksei Gagarin <[email protected]> Co-authored-by: Alexander Makarov <[email protected]>
1 parent 59c96e6 commit 2bc9774

File tree

6 files changed

+77
-9
lines changed

6 files changed

+77
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Enh #150: Cleanup templates, remove legacy code (@vjik)
66
- New #151: Add `$traceLink` parameter to `HtmlRenderer` to allow linking to trace files (@vjik)
7+
- New #153: Introduce `UserException` attribute to mark user exceptions (@vjik)
78

89
## 4.1.0 April 18, 2025
910

src/Exception/UserException.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,34 @@
44

55
namespace Yiisoft\ErrorHandler\Exception;
66

7+
use Attribute;
78
use Exception;
9+
use ReflectionClass;
10+
use Throwable;
11+
12+
use function count;
813

914
/**
10-
* UserException is the base class for exceptions that are meant to be shown to end users.
11-
* Such exceptions are often caused by mistakes of end users.
15+
* `UserException` is an exception and a class attribute that indicates
16+
* the exception message is safe to display to end users.
17+
*
18+
* Usage:
19+
* - throw directly (`throw new UserException(...)`) for explicit user-facing errors;
20+
* - annotate any exception class with the `#[UserException]` attribute
21+
* to mark its messages as user-facing without extending this class.
1222
*
1323
* @final
1424
*/
25+
#[Attribute(Attribute::TARGET_CLASS)]
1526
class UserException extends Exception
1627
{
28+
public static function isUserException(Throwable $throwable): bool
29+
{
30+
if ($throwable instanceof self) {
31+
return true;
32+
}
33+
34+
$attributes = (new ReflectionClass($throwable))->getAttributes(self::class);
35+
return count($attributes) > 0;
36+
}
1737
}

templates/production.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* @var HtmlRenderer $this
1010
*/
1111

12-
if ($throwable instanceof UserException) {
12+
if (UserException::isUserException($throwable)) {
1313
$name = $this->getThrowableName($throwable);
1414
$message = $throwable->getMessage();
1515
} else {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\ErrorHandler\Tests\Exception\UserException;
6+
7+
use Exception;
8+
use Yiisoft\ErrorHandler\Exception\UserException;
9+
10+
#[UserException]
11+
final class NotFoundException extends Exception
12+
{
13+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\ErrorHandler\Tests\Exception\UserException;
6+
7+
use Exception;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\TestCase;
10+
use Throwable;
11+
use Yiisoft\ErrorHandler\Exception\UserException;
12+
13+
use function PHPUnit\Framework\assertInstanceOf;
14+
use function PHPUnit\Framework\assertSame;
15+
16+
final class UserExceptionTest extends TestCase
17+
{
18+
public function testUserExceptionInstance(): void
19+
{
20+
$exception = new UserException('User error message');
21+
22+
assertSame('User error message', $exception->getMessage());
23+
assertInstanceOf(Exception::class, $exception);
24+
}
25+
26+
public static function dataIsUserException(): iterable
27+
{
28+
yield [true, new UserException()];
29+
yield [false, new Exception()];
30+
yield [true, new NotFoundException()];
31+
}
32+
33+
#[DataProvider('dataIsUserException')]
34+
public function testIsUserException(bool $expected, Throwable $exception): void
35+
{
36+
assertSame($expected, UserException::isUserException($exception));
37+
}
38+
}

tests/Factory/ThrowableResponseFactoryTest.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,8 @@ public function testHandleWithHeadRequestMethod(): void
3232
$this->createThrowable(),
3333
$this->createServerRequest('HEAD', ['Accept' => ['test/html']])
3434
);
35-
$response
36-
->getBody()
37-
->rewind();
38-
$content = $response
39-
->getBody()
40-
->getContents();
35+
$response->getBody()->rewind();
36+
$content = $response->getBody()->getContents();
4137

4238
$this->assertEmpty($content);
4339
$this->assertSame([HeaderRenderer::DEFAULT_ERROR_MESSAGE], $response->getHeader('X-Error-Message'));

0 commit comments

Comments
 (0)