• Stars
    star
    124
  • Rank 288,207 (Top 6 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 8 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An Laravel Exception handler build specifically for APIs.

Heimdal

Latest Version Software License Build Status Coverage Status Total Downloads

Introduction

Heimdal is a Laravel exception handler build specifically for APIs.

Why is it needed?

When building APIs there are specific formatting do's and dont's on how to send errors back to the user. Frameworks like Laravel are not build specifically for API builders. This small library just bridges that gap. For instance, specifications like JSON API have guidelines for how errors should be formatted.

Installation

composer require optimus/heimdal ~1.0

Add the service provider to config/app.php

// other providers...
Optimus\Heimdal\Provider\LaravelServiceProvider::class,

Publish the configuration.

php artisan vendor:publish --provider="Optimus\Heimdal\Provider\LaravelServiceProvider"

Add the exception handler to bootstrap/app.php

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    Optimus\Heimdal\ExceptionHandler::class
);

What does it do?

Imagine you have a piece of code that throws an InvalidArgumentException. This is a server error (500). It will parse through the flow described below.

1. Exception is thrown
2. The Exception is parsed through reports. A reporter is a class that reports the Exception. Log it in logs, send to external trackers like Sentry, Bugsnag etc.
3. The Exception is parsed through an appropriate formatter that formats the response in accordance to the error type.
4. The response is sent to the user.

Is this not what Laravel does?

Yes, pretty much. However, if you want to report to something like Sentry you usually do this through something like Monolog. The problem with Monolog is that it is difficult to pick up the response of the reporters. For instance, Sentry reports back a unique ID for every reported exception which is extremely useful to give to the user, so they can give it to customer support. Heimdal supports this out-of-the-box by giving the response of all reporters to the formatter classes. This makes it trivial for formatters to use the response of the reporters in their final response to the user.

Second, Heimdal comes with sensible defaults as how different error types should be reported to the user. And makes it trivial to implement alternative responses for specific exception types.

Configuration

Heimdal has two things that should be configured: formatters and reporters.

Reporters

You should determine where your exceptions should be reported to. Heimdal still calls the base report function in Laravel, so your exceptions will still be logged as normal. However, adding external reporting is easy. Heimdal comes with Sentry integration out of the box. To send exceptions to Sentry simply add this entry to the reporters section in config/optimus.heimdal.php

'sentry' => [
    'class'  => \Optimus\Heimdal\Reporters\SentryReporter::class,
    'config' => [
        'dsn' => '',
        // For extra options see https://docs.sentry.io/clients/php/config/
        // php version and environment are automatically added.
        'sentry_options' => []
    ]
]

Adding a custom reporter, for instance Bugsnag, is as simple as writing a small reporter class like this

<?php

namespace My\Namespace\Exceptions\Reporters;

use Optimus\Heimdal\Reporters\ReporterInterface;

class BugsnagReporter implements ReporterInterface
{
    public function __construct(array $config)
    {
        // initialize with config
    }

    public function report(Exception $e)
    {
        // report to bugsnag
    }
}

And then add it to config/optimus.heimdal.php

'bugsnag' => [
    'class'  => \My\Namespace\Exceptions\Reporters\BugsnagReporter::class,
    'config' => [
        // config.
    ]
]

Formatters

Heimdal already comes with sensible formatters out of the box. In config/optimus.heimdal.php is a section where the formatter priority is defined.

'formatters' => [
    SymfonyException\UnprocessableEntityHttpException::class => Formatters\UnprocessableEntityHttpExceptionFormatter::class,
    SymfonyException\HttpException::class => Formatters\HttpExceptionFormatter::class,
    Exception::class => Formatters\ExceptionFormatter::class,
],

The higher the entry, the higher the priority. In this example, a UnprocessableEntityHttpException will be formatted used the UnprocessableEntityHttpExceptionFormatter because it is the first entry. However, an NotFoundHttpException will not match UnprocessableEntityHttpException but will match HttpException (since it is a child class hereof) and will therefore be formatted using the HttpExceptionFormatter.

You can write custom formatters easily:

<?php

namespace My\Namespace\Exceptions\Formatters;

use Exception;
use Illuminate\Http\JsonResponse;
use Optimus\Heimdal\Formatters\BaseFormatter;

class NotFoundHttpExceptionFormatter extends BaseFormatter
{
    public function format(JsonResponse $response, Exception $e, array $reporterResponses)
    {
        $response->setStatusCode(404);
        $data = $response->getData(true);

        if ($this->debug) {
            $data = array_merge($data, [
                'code'   => $e->getCode(),
                'message'   => $e->getMessage(),
                'exception' => (string) $e,
                'line'   => $e->getLine(),
                'file'   => $e->getFile()
            ]);
        } else {
            $data['message'] = [
                'message' => 'The resource was not found.',
                'log_id' => $reporterResponses['sentry']['sentry_id']
            ]
        }

        $response->setData($data);
    }
}

Notice how easily we used $reporterResponses to attach the ID of the Sentry log to the JSON response. Now we simply add it to config/optimus.heimdal.php

'formatters' => [
    SymfonyException\UnprocessableEntityHttpException::class => Formatters\UnprocessableEntityHttpExceptionFormatter::class,
    SymfonyException\NotFoundHttpException::class => My\Namespace\Exceptions\Formatters\NotFoundHttpExceptionFormatter::class,
    SymfonyException\HttpException::class => Formatters\HttpExceptionFormatter::class,
    Exception::class => Formatters\ExceptionFormatter::class,
],

Now all NotFoundHttpExceptions will be formatted using our custom formatter.

Available Reporters

Sentry

To send Exceptions to Sentry add the following reporter configuration in config/optimus.heimdal.php.

'reporters' => [
    'sentry' => [
        'class'  => \Optimus\Heimdal\Reporters\SentryReporter::class,
        'config' => [
            'dsn' => '',
            // For extra options see https://docs.sentry.io/clients/php/config/
            // php version and environment are automatically added.
            'sentry_options' => []
        ]
    ]
]

Adding context at runtime

Sometimes you want to add information at runtime, like request data, user information or similar. For this you can add the add_context key to the config array. Below is an example of how it could be implemented.

'reporters' => [
    'sentry' => [
        'class'  => \Optimus\Heimdal\Reporters\SentryReporter::class,
        'config' => [
            'dsn' => env('SENTRY_DSN'),
            // For extra options see https://docs.sentry.io/clients/php/config/
            // php version and environment are automatically added.
            'add_context' => function (Exception $e) {
                $context = [
                    'environment' => app()->environment(),
                    'release' => \Infrastructure\Version::getGitTag()
                ];

                $user = \Auth::User();
                if ($user) {
                    $context['user'] = [
                        'id' => $user->id,
                        'email' => $user->email,
                    ];
                } else {
                    $context['user'] = [];
                }

                // When running in console request is not available
                if (substr(php_sapi_name(), 0, 3) !== 'cli') {
                    $request = app('request');

                    if (!isset($context['extra'])) {
                        $context['extra'] = [];
                    }

                    $context['extra']['request_data'] = json_encode($request->all());
                    $context['user']['ip_address'] = \Request::getClientIp();
                }

                return $context;
            }
        ]
    ]
]

Bugsnag

Thanks to Nikolaj LΓΈvenhardt Petersen for adding support

Install Bugsnag using the Laravel installation guide

To send Exceptions to Bugsnag add the following reporter configuration in config/optimus.heimdal.php.

'reporters' => [
    'bugsnag' => [
        'class'  => \Optimus\Heimdal\Reporters\BugsnagReporter::class,
        'config' => []
    ]
]

Rollbar

To send Exceptions to Rollbar add the following reporter configuration in config/optimus.heimdal.php.

'reporters' => [
'rollbar' => [
        'class'  => \Optimus\Heimdal\Reporters\RollbarReporter::class,
        'config' => [
            'access_token' => '',
            'environment' => 'production'
        ]
    ]
]

The complete list of config options can be found in here

Standards

This package is compliant with PSR-1, PSR-2 and PSR-4. If you notice compliance oversights, please send a patch via pull request.

Testing

$ phpunit

Contributing

Please see CONTRIBUTING for details.

License

The MIT License (MIT). Please see License File for more information.

More Repositories

1

pdf-bot

πŸ€– A Node queue API for generating PDFs using headless Chrome. Comes with a CLI, S3 storage and webhooks for notifying subscribers about generated PDFs
JavaScript
2,595
star
2

react-native-clean-form

Easy react-native forms using bootstrap-like syntax with redux-form+immutablejs integration. Styled using styled-components
JavaScript
479
star
3

larapi

An API-friendly fork of Laravel. Authentication, error handling, resource filtering, sorting, pagination and much more included
PHP
404
star
4

bruno

A base API controller for Laravel that gives sorting, filtering, eager loading and pagination for your resources
PHP
164
star
5

onion

A before/after middleware implementation
PHP
72
star
6

lumen-api-oauth

The code for a blog post I wrote about creating web apps using a Lumen API that is authenticated by OAuth2
PHP
61
star
7

oauth2-server-lumen

A lumen bridge for lucadegasperi/oauth2-server-laravel
PHP
56
star
8

jquery-oauth

A $.ajax wrapper for OAuth 2 access and refresh token management for use in a SPA
JavaScript
52
star
9

laravel-api-consumer

PHP
39
star
10

architect

Dynamically create API resource structures for related resources
PHP
39
star
11

redux-refresh-token

An extension to your Redux API middleware that will refresh access tokens when hitting a 401 response. Works with middlewares that use FSA-style actions.
JavaScript
37
star
12

distributed-laravel

PHP
30
star
13

genie

A base repository class for Eloquent to abstract away persistence layer from your business code
PHP
27
star
14

react-native-redux-form-example

Code for http://esbenp.github.io/2017/01/06/react-native-redux-form-immutable-styled-components/
JavaScript
22
star
15

wp-darkshell

A Next-Gen Wordpress Hacker Backend
PHP
15
star
16

fineuploader-server

PHP
7
star
17

pdf-bot-laravel

πŸ€– Laravel integration for pdf-bot. A Node microservice for generating PDFs using headless Chrome
PHP
5
star
18

larapi-series-part-2

PHP
4
star
19

cloudinary-url-resolver

Convert Cloudinary transformations and public ids to Cloudinary resource urls
JavaScript
4
star
20

laravel-batch-request

Batch consume own api
PHP
3
star
21

esbenp.github.io

Blog site
JavaScript
3
star
22

larapi-part-3

PHP
3
star
23

redux-refresh-token-example

Example app for redux-refresh-token
JavaScript
3
star
24

fineuploader-server-thumbnail-creator

PHP
3
star
25

fineuploader-server-cloudinary

PHP
2
star
26

knockout-null-observable

JavaScript
1
star
27

sentinel-lumen

PHP
1
star
28

react-native-swift-test

Objective-C
1
star
29

fineuploader-client-knockout

JavaScript
1
star
30

knockout-bootstrap-modal

JavaScript
1
star
31

laravel-package-boilerplate

PHP
1
star
32

knockout-subscriptions-manager

JavaScript
1
star
33

knockout-bulk-table

JavaScript
1
star
34

fineuploader-client

JavaScript
1
star
35

react-init

Boilerplate: Flow, Immutable, Redux, Redux Immutable, React Router, Styled Components, Webpack 3. Based on an ejected create-react-app.
JavaScript
1
star