Error Reporting

When you transfer your project to a production server, it is impossible to see the error that every user encounters. In order to intervene early in such cases, you can create an error reporter class as in the following example and call this class from the App\Middleware\ErrorResponseGenerator middleware to ensure that active errors are recorded in the database once and reported by email.

<?php
declare(strict_types=1);

namespace App\Middleware;

use Throwable;
use App\Utils\ErrorMailer;
use Psr\Container\ContainerInterface;
use App\Exception\ConsultationSessionException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

class ErrorResponseGenerator
{
    protected $config;
    protected $container;

    public function __construct(array $config, ContainerInterface $container)
    {
        $this->config = $config;
        $this->container = $container;
    }

    public function __invoke(Throwable $e, ServerRequestInterface $request, ResponseInterface $response)
    {
        $data = $e->getTrace();
        $trace = array_map(
            function ($a) {
                    if (isset($a['file'])) {
                        $a['file'] = str_replace(PROJECT_ROOT, '', $a['file']);
                    }
                    return $a;
                },
            $data
        );        
        $json = [
            'title' => get_class($e),
            'type' => 'https://httpstatus.es/400',
            'status' => 400,
            'file' => str_replace(PROJECT_ROOT, '', $e->getFile()),
            'line' => $e->getLine(),
            'error' => $e->getMessage(),
        ];
        if (getenv('APP_ENV') == 'local') { // enable trace on local mode
            $json['trace'] = $trace;
        }
        $response = $response->withHeader('Access-Control-Allow-Origin', '*');
        $response = $response->withHeader('Access-Control-Allow-Headers', '*');
        $response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
        $response = $response->withHeader('Access-Control-Expose-Headers', 'Token-Expired');
        $response = $response->withHeader('Access-Control-Max-Age', '3600');
        $response = $response->withHeader('Content-Type', 'application/json');
        $response = $response->withStatus(400);
        $response->getBody()->write(json_encode($json));

        // Error mailer
        // 
        if (getenv('APP_ENV') == 'prod') {
            $class = get_class($e);
            if (false === strpos($class, 'App\Exception') 
                AND false === strpos($class, 'Laminas\Validator\Exception')) {
                $errorMailer = $this->container->get(ErrorMailer::class);
                $errorMailer->setException($e);
                $errorMailer->setUri($request->getUri()->getPath());
                $errorMailer->setServerParams($request->getServerParams());
                $errorMailer->send();
            }
        }
        return $response;
    }
}

ErrorResponseGenerator

In order for error reporting to work, the App\Middleware\ErrorResponseGenerator middleware must be defined at the top level in the config/pipeline.php file as follows.

return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {

    $config = $container->get('config');
    $errorHandler = new ErrorHandler(
        function () {
            return new Response;
        },
        new App\Middleware\ErrorResponseGenerator($config, $container)
    );
    $app->pipe($errorHandler);

    // This middleware registers the Mezzio\Router\RouteResult request attribute.
    $app->pipe(RouteMiddleware::class);
}

ErrorMailerFactory

App\Container\ErrorMailerFactory Allows you to inject the ErrorMailer class into other classes.

declare(strict_types=1);

namespace App\Container;

use App\Utils\SmtpMailer;
use App\Utils\ErrorMailer;
use Laminas\Db\TableGateway\TableGateway;
use Laminas\Db\Adapter\AdapterInterface;
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Laminas\I18n\Translator\TranslatorInterface;

class ErrorMailerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $dbAdapter = $container->get(AdapterInterface::class);
        $errors = new TableGateway('errors', $dbAdapter, null);
        $smtpMailer = $container->get(SmtpMailer::class);
        return new ErrorMailer($errors, $smtpMailer);
    }
}

The ErrorMailer object is made ready by configuring the App\Container\ErrorMailerFactory class to the Container\ErrorMailerFactory::class class in the App\ConfigProvider.php file.

public function getDependencies() : array
{
    return [
        'factories' => [
            ErrorMailer::class => Utils\ErrorMailerFactory::class,
        ]
    ]
}
This documents is available for subscribers only

Get Full Accesss