Installation:
composer require alterway/rest-problem-bundle
Enable the bundle in config/bundles.php:
return [
// ...
Alterway\Bundle\RestProblemBundle\AwRestProblemBundle::class => ['all' => true],
];
First Use Case:
Return a ProblemResponse for invalid form submissions:
use Alterway\Bundle\RestProblemBundle\Response\ProblemResponse;
use Alterway\Bundle\RestProblemBundle\Problem;
public function submitAction(Request $request)
{
$form = $this->createForm(YourFormType::class);
$form->handleRequest($request);
if (!$form->isValid()) {
$problem = new Problem\InvalidQueryForm($form);
return new ProblemResponse($problem, Response::HTTP_BAD_REQUEST);
}
}
src/Problem/ (Default problem types like InvalidQuery, InvalidQueryForm).src/Response/ProblemResponse.php (Core response class).src/Controller/Annotations/Problem.php (Annotation for automatic problem handling).Manual Problem Responses:
// For custom errors (e.g., 404 with details)
$problem = new Problem\CustomProblem('Resource not found', 'https://example.com/probs/resource-not-found');
return new ProblemResponse($problem, Response::HTTP_NOT_FOUND);
Annotation-Driven Problems:
use Alterway\Bundle\RestProblemBundle\Controller\Annotations\Problem;
/**
* @Problem
*/
public function createAction(Request $request)
{
// Automatically wraps exceptions in ProblemResponse with 500 status.
}
Extending Problem Types:
Create a custom problem class (e.g., src/Problem/ValidationProblem.php):
namespace App\Problem;
use Alterway\Bundle\RestProblemBundle\Problem\Problem;
class ValidationProblem extends Problem
{
public function __construct(array $errors)
{
parent::__construct('Validation failed', 'https://example.com/probs/validation-failed');
$this->setDetail($errors);
}
}
Integration with Symfony’s Exception Listener:
Override the default exception handler to use ProblemResponse:
# config/packages/framework.yaml
framework:
exception_listener:
enabled: false # Disable Symfony's default handler
type field in problems to version your API errors (e.g., type: "https://api.example.com/v1/probs/...").$this->logger->error('Problem occurred', ['problem' => $problem->toArray()]);
ProblemResponse in unit tests:
$this->assertInstanceOf(ProblemResponse::class, $response);
$this->assertEquals(400, $response->getStatusCode());
Deprecated Symfony 2.x:
nelmio/api-doc-bundle (which supports Problem Details natively).ProblemResponse in Symfony 3+).Annotation Setup:
config/packages/annotations.yaml:
annotations:
autoload_resource: true
kernel.php.Problem Type Discovery:
src/Problem/).Header Mismatch:
Content-Type: application/api-problem+json. Some clients expect application/problem+json (RFC 7807). Override the ProblemResponse class to fix this:
class CustomProblemResponse extends ProblemResponse
{
public function getContentType()
{
return 'application/problem+json';
}
}
Inspect Problem Data: Dump the problem object to verify fields:
$problem = new Problem\InvalidQueryForm($form);
dump($problem->toArray()); // Check structure
Check Status Codes:
Ensure the HTTP status code matches the problem severity (e.g., 4xx for client errors, 5xx for server errors).
Validate RFC Compliance:
Use tools like Postman or Swagger UI to validate the Problem response structure against RFC 7807.
Custom Problem Fields:
Extend the Problem class to add custom fields (e.g., setMeta()):
class CustomProblem extends Problem
{
protected $meta = [];
public function setMeta(array $meta)
{
$this->meta = $meta;
return $this;
}
public function toArray()
{
$array = parent::toArray();
$array['meta'] = $this->meta;
return $array;
}
}
Global Problem Handling:
Create a subscriber to convert exceptions to ProblemResponse:
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class ProblemSubscriber implements EventSubscriberInterface
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getThrowable();
$problem = new Problem\ServerError($exception);
$event->setResponse(new ProblemResponse($problem, Response::HTTP_INTERNAL_SERVER_ERROR));
}
public static function getSubscribedEvents()
{
return [
KernelEvents::EXCEPTION => 'onKernelException',
];
}
}
Localization:
Override the getTitle()/getDetail() methods to support translations:
class LocalizedProblem extends Problem
{
public function getTitle()
{
return $this->translator->trans('problem.title', [], 'errors');
}
}
How can I help you explore Laravel packages today?