How can I return an ApiProblem when an error occurs pulling a controller from the container?

zf-api-problem

#1

Sometimes an error occurs when pulling a controller (or one of its dependencies) from the container. In such cases, an ApiProblem is not returned; instead, the default error page for the application is returned, which can cause problems for API clients.


#2

When a container error occurs, it is caught by the DispatchListener, which then leads to triggering the dispatch.error event. This event is not normally handled by Apigility, so we need to get creative, and create a listener that:

  • detects if the triggered event is due to an exception
  • detects if the controller leading to the exception is one managed by Apigility
  • returns an ApiProblemResponse in such situations

The following is an example demonstrating the listener, a factory for creating it, and a sample Module that wires the listener onBootstrap.

namespace ApiProblemDispatchError;

use Psr\Container\ContainerInterface;
use Zend\Mvc\Application;
use Zend\Mvc\MvcEvent;
use ZF\ApiProblem\ApiProblem;
use ZF\ApiProblem\ApiProblemResponse;

class ApiProblemDispatchErrorListener
{
    private $apiControllers;

    public function __construct(array $controllers)
    {
        $this->apiControllers = $controllers;
    }

    public function __invoke(MvcEvent $e)
    {
        if ($e->getError() !== Application::ERROR_EXCEPTION) {
            return;
        }

        if (! in_array($e->getController(), $this->apiControllers, true)) {
            return;
        }

        $problem = new ApiProblem(500, $e->getParam());
        return new ApiProblemResponse($problem);
    }
}

class ApiProblemDispatchErrorListenerFactory
{
    public function __invoke(ContainerInterface $container)
    {
        $config          = $container->get('config');
        $restControllers = isset($config['zf-rest']) ? array_keys($config['zf-rest']) : [];
        $rpcControllers  = isset($config['zf-rpc']) ? array_keys($config['zf-rpc']) : [];
        $apiControllers  = array_merge($restControllers, $rpcControllers);

        return new ApiProblemDispatchErrorListener($apiControllers);
    }
}

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $app = $e->getApplication();
        $contaienr = $app->getServiceManager();
        $events = $app->getEventManager();
        $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, $container->get(ApiProblemDispatchErrorListener::class), 100);
    }
}