8000 [HttpKernel] Allow using `#[HttpStatus]` for setting status code and … · symfony/symfony@d1cbcca · GitHub
[go: up one dir, main page]

Skip to content

Commit d1cbcca

Browse files
angelovnicolas-grekas
authored andcommitted
[HttpKernel] Allow using #[HttpStatus] for setting status code and headers for HTTP exceptions
1 parent a27e37a commit d1cbcca

File tree

4 files changed

+110
-0
lines changed

4 files changed

+110
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\Attribute;
13+
14+
/**
15+
* @author Dejan Angelov <angelovdejan@protonmail.com>
16+
*/
17+
#[\Attribute(\Attribute::TARGET_CLASS)]
18+
class HttpStatus
19+
{
20+
/**
21+
* @param array<string, string> $headers
22+
*/
23+
public function __construct(
24+
public readonly int $statusCode,
25+
public readonly array $headers = [],
26+
) {
27+
}
28+
}

src/Symfony/Component/HttpKernel/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead
88
* `FileProfilerStorage` removes profiles automatically after two days
9+
* Add `#[HttpStatus]` for defining status codes for exceptions
910

1011
6.2
1112
---

src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php

+15
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1616
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1717
use Symfony\Component\HttpFoundation\Request;
18+
use Symfony\Component\HttpKernel\Attribute\HttpStatus;
1819
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
1920
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
2021
use Symfony\Component\HttpKernel\Event\ResponseEvent;
@@ -72,6 +73,20 @@ public function logKernelException(ExceptionEvent $event)
7273
break;
7374
}
7475

76+
// There's no specific status code defined in the configuration for this exception
77+
if (!$throwable instanceof HttpExceptionInterface) {
78+
$class = new \ReflectionClass($throwable);
79+
$attributes = $class->getAttributes(HttpStatus::class, \ReflectionAttribute::IS_INSTANCEOF);
80+
81+
if ($attributes) {
82+
/** @var HttpStatus $instance */
83+
$instance = $attributes[0]->newInstance();
84+
85+
$throwable = new HttpException($instance->statusCode, $throwable->getMessage(), $throwable, $instance->headers);
86+
$event->setThrowable($throwable);
87+
}
88+
}
89+
7590
$e = FlattenException::createFromThrowable($throwable);
7691

7792
$this->logException($throwable, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), $e->getFile(), $e->getLine()), $logLevel);

src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php

+66
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\EventDispatcher\EventDispatcher;
1818
use Symfony\Component\HttpFoundation\Request;
1919
use Symfony\Component\HttpFoundation\Response;
20+
use Symfony\Component\HttpKernel\Attribute\HttpStatus;
2021
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
2122
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
2223
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
@@ -117,6 +118,36 @@ public function testHandleWithLoggerAndCustomConfiguration()
117118
$this->assertCount(1, $logger->getLogs('warning'));
118119
}
119120

121+
/**
122+
* @dataProvider exceptionWithAttributeProvider
123+
*/
124+
public function testHandleHttpAttribute(\Throwable $exception, int $expectedStatusCode, array $expectedHeaders)
125+
{
126+
$request = new Request();
127+
$event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, $exception);
128+
$l = new ErrorListener('not used');
129+
$l->logKernelException($event);
130+
$l->onKernelException($event);
131+
132+
$this->assertEquals(new Response('foo', $expectedStatusCode, $expectedHeaders), $event->getResponse());
133+
}
134+
135+
public function testHandleCustomConfigurationAndHttpAttribute()
136+
{
137+
$request = new Request();
138+
$event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new WithGeneralAttribute());
139+
$l = new ErrorListener('not used', null, false, [
140+
WithGeneralAttribute::class => [
141+
'log_level' => 'warning',
142+
'status_code' => 401,
143+
],
144+
]);
145+
$l->logKernelException($event);
146+
$l->onKernelException($event);
147+
148+
$this->assertEquals(new Response('foo', 401), $event->getResponse());
149+
}
150+
120151
public function provider()
121152
{
122153
if (!class_exists(Request::class)) {
@@ -216,6 +247,12 @@ public function controllerProvider()
216247
return new Response('OK: '.$exception->getMessage());
217248
}];
218249
}
250+
251+
public static function exceptionWithAttributeProvider()
252+
{
253+
yield [new WithCustomUserProvidedAttribute(), 208, ['name' => 'value']];
254+
yield [new WithGeneralAttribute(), 412, ['some' => 'thing']];
255+
}
219256
}
220257

221258
class TestLogger extends Logger implements DebugLoggerInterface
@@ -246,3 +283,32 @@ public function handle(Request $request, $type = self::MAIN_REQUEST, $catch = tr
246283
throw new \RuntimeException('bar');
247284
}
248285
}
286+
287+
#[\Attribute(\Attribute::TARGET_CLASS)]
288+
class UserProvidedHttpStatusCodeAttribute extends HttpStatus
289+
{
290+
public function __construct(array $headers = [])
291+
{
292+
parent::__construct(
293+
Response::HTTP_ALREADY_REPORTED,
294+
$headers
295+
);
296+
}
297+
}
298+
299+
#[UserProvidedHttpStatusCodeAttribute(headers: [
300+
'name' => 'value',
301+
])]
302+
class WithCustomUserProvidedAttribute extends \Exception
303+
{
304+
}
305+
306+
#[HttpStatus(
307+
statusCode: Response::HTTP_PRECONDITION_FAILED,
308+
headers: [
309+
'some' => 'thing',
310+
]
311+
)]
312+
class WithGeneralAttribute extends \Exception
313+
{
314+
}

0 commit comments

Comments
 (0)
0