honeybadger-io/honeybadger-php

View on GitHub
src/ExceptionNotification.php

Summary

Maintainability
A
1 hr
Test Coverage
A
100%
<?php

namespace Honeybadger;

use ErrorException;
use Honeybadger\Support\Arr;
use Honeybadger\Support\Repository;
use stdClass;
use Symfony\Component\HttpFoundation\Request as FoundationRequest;
use Throwable;

class ExceptionNotification
{
    /**
     * @var \Honeybadger\Config
     */
    protected $config;

    /**
     * @var \Honeybadger\Support\Repository
     */
    protected $context;

    /**
     * @var \Honeybadger\Breadcrumbs
     */
    protected $breadcrumbs;

    /**
     * @var \Throwable
     */
    protected $throwable;

    /**
     * @var \Honeybadger\BacktraceFactory
     */
    protected $backtrace;

    /**
     * @var \Honeybadger\Request
     */
    protected $request;

    /**
     * @var \Honeybadger\Environment
     */
    protected $environment;

    /**
     * @var array
     */
    protected $additionalParams;

    /**
     * @param \Honeybadger\Config $config
     * @param \Honeybadger\Support\Repository $context
     * @param \Honeybadger\Breadcrumbs $breadcrumbs
     */
    public function __construct(Config $config, Repository $context, Breadcrumbs $breadcrumbs)
    {
        $this->config = $config;
        $this->context = $context;
        $this->breadcrumbs = $breadcrumbs;
    }

    /**
     * @param  \Throwable  $e
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @param  array  $additionalParams
     * @return array
     */
    public function make(Throwable $e, FoundationRequest $request = null, array $additionalParams = []): array
    {
        $this->throwable = $e;
        $this->backtrace = $this->makeBacktrace();
        $this->request = $this->makeRequest($request);
        $this->additionalParams = $additionalParams;
        $this->environment = $this->makeEnvironment();

        return $this->format();
    }

    /**
     * @return array
     */
    private function format(): array
    {
        return [
            'breadcrumbs' => [
                'enabled' => $this->config['breadcrumbs']['enabled'],
                'trail' => $this->breadcrumbs->toArray(),
            ],
            'notifier' => $this->config['notifier'],
            'error' => [
                'class' => $this->getExceptionType(),
                'message' => $this->throwable->getMessage(),
                'backtrace' => $this->backtrace->trace(),
                'causes' => $this->backtrace->previous(),
                'fingerprint' => Arr::get($this->additionalParams, 'fingerprint', null),
                'tags' => Arr::wrap(Arr::get($this->additionalParams, 'tags', null)),
            ],
            'request' => [
                // Important to set empty maps to stdClass so they don't get JSON-encoded as arrays
                'cgi_data' => $this->environment->values() ?: new stdClass,
                'params' => $this->request->params() ?: new stdClass,
                'session' => $this->request->session() ?: new stdClass,
                'url' => $this->request->url(),
                'context' => $this->context->except(['honeybadger_component', 'honeybadger_action']) ?: new stdClass,
                'component' => Arr::get($this->additionalParams, 'component', null) ?? Arr::get($this->context, 'honeybadger_component', null),
                'action' => Arr::get($this->additionalParams, 'action', null) ?? Arr::get($this->context, 'honeybadger_action', null),
            ],
            'server' => [
                'pid' => getmypid(),
                'version' => $this->config['version'],
                'hostname' => $this->config['hostname'],
                'project_root' => $this->config['project_root'],
                'environment_name' => $this->config['environment_name'],
            ],
        ];
    }

    /**
     * @return \Honeybadger\Environment
     */
    private function makeEnvironment(): Environment
    {
        return (new Environment)
            ->filterKeys($this->config['environment']['filter'])
            ->include($this->config['environment']['include']);
    }

    /**
     * @return \Honeybadger\BacktraceFactory
     */
    private function makeBacktrace(): BacktraceFactory
    {
        return new BacktraceFactory($this->throwable, $this->config);
    }

    /**
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @return \Honeybadger\Request
     */
    private function makeRequest(FoundationRequest $request = null): Request
    {
        return (new Request($request))
            ->filterKeys($this->config['request']['filter']);
    }

    private function getExceptionType(): string
    {
        // ErrorExceptions are not exceptions, but wrappers around errors.
        if ($this->throwable instanceof ErrorException) {
            $severity = $this->throwable->getSeverity();
            $severityName = $this->friendlyErrorName($severity);

            return $severity == E_DEPRECATED || $severity == E_USER_DEPRECATED
                ? "Deprecation Warning ($severityName)"
                : "Error ($severityName)";
        }

        return get_class($this->throwable);
    }

    protected function friendlyErrorName(int $severity): string
    {
        // See https://www.php.net/manual/en/errorfunc.constants.php
        return [
            E_ERROR => 'E_ERROR',
            E_WARNING => 'E_WARNING',
            E_PARSE => 'E_PARSE',
            E_NOTICE => 'E_NOTICE',
            E_CORE_ERROR => 'E_CORE_ERROR',
            E_CORE_WARNING => 'E_CORE_WARNING',
            E_COMPILE_ERROR => 'E_COMPILE_ERROR',
            E_COMPILE_WARNING => 'E_COMPILE_WARNING',
            E_USER_ERROR => 'E_USER_ERROR',
            E_USER_WARNING => 'E_USER_WARNING',
            E_USER_NOTICE => 'E_USER_NOTICE',
            E_STRICT => 'E_STRICT',
            E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
            E_DEPRECATED => 'E_DEPRECATED',
            E_USER_DEPRECATED => 'E_USER_DEPRECATED',
        ][$severity] ?? '';
    }
}