Exceptions
Several internal methods of Laniakea uses Laniakea\Exceptions\BaseHttpException
(and its subclasses) to report unrecoverable errors. Those exceptions are designed to be rendered with a specific JSON schema and are not intended to be reported as regular exceptions.
Features
BaseHttpException
and Laniakea's exceptions renderer offers few advantages that might be useful for your project:
- It renders exceptions with a consistent JSON schema (see example below);
- It can have translations for error messages;
- It supports JSON responses and View-based responses;
- It has a
Illuminate\Validation\ValidationException
wrapper and dedicated rendering method for validation exceptions, so they can be rendered in the same JSON format as other exceptions; - You can define custom metadata and HTTP headers per exception.
Reporting and rendering
You can disable reporting for these exceptions and render them in the package-intended format by using Laravel bootstrap's withExceptions()
method.
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Laniakea\Exceptions\BaseHttpException;
use Laniakea\Exceptions\ExceptionRenderer;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
//
})
->withExceptions(function (Exceptions $exceptions) {
/** @var ExceptionRenderer $renderer */
$renderer = app(ExceptionRenderer::class);
// Disable reporting for all Lanikea exceptions.
$exceptions->dontReport(BaseHttpException::class);
// Allow Laniakea's ExceptionRenderer render base exceptions
$exceptions->renderable(fn (BaseHttpException $e, Request $request) => $renderer->render($e, $request));
// Optionally: render Laravel's ValidationException with Laniakea's renderer (see below).
$exceptions->renderable(fn (ValidationException $e, Request $request) => $renderer->renderValidationException($e, $request));
})->create();
The render()
method of Laniakea\Exceptions\ExceptionRenderer
will render the exception in the following format:
{
"error": {
"message": "Product was not found.",
"original_message": "Product was not found.",
"code": "products.not_found",
"meta": []
}
}
Validation exceptions
By default Laravel's Illuminate\Validation\ValidationException
renders like this:
{
"message": "The given data was invalid.",
"errors": {
"name": [
"The name is requried"
]
}
}
If you want to render it with Laniakea's renderer, you can use the renderValidationException()
method of Laniakea\Exceptions\ExceptionRenderer
:
use Illuminate\Validation\ValidationException;
$exceptions->renderable(function (ValidationException $e, Request $request) {
$renderer->renderValidationException($e, $request)
});
This method will wrap your ValidationException
with Laniakea\Exceptions\ValidationException
and render it like this:
{
"error": {
"message": "The name is requried",
"original_message": "The given data was invalid.",
"code": "validation.failed",
"meta": {
"errors": {
"name": ["The name is requried"]
}
}
}
}
Notice how it matches other exceptions format and also provides list of errors in a meta
key.
TIP
ValidationException
wrapper will be used only if the request expects JSON response (i.e. Accept: application/json
header is present).
Otherwise it will fall back to Laravel's default renderer (or your custom-defined renderer).
Creating exceptions
If you want to create new exceptions that should be rendered with Laniakea's renderer, you can extend the Laniakea\Exceptions\BaseHttpException
class and override three constants:
public const MESSAGE
– the default message of the exception;public const ERROR_CODE
– string-based error code with dot notation support. This error code can also be used as translation key;public const HTTP_CODE
– the HTTP status code of the exception – the response will be sent with this status.
Here is an example of a custom exception:
<?php
declare(strict_types=1);
namespace App\Exceptions;
use Laniakea\Exceptions\BaseHttpException;
class UserNotFoundException extends BaseHttpException
{
public const MESSAGE = 'User was not found.';
public const ERROR_CODE = 'users.not_found';
public const HTTP_CODE = 404;
}
Now you can throw this exception in your code and it will be rendered with the same format as other Laniakea exceptions.
{
"error": {
"message": "User was not found.",
"original_message": "User was not found.",
"code": "users.not_found",
"meta": []
}
}
Override default message
When you throw an exception, you can override the default message by passing a string as the first argument of the exception constructor:
<?php
use App\Exceptions\UserNotFoundException;
throw new UserNotFoundException('User with ID 123 was not found.');
Specify metadata
Use the addMeta()
method to add metadata to the exception:
<?php
use App\Exceptions\UserNotFoundException;
throw (new UserNotFoundException())->addMeta(['user_id' => 123]);
To override all previously set metadata, use the setMeta()
method:
<?php
use App\Exceptions\UserNotFoundException;
throw (new UserNotFoundException())->setMeta(['user_id' => 123]);
HTTP headers
If you need to send response with custom HTTP headers, you can use the addHeaders()
method:
<?php
use App\Exceptions\UserNotFoundException;
throw (new UserNotFoundException())->addHeaders([
'X-Error-Identifier' => '5a8e4ef9-3e26-4693-9574-a655361e18bb',
]);
To override all previously set headers, use the setHeaders()
method:
<?php
use App\Exceptions\UserNotFoundException;
throw (new UserNotFoundException())->setHeaders([
'X-Error-Identifier' => '5a8e4ef9-3e26-4693-9574-a655361e18bb',
]);
Message translations
If your application supports multiple languages, you can use Laravel's translation system to provide localized error messages along with error message in English (or any other language that was used to describe the MESSAGE
constant).
By default, Laniakea's exception renderer will use the error code (ERROR_CODE
) constant as a part of translation key. It's also uses exceptions.
as the translation namespace and .message
as the final translation segment.
For example, if your error code is users.not_found
, the translation key will be exceptions.users.not_found.message
. That means that Laravel's translation system will look for a translation in lang/<locale>/exceptions.php
file (where <locale>
is the current locale of the application):
<?php
return [
'users' => [
'not_found' => ['message' => 'Пользователь не найден.'],
],
];
If you're OK with this translation paths, there's no need to do anything else. Simply add translations to your lang/<locale>/exceptions.php
file and Laniakea will pick them up. The translated message will be placed under message
key in the JSON response while original message will be placed under original_message
key.
{
"error": {
"message": "Пользователь не найден.",
"original_message": "User was not found.",
"code": "users.not_found",
"meta": []
}
}
TIP
Translated message has a higher priority than the original message. Even if you override the default message with constructor argument, translated message will be used if it's available.
Replacements
Any metadata that you set with addMeta()
or setMeta()
can be used as replacements in the translated message. For example, you can use :user_id
in the translation string and it will be replaced with the actual value of the user_id
key from the metadata.
<?php
use App\Exceptions\UserNotFoundException;
throw (new UserNotFoundException())->setMeta(['user_id' => 123]);
<?php
return [
'users' => [
'not_found' => ['message' => 'Пользователь с ID :user_id не найден.'],
],
];
{
"error": {
"message": "Пользователь с ID 123 не найден.",
"original_message": "User was not found.",
"code": "users.not_found",
"meta": {
"user_id": 123
}
}
}
Use custom translation namespace
If you don't want to use exceptions.
as a namespace, you can override the getTranslationNamespace()
method in your exception class:
<?php
declare(strict_types=1);
namespace App\Exceptions;
use Laniakea\Exceptions\BaseHttpException;
class UserNotFoundException extends BaseHttpException
{
public const MESSAGE = 'User was not found.';
public const ERROR_CODE = 'users.not_found';
public const HTTP_CODE = 404;
protected function getTranslationNamespace(): string
{
return 'users.exceptions.';
}
}
Now the translation key will be users.exceptions.users.not_found.message
.
TIP
Do not forget to add trailing dot (.
) to the namespace string.
Use custom translation path
If you want to override full translation path for a specific exception, you can override the getTranslationPath()
method:
<?php
declare(strict_types=1);
namespace App\Exceptions;
use Laniakea\Exceptions\BaseHttpException;
class UserNotFoundException extends BaseHttpException
{
public const MESSAGE = 'User was not found.';
public const ERROR_CODE = 'users.not_found';
public const HTTP_CODE = 404;
protected function getTranslationPath(): string
{
return 'users.errors.not_found';
}
}
Why the .message
suffix?
The .message
suffix allows you to have nested error codes for similar exceptions. For example, you can have users.not_found
error code (with translation key of users.not_found.message
) and users.not_found.email
error code (with translation key of users.not_found.email.message
).
<?php
return [
'users' => [
'not_found' => [
'message' => 'Пользователь с ID :user_id не найден.',
'email' => ['message' => 'Пользователь с таким email не найден.'],
],
],
];
Render exceptions as views
The Laniakea\Exceptions\RenderableExceptionInterface
interface allows you to specify a custom view for rendering your exception. The view will be rendered instead of the default JSON response but only if current request does not expect a JSON response (i.e. Accept: application/json
header is not present).
Implement the getView()
method in your exception class and return an instance of Illuminate\View\View
to make it work:
<?php
declare(strict_types=1);
namespace App\Exceptions;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Laniakea\Exceptions\BaseHttpException;
use Laniakea\Exceptions\RenderableExceptionInterface;
class UserNotFoundException extends BaseHttpException implements RenderableExceptionInterface
{
public const MESSAGE = 'User was not found.';
public const ERROR_CODE = 'users.not_found';
public const HTTP_CODE = 404;
public function getView(Request $request): View
{
return view('users.errors.not_found', [
'message' => $this->getErrorMessage(),
'metaData' => $this->getMeta(),
]);
}
}