PHP attributes allow to define machine-readable metadata in your code instead of having to add that configuration in a separate file. With each new Symfony version we add more attributes that you can optionally use. In Symfony 6.3 we've added new attributes to configure HTTP exceptions.
Currently, to create your own HTTP exceptions, you need to implement HttpExceptionInterface
(or extend the HttpException
base class) and configure it in the
framework.exceptions option:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
use App\Domain\Exception\Order\OrderNotFound;
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'],
);
}
}
1 2 3 4 5 6
# config/packages/exceptions.yaml
framework:
exceptions:
App\Domain\Exception\Order\OrderNotFound:
log_level: 'debug'
status_code: 404
In Symfony 6.3, the above code and configuration still work, but you can optionally replace them by the following PHP attributes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use App\Domain\Exception\Order\OrderNotFound;
use Psr\Log\LogLevel;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\WithHttpStatus;
use Symfony\Component\HttpKernel\Attribute\WithLogLevel;
#[WithHttpStatus(Response::HTTP_NOT_FOUND, headers: ['x-header' => 'foo'])]
#[WithLogLevel(LogLevel::DEBUG)]
class OrderNotFound extends \Exception
{
public function __construct(Order $order)
{
parent::__construct(
message: sprintf('The order "%s" could not be found.', $order->getId())
);
}
// ...
}
That's all. You no longer need to configure anything in the framework.exceptions
option. In addition to having all the information in a single file, this also removes
the coupling of your HTTP exceptions with the HttpKernel component. In other words,
your domain exceptions are decoupled from the infrastructure code.
If your application uses both attributes and the framework.exceptions
option,
the configuration will have more priority than the attributes.
If you extends a (http) Exception on your class, you can set the code field directly with the status_code value. The attribute WithHttpStatus doesn't seem useful to me.
I fail to see how this can remove the coupling with the HttpKernel component. You still need to use HttpKernel attributes, right?
The goal is to simplify the writing of the code, without having the need to retain an additional attribute. In this example, it is simple to declare the status code without using the WithHttpStatus attribute, the other attribute would remain necessary to activate the debug.
@Massimiliano in this example, we use the attributes from HttpKernel, so we're still fully coupled to it.
However, the beauty of attributes is that they are declarative, so their implementation is independent from their declaration. We could add this class to a project not using Symfony and the exception class would still be working. Of course, we'd lose the behavior of the attributes but we could implement them ourselves or rely on a third-party library (or other framework) that implements them.
@Massimiliano Arione, it is decoupled in the sense that you can use the exception as an ordinary exception without having the HttpKernel component installed.
Whereas if you extend from
HttpException
in the exception, you need the HttpKernel to use the exception. See https://3v4l.org/MHeT7 vs https://3v4l.org/7HqB4In the example how are you getting the order ID ($this->order->getId()) ?
@Stiff in the constructor of the exception class, which could be used as "throw new OrderNotFound($order)"