Install the package:
composer require phpro/api-problem
First use case:
Throw an ApiProblemException with a built-in problem type (e.g., NotFoundProblem) in your Laravel controller or service:
use Phpro\ApiProblem\Exception\ApiProblemException;
use Phpro\ApiProblem\Http\NotFoundProblem;
throw new ApiProblemException(new NotFoundProblem('Resource not found'));
Handle exceptions in Laravel:
Use Laravel’s exception handler (app/Exceptions/Handler.php) to convert ApiProblemException into a JSON response:
public function render($request, Throwable $exception)
{
if ($exception instanceof ApiProblemException) {
return response()->json($exception->getProblem()->toArray(), $exception->getProblem()->getStatus());
}
return parent::render($request, $exception);
}
Test locally: Trigger the exception in a route or controller and verify the response matches RFC7807 format:
{
"status": 404,
"type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
"title": "Not found",
"detail": "Resource not found"
}
Standard Error Responses:
Replace generic Laravel exceptions (e.g., HttpResponseException) with ApiProblemException for consistent API responses:
// Before
throw new HttpResponseException(response()->json(['error' => 'Not found'], 404));
// After
throw new ApiProblemException(new NotFoundProblem('Resource not found'));
Validation Errors:
Use ValidationApiProblem for Symfony Validator errors (requires symfony/validator):
use Phpro\ApiProblem\Http\ValidationApiProblem;
use Symfony\Component\Validator\ConstraintViolationList;
$violations = $validator->validate($request->all());
throw new ApiProblemException(new ValidationApiProblem($violations));
Custom Problems:
Extend HttpApiProblem for domain-specific errors:
class PaymentFailedProblem extends HttpApiProblem
{
public function __construct(string $message, ?int $status = 400)
{
parent::__construct($status, [
'detail' => $message,
'type' => 'https://example.com/problems/payment-failed'
]);
}
}
Debug Context:
Implement DebuggableApiProblemInterface to include stack traces in development:
class DebugProblem implements DebuggableApiProblemInterface
{
public function toDebuggableArray(): array
{
return array_merge(
$this->toArray(),
['trace' => debug_backtrace()]
);
}
}
ApiProblemException and format responses globally.ApiProblemException in unit tests to assert response formats:
$this->expectException(ApiProblemException::class);
$this->expectExceptionMessage('Resource not found');
Exception Handling:
ApiProblemException to JSON. Override render() in Handler.php as shown in Getting Started.instanceof checks to avoid masking other exceptions.Debug Information:
ExceptionApiProblem only includes debug data (e.g., stack traces) in debug mode (Laravel’s APP_DEBUG=true). Disable in production to avoid leaking sensitive data.if (app()->environment('local')) {
$problem = new ExceptionApiProblem($exception);
} else {
$problem = new HttpApiProblem(500, ['detail' => $exception->getMessage()]);
}
HTTP Status Codes:
400, 500). Invalid codes may cause issues with Laravel’s response handling.HttpApiProblem to enforce valid codes:
parent::__construct($this->validateStatusCode($status), $data);
ValidationApiProblem:
symfony/validator (≥4.1). Install via Composer:
composer require symfony/validator
Performance:
ApiProblem objects in tight loops (e.g., batch processing). Reuse instances where possible.class ProblemFactory {
public static function notFound(string $detail): NotFoundProblem {
return new NotFoundProblem($detail);
}
}
Missing Responses:
ApiProblemException is caught in Handler.php. Check for typos in exception class names.render() to confirm they’re being processed:
Log::debug('Caught exception:', ['exception' => $exception]);
Invalid JSON:
toArray() returns a valid JSON-serializable structure. Use json_encode($problem->toArray()) to test.public function isValid(): bool {
return json_encode($this->toArray()) !== false;
}
Debug Data Leaks:
APP_DEBUG is false in production. Use Laravel’s config('app.debug') to check dynamically:
if (config('app.debug')) {
$problem = new ExceptionApiProblem($exception);
}
Custom Problem Types:
HttpApiProblem or implementing ApiProblemInterface. Place them in app/ApiProblem for autoloading.RateLimitProblem for API rate limiting.Global Problem Modifiers:
public function boot() {
ApiProblemException::macro('addContext', function ($context) {
$this->getProblem()->addProperty('context', $context);
return $this;
});
}
throw (new ApiProblemException(new NotFoundProblem('Not found')))
->addContext(['user_id' => auth()->id()]);
Localization:
HttpApiProblem to use __():
parent::__construct(404, [
'title' => __('errors.not_found.title'),
'detail' => __('errors.not_found.detail', ['id' => $id])
]);
How can I help you explore Laravel packages today?