Ignition: Новая страница ошибок для Ларавел

30 августа, на конференции Laracon EU, мы с Marcel Pociot представили Ignition, новую великолепную страницу ошибок для Laravel. Она станет дефолтной в Laravel 6, но вы можете вручную установить его для Laravel 5. В этой статье я хотел бы рассказать вам об этом всём подробно.

Ключ на старт!

Мы любим создавать пакеты. Но мы не единственные, кто активно создает опенсорсные вещи для Laravel и PHP. Мой друг Marcel Pociot и его компания Beyond Code тоже внесли большой вклад. В совокупности наши команды создали более 300 пакетов, которые были загружены более 50 миллионов раз.

Время от времени мы с Марселем обмениваемся отзывами и помогаем друг другу. В прошлом году даже вместе собрали пакет: laravel-websockets. Этот пакет предоставляет полную реализацию серверных веб-сокетов. Замена для Pusher.

Нам было довольно фаново работать как дуэт, поэтому логично, что мы решили продолжить. Была пара идей. Одной из них были сообщения об ошибках.

В то время я посетил семинар по Elm, который проводил мой приятель Steven Vandevelde. И мне очень сильно запомнилось, что в Elm классные экраны ошибок.

Это убедило нас, что можно улучшить экраны ошибок для ларавелной экосистемы. Практически сразу мы поняли, что если хотим решить эту проблему правильно, нам потребуется много времени и помощь наших команд. Мы поговорили со всеми участниками и вместе основали новую фирму под названием Facade. И над этим начали работать уже все вместе.

Мы с Марселем, может и публичные фигуры, но не смогли бы ничего сделать без наших команд. От них пришло много хороших идей. Вот список в Твиттере всех членов команды Spatie, а это твиттер Себастьяна из Beyond Code. Обязательно подпишитесь на него.

Мы создали новый сервис отслеживания ошибок под названием Flare (вы можете почитать о нём в блоге) и новую страницу ошибок под названием Ignition.

В этоё статье я расскажу всё о Ignition.

Из Elm в Ignition

Давайте посмотрим на несколько экранов ошибок из Elm. Обратите внимание, что они фокусируется не только на ошибке, но и на её решениях.

Вот еще один, он даёт ссылки на документацию.

А теперь давайте посмотрим, что выдаст PHP по дефолту. Без фреймворка он покажет только ошибку: без трассировки стека, без запроса или сведений о приложении.

Страница ошибок Symfony немного получше, она показывает трассировку стека, но это не очень-то и помогает.

Следующий скриншот — Whoops, стандарт в Laravel 5. Он намного лучше, чем дефолтный из Symfony, показывая трассировку стека и некоторую информацию о запросе. Несмотря на то, что в Laravel по умолчанию используется Whoops, но он не зависит от фреймворка. Он показывает только общую информацию.

А вот скриншот Ignition, нового экрана ошибок, созданного нами. Поскольку сделано специально для Laravel, мы смогли реализовать много крутых вещей.

Изучаем Ignition

Давайте рассмотрим все фишки Ignition. Код проекта открыт, и вы можете изучить его здесь.

Если у вас есть ошибки в шаблонах, то вот так их отображает Whoops. Обратите внимание, что сообщение об исключении не соответствует выделенному пространству. Вы должны навести на него курсор, чтобы увидеть полностью. В трассировке стека вы видите, что используются скомпилированные шаблоны и контент Blade. Это затрудняет отслеживание того, какой шаблон Blade содержит ошибку, а само его содержимое нельзя посмотреть.

Ignition — страница ошибок, сделанная специально для Laravel. Она может подключиться к фреймворку, чтобы показать нескомпилированный путь к шаблону и сам шаблон Blade. Вверху достаточно места для отображения всей страницы исключений, никаких дополнительных кликов не требуется. Мы также отображаем только фреймы приложения по дефолту, так как именно они вам скорей всего и интересны.

Если кликнуть на иконку карандаша, рядом с именем файла в правой части вкладки трассировки стека, то автоматически откроется файл в вашем любимом редакторе. По умолчанию это PhpStorm. Вы можете настроить своей любимый редактор в файле конфигурации Ignition.

Заметили небольшую ссылку «Телескоп» в правом верхнем углу? Мы покажем её только в том случае, если  установлен Laravel Telescope. Если вы нажмете эту ссылку, вы попадете на исключение в Телескопе, где произошла ошибка.

Темная тема

Если наш дефолтная тема слишком яркая для вас, то вы будете рады узнать, что у нас есть и темный режим.

Вкладки Ignition

Давайте рассмотрим отображаемые вкладки.

Вкладка запроса (request)

Рядом со вкладкой «Stack trace» вы увидите вкладку «Request». Он отображает всю информацию о запросе.

Вкладка приложения (app)

На вкладке «App» уже интереснее. Она отображает специфическую информацию Laravel о вашем приложении. Сначала мы покажем вам информацию о маршруте, где произошло исключение. Включая контроллер, который был вызван из текущего запроса. И, если доступно, то имя маршрута.

Еще одна интересная особенность — возможность проверить параметры вашего маршрута.

Допустим, у вас есть такое определение маршрута:

Route::get('/posts/{post}', function (Post $post) {
  //
});

Когда вы поймаете исключение на этом маршруте, то мы покажем вам toArray модели Post, как часть параметров вашего маршрута. То же самое касается «простых» параметров маршрута, которые не требуют привязок. Это хороший способ увидеть, какую информацию Laravel получил для этого конкретного маршрута.

После параметров маршрута мы показываем вам список мидлваров, которые использовались в этом запросе.

Далее у нас есть раздел «View». Если исключение произошло в представлении, мы покажем вам здесь имя шаблона. Более того: мы также покажем список всех данных, которые были переданы в него.

Вкладка пользователя (user)

Вкладка «User» содержит дополнительную информацию о том, кто использует приложение и какой используется браузер.

Вкладка контекста (context)

На вкладке «Context» отображается информацию о вашем репозитории (где находится, хэш коммита) и о среде (версии PHP и Laravel).

Вкладка отладки (debug)

На вкладке «Debug» показывается то, что было до возникновения ошибки. Запросы, журнал и дамп. Рядом с дампом также отображается имя файла, в котором находится этот оператор dump. Клик по иконке с карандашом перенесет вас прямо в этот файл и на ту самую строку в ваш любимый редактор.

Предлагая решения

Давайте посмотрим еще на одну ошибку. На этот раз мы забыли импортировать класс. Так выглядит страница Ignition для подобной ошибки.

Ignition видит, что исключение касается класса, который не был найден. Он попытается выяснить, есть ли класс с таким именем в другом пространстве имен. Если он есть, он предложит импортировать его.

Ignition содержит массу готовых решений для основных проблем. Вот один из случаев, когда не найден шаблон Blade.

Вы также можете добавить свои решения для собственных исключений. Ваш исключение должно реализовать Facade\IgnitionContracts\ProvidesSolutions. Вам потребуется добавить функцию getSolution. Вот возможная реализация.

namespace App\Exceptions;

use Exception;
use Facade\IgnitionContracts\Solution;
use Facade\IgnitionContracts\BaseSolution;
use Facade\IgnitionContracts\ProvidesSolution;

class CustomException extends Exception implements ProvidesSolution
{
    public function getSolution(): Solution
    {
          return BaseSolution::create("You're doing it wrong")
            ->setSolutionDescription('You are obviously doing something wrong. Check your code and try again.')
            ->setDocumentationLinks([
                'Laracasts' => 'https://laracasts.com',
                'Use Flare' => 'https://flareapp.io',
            ]);
    }
}

Вот так будет выглядеть исключение в Ignition.

Запускаемые решения

Помимо простого предложения решений, мы также можем запустить их. Например, вы забыли установить ключ приложения. Вот как выглядит эта ошибка в Ignition.

Если вы нажмете кнопку «Generate app key», то сгенерируется и установится ключ приложения в фоновом режиме.

После обновления страницы ваше приложение будет работать (если конечно оно не содержит других ошибок).

Вот видео, где вы можете увидеть несколько готовых решений в действии. Мы пытаемся сохранить модель, но забыли сгенерировать ключ приложения и создать базу данных. Решения создадут приложение, установят правильные учетные данные БД и выполнят наши миграции.

Вы можете создавать свои запускаемые решения, позволяя исключению реализовывать Facade\IgnitionContracts\ProvidesSolution (как и обычные решения). Метод getSolution может возвращать как запускаемые, так и обычные решения.

namespace App\Exceptions;

use Exception;
use Facade\IgnitionContracts\ProvidesSolution;

class CustomException extends Exception implements ProvidesSolution
{
    public function getSolution(): Solution
    {
          return new MyRunnableSolution();
    }
}

Вот пример реализации MyRunnableSolution.

namespace App\Solutions;

use Facade\IgnitionContracts\RunnableSolution;

class MyRunnableSolution implements RunnableSolution
{
    public function getSolutionTitle(): string
    {
        return 'You are doing it wrong';
    }

    public function getSolutionDescription(): string
    {
        return 'You are doing something wrong, but we can fix it for you.';
    }

    public function getDocumentationLinks(): array
    {
        return [];
    }

    public function getSolutionActionDescription(): string
    {
        return 'To fix this issue, all you need to do is press the button below.';
    }

    public function getRunButtonText(): string
    {
        return 'Fix this for me';
    }

    public function run(array $parameters = [])
    {
        // Your solution implementation
    }

    public function getRunParameters(): array
    {
        return [];
    }
}

Так будет выбрасываться CustomException в Ignition.

Функция run запустится, как только пользователь нажмет кнопку «Fix this for me».

Вы можете передавать параметры из запроса, в котором произошло ваше исключение, в запрос, в котором будет выполняться решение. Просто пусть getRunParameters возвращает массив. Этот массив будет передан в run.

Делаем Ignition умнее

Таким образом, у вас есть возможность улучшить ваши собственные исключения с помощью текстовых или запускаемых решений. Но иногда было бы неплохо предложить решения для встроенных исключений PHP или даже сторонних исключений, когда вы не можете контролировать код.

Можно делать это с помощью Провайдеров Решений (Solution Providers). Провайдеры решений — это классы, которые могут подключиться к процессу поиска решения из Ignition. Когда генерируется исключение и Ignition получает его, может быть вызван ваш пользовательский провайдер решений и вернуть одно или несколько возможных решений для этого исключения.

Например, вы можете создать собственного провайдера решений «Stack Overflow», который попытается найти соответствующие ответы на stackoverflow для данного исключения и вернуть их в качестве решений.

Мы также используем провайдеров решений в самом Ignition. Вот как выглядит один из таких из них:

use Throwable;
use RuntimeException;
use Facade\IgnitionContracts\Solution;
use Facade\Ignition\Solutions\GenerateAppKeySolution;
use Facade\IgnitionContracts\HasSolutionForThrowable;

class MissingAppKeySolutionProvider implements HasSolutionForThrowable
{
    public function canSolve(Throwable $throwable): bool
    {
        if (! $throwable instanceof RuntimeException) {
            return false;
        }

        return $throwable->getMessage() === 'No application encryption key has been specified.';
    }

    public function getSolutions(Throwable $throwable): array
    {
        return [
            new GenerateAppKeySolution()
        ];
    }
}

Эти провайдеры решений могут автоматически регистрироваться в Ignition следующим образом:

namespace App\Providers;

use App\Solutions\GenerateAppKeySolution;
use Facade\IgnitionContracts\SolutionProviderRepository;
use Illuminate\Support\ServiceProvider;

class YourServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register(SolutionProviderRepository $solutionProviderRepository)
    {
        $solutionProviderRepository->registerSolutionProvider(GenerateAppKeySolution::class);
    }
}

Таким образом, провайдеры решений расширяют возможности Ignition по предоставлению решений для ваших исключений, и мы c нетерпением ждём, что придумает сообщество!

Настройка Ignition

Мы сделали Ignition расширяемым. Вы можете добавлять новые вкладки или заменять дефолтные.

Давайте взглянем на предоставленную нами facade/ignition-tinker-tab. Пакет представляет собой обертку вокруг spatie/laravel-web-tinker, которая позволяет использовать Artisan tinker в браузере.

С установленным facade/ignition-tinker-tab вы можете использовать artisan tinker прямо на странице ошибок.

Мы также создали второй пакет, который называется facade/ignition-code-editor. Он заменит вкладку трассировки стека и позволит редактировать код прямо на экране ошибки. Вот как это происходит.

Чтобы узнать, как добавить пользовательские вкладки, смотрите документацию по добавлению вкладок.

Обмен локальными ошибками через Flare

Одна из фич новых страниц ошибок — возможность делиться ошибками с другими. Рядом с вкладкой Debug есть кнопка Share. Нажмите на нее, появится всплывающее меню, которое позволит вам выбрать, какая информация будет передана.

Если вы нажмете на него, и вы увидите две ссылки.

Публичная ссылка для общего доступа предназначена для того, чтобы вы могли расшарить ошибку для кого-угодно. Сохраните себе админскую ссылку. Через неё можно будет удалить расшаренную ошибку.

Вот как выглядит расшаренная ошибка.

Сообщать обо всех ошибках во Flare

На скриншоте выше вы, вероятно, заметили, что расшаренная ошибка показана через flareapp.io. Flare — это новый специальный трекер исключений Laravel, который мы запустили вместе с Ignition.

Большинство функций, которые Ignition обеспечивает Ignition, действительны и для Flare. Если вы сообщите об ошибке во flare для исключения, у которого уже есть решение, мы отобразим это решение во Flare.

Чтобы узнать больше о Flare, прочтите эту статью.

Заключительные мысли

Если вы хотите узнать, как работает страница ошибок, загляните в репозиторий Ignition на GitHub. Readme пакета находится на страницах документации Flare.

Мы работали над этим проектом около восьми месяцев вместе с командой очень талантливых людей. По нашему мнению, новая страница ошибок является существенным улучшением Whoops, использовавшуюся в Laravel 4 и 5. Мы надеемся, что вам она понравится так же, как и нам.

Авторы: Marcel Pociot & Freek Van der Herten
Перевод: Demiurge Ash