abc/api-problem
Lightweight PHP library for representing API errors using RFC 7807 “Problem Details for HTTP APIs”. Create ApiProblem instances with type, title, status, detail, and instance, then serialize to JSON for consistent error responses.
Installation:
composer require abc/api-problem
Add the namespace to your composer.json autoload or use use Abc\ApiProblem; directly.
First Use Case: Return a standardized API error response in a Laravel controller:
use Abc\ApiProblem;
use Symfony\Component\HttpFoundation\Response;
public function show($id)
{
if (!Resource::find($id)) {
$problem = new ApiProblem(
url('/api/resource'),
'Resource Not Found',
Response::HTTP_NOT_FOUND,
"Resource with ID {$id} not found"
);
return response()->json($problem->toJson(), 404);
}
// ... success logic
}
Where to Look First:
ApiProblem class: Focus on its constructor and toJson() method.Response or middleware for consistency.Error Handling Middleware:
Catch exceptions and convert them to ApiProblem responses:
use Abc\ApiProblem;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
public function handle(NotFoundHttpException $e, $next)
{
$problem = new ApiProblem(
url('/api'),
'Not Found',
404,
$e->getMessage()
);
return response()->json($problem->toJson(), 404);
}
Validation Errors:
Transform Laravel’s Validator errors into ApiProblem:
$validator = Validator::make($request->all(), [...]);
if ($validator->fails()) {
$problem = new ApiProblem(
url('/api/validate'),
'Validation Error',
422,
$validator->errors()->first()
);
return response()->json($problem->toJson(), 422);
}
Extending with Custom Fields:
Add RFC-compliant extensions (e.g., instance, about, or custom fields):
$problem = new ApiProblem(
url('/api/resource'),
'Conflict',
409,
'Resource already exists',
null,
['instance' => url("/api/resource/{$id}")]
);
API Versioning:
Use the type field to distinguish between API versions:
$problem = new ApiProblem(
url('/api/v1/resource'),
'Unsupported Media Type',
415,
'Only JSON is supported',
null,
['type' => 'about:schemas.org/errors/v1/unsupported-media-type']
);
ApiProblem in a helper for consistency:
if ($error) {
return apiProblemResponse($error, 400, 'Bad Request');
}
ApiProblem in unit tests to verify error responses:
$this->expectException(ApiProblem::class)
->expectJson($problem->toJson());
RFC Compliance:
type field must be a URI (e.g., about:schemas.org/errors/resource-not-found).instance, title, detail) if unused, but ensure required fields (type, status, detail) are present.JSON Serialization:
toJson() method returns a string. Parse it with json_decode() if further manipulation is needed:
$data = json_decode($problem->toJson(), true);
Status Codes:
Response constants (e.g., Response::HTTP_NOT_FOUND) are preferred over raw integers for clarity.Localization:
title and detail fields are not automatically translated. Localize them manually or use Laravel’s __() helper:
$problem = new ApiProblem(
url('/api/resource'),
__('errors.resource_not_found.title'),
404,
__('errors.resource_not_found.detail', ['id' => $id])
);
var_dump($problem) to inspect the object structure before calling toJson().type and instance fields with filter_var($uri, FILTER_VALIDATE_URL).ApiProblem is created (e.g., avoid app->abort() in middleware).Custom Problem Types:
Extend the ApiProblem class to add domain-specific fields:
class CustomProblem extends ApiProblem {
public function __construct($type, $status, $detail, $customField) {
parent::__construct($type, $status, $detail);
$this->customField = $customField;
}
}
Laravel Service Provider:
Bind ApiProblem to the container for dependency injection:
$this->app->bind(ApiProblem::class, function () {
return new ApiProblem(...);
});
API Problem Factory: Create a factory class to standardize problem creation:
class ProblemFactory {
public static function notFound($id) {
return new ApiProblem(
url('/api/resource'),
'Not Found',
404,
"Resource {$id} not found"
);
}
}
Logging:
Log ApiProblem instances for analytics or debugging:
\Log::error('API Problem', [
'type' => $problem->type,
'status' => $problem->status,
'detail' => $problem->detail,
'instance' => $problem->instance ?? null,
]);
How can I help you explore Laravel packages today?