-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[HttpKernel] Allow using #[HttpStatus]
for setting status code and headers for HTTP exceptions
#48352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
eb43f5f
to
4605e8b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good!
Another option is to have multiple attributes created in the component, for each HTTP status code.
I'm thinking just a few of the common ones (even just the ones you demonstrate in your blog post). They should all extend HttpException
.
I like the idea but I've some doubts, as you said, this feature is an alternative to implementing Can you elaborate a bit more about the advantages of using this attribute versus doing this: use Symfony\Component\HttpKernel\Exception\HttpException;
class OrderNotFound extends HttpException
{
public static function create(string $id): self
{
return new self(
statusCode: Response::HTTP_NOT_FOUND,
message: sprintf('The order "%s" could not be found.', $id),
headers: ['x-header' => 'foo'],
);
}
} or this (in case you don't want to couple the Infrastructure/Framework subjects with your Domain layer): use App\Domain\Exception\NotFound;
class OrderNotFound extends NotFound
{
public static function create(string $id): self
{
return new self(sprintf('The order "%s" could not be found.', $id));
}
} framework:
exceptions:
App\Domain\Exception\NotFound:
status_code: 404 Full reflection and attributes go hand in hand, Imo if the use-case is satisfied by inheritance, prefer that. There are other things to think about:
About the implementation, the status code must be set before handling the error controller thus we render the exception into a response with the proper status code (e.g. |
My two cents:
I think this provides a nice optional pattern. Not having to define things in config (outside of your php code) has been a direction Symfony has been moving in for some time.
No, this is still needed for exception classes you don't own (or if you choose not to use this pattern).
What does DI prioritize if you have a service configured in yaml and using say the
Makes sense to include I think - provides feature parity with |
My opinion as well is that this should be just another possible way to configure the status codes, without an intention to replace any of the existing ways.
You've already explained the advantages :) Not having to couple the domain exceptions with infrastructure code. Using inheritance and extending a base class for the status code is a bit limiting to me, for example for cases where the users would want their exceptions to extend some other base class. Eg. class PromoCodeExpired extends DiscountException { ... } // UnprocessableEntity and class PromoCodeNotFound extends DiscountException { ... } // NotFound
This is discussable, but i think that "framework.exceptions mapping" should have a bigger priority, for the cases when the user may want to change the status code for an exception with already declared attribute they cannot change?
To me, it makes more sense if this is configurable by a separate attribute. To be honest, it even feels a bit weird to me that both the status code setting from the config mapping and the logging are happening in a same event listener (
I was under impression that we might need to add the functionality to both places, but I might be wrong. I'll take a deeper look regarding this. |
I'm reopening this for reviews. With this PR now, the framework will provide several PHP attributes that can be declared on exception classes in order to set a custom HTTP status code and a list of headers to be used when preparing a response for the client. For example, if the user wants to return 404 for // ...
use Symfony\Component\HttpKernel\Attribute\HttpException\NotFound;
#[NotFound(headers: [
"resource" => "something"
])]
class SomethingNotFound extends ResourceNotFound
{
// ...
} For the list of provided attributes, I've taken the list of exception classes under If the user needs an attribute for another status code not provided by the framework, they can create it themself by extending the use Symfony\Component\HttpFoundation\Response;
#[\Attribute(\Attribute::TARGET_CLASS)]
class SomethingElse extends HttpException
{
public function getStatusCode(): int
{
return Response::HTTP_ALREADY_REPORTED;
}
} Now, this attribute can be declared on exception classes just as in the example above. When implementing a new attribute, the user can also specify a list of headers which will be applied automatically by overriding the Regarding the prioritization, specifying the status code using this way has a lowest priority when used together with another option. As I mentioned above, I think this should be like that in order for the user to be able to set a status code using the configuration even when handling an exception (with an already declared attribute) they have no control over (to change/remove the attribute). Regarding the |
Attributes allow to document arbitrary exceptions for Symfony without a hard dependency on the HttpKernel component. This looks pretty useful to me. However, I don't think we should require to extend the attribute just to change the status code. How about this? class HttpException
{
public function __construct(
public readonly int $statusCode,
public readonly array $headers = [],
) {}
}
class NotFound extends HttpException
{
public function __construct(
array $headers = []
) {
parent::__construct(404, $headers);
}
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @derrabus. Although I think we can use the Response::*
constants as http-hernel has a hard-dep on http-foundation.
src/Symfony/Component/HttpKernel/Attribute/HttpException/AccessDenied.php
Outdated
Show resolved
Hide resolved
9dcd41f
to
89c6000
Compare
I updated the attributes to include the status code. Now the users have 3 possible ways to use this:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feature needs to hook into the current error mechanism. Note that there are ErrorRenderers that depend on the status code and header to build a response. See TwigErrorRenderer, SerializerErrorRenderer + ProblemNormalizer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, @yceruto is right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR.
Here are some comments before approval on my side.
Can you please also update the description of the PR so that it documents what it does now?
src/Symfony/Component/HttpKernel/Attribute/HttpException/AccessDenied.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/HttpKernel/Attribute/HttpException/AccessDenied.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php
Outdated
Show resolved
Hide resolved
7a5f4ba
to
081b0da
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 minor things and good to me, thanks.
* @param array<string, string> $headers | ||
*/ | ||
public function __construct( | ||
public readonly int $statusCode = Response::HTTP_INTERNAL_SERVER_ERROR, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should remove the default value IMHO
$class = new \ReflectionClass($throwable); | ||
$attributes = $class->getAttributes(HttpStatus::class, \ReflectionAttribute::IS_INSTANCEOF); | ||
|
||
if (\count($attributes)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ($attributes) {
Just one more thing: I don't think it's worth adding attributes for all possible status codes. I would code with only the most common ones, which are NotFound, BadRequest and likely Forbidden to me. |
I like this feature a lot, great idea! My (not strong) opinion is that we don't need the helpers for NotFound, BadRequest, ... at all:
|
Let me give you my (strong) opinion here :) 1/ I think it should be all or nothing: we either have attributes for all status codes or none. Why? Because we will have follow-up PRs for years adding one more class for good reasons. Then, it introduces some inconsistencies in how you configure the attribute. Finally, it gives you two ways to do the same thing. 2/ As both possibilities are equally self-descriptive, I don't see a good reason to introduce so many attributes:
3/ It will be easy to introduce these classes later on if we think it would be better, instead of deprecating classes. Less work for us, and opt-in for our users. So, I would vote for removing all these classes. |
I removed all the attributes except the general one and updated the PR description. |
#[HttpStatus]
for setting status code and headers for HTTP exceptions
…headers for HTTP exceptions
56921da
to
d1cbcca
Compare
Thank you @angelov. |
Thank you all for the support! |
See #48876 where I've renamed HttpStatus to WithHttpStatus. |
…tus (fabpot) This PR was merged into the 6.3 branch. Discussion ---------- [HttpKernel] Rename HttpStatus atribute to WithHttpStatus | Q | A | ------------- | --- | Branch? | 6.3 | Bug fix? | yes-ish | New feature? | no <!-- please update src/**/CHANGELOG.md files --> | Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tickets | Refs #48352 and #48747 As discussed in the 2 referenced PRs for better naming consistency. Commits ------- fece766 [HttpKernel] Rename HttpStatus atribute to WithHttpStatus
Currently, among the other ways, to specify a status code and list of headers to be used for the response for a given exception, we can implement the
HttpExceptionInterface
interface.With this PR now, the framework will provide a new PHP attribute that can be declared on exception classes in order to set a custom HTTP status code and a list of headers to be used when preparing a response for the client.
For example, if the user wants to return 404 for
SomethingNotFoundException
exceptions, they will be able to declare the\Symfony\Component\HttpKernel\Attribute\HttpStatus
on the exception class, and provide the status code and optionally a list of headers as arguments.The users can also can create additional status-specific attributes themself by extending the
\Symfony\Component\HttpKernel\Attribute\HttpStatus
class.Note that this has the lowest priority when using it alongside some of the other possible ways for setting the status code.
The list of priorities is:
HttpExceptionInterface
interface(Meaning that, for example, if there's an exception with a
NotFound
attribute declared on an exception, but it is defined in the configuration that the status code for this exception should be 400, 400 will be used as a final status code. The priority of the first two ways is not changed.)