Ignition: a new error page for Laravel
Today at Laracon EU, Marcel Pociot and I unveiled Ignition, a beautiful new error page for Laravel. It is the new default error screen in Laravel 6 and you can manually install it into Laravel 5 applications. In this blog post, I'd like to tell you all about it.
Starting the ignition
My team and I love to create packages. But we're not the only ones that are very active in creating open-source stuff for the Laravel and PHP community. My friend Marcel Pociot and his company Beyond Code are contributing a lot too. Combined, our teams have made more than 300 packages which have been downloaded over 50 million times.
From time to time Marcel and I ask some feedback and help each other out. Last year Marcel and I even made a package together: laravel-websockets. This package provides an entire server-side websockets implementation. It's a drop-in replacement for Pusher.
We had a lot of fun programming it as a duo, so it was only logical that we'd do another one together. We had a couple of ideas. One of them was error reporting.
At the time, I visited a workshop on Elm given by my buddy Steven Vandevelde. Something that stuck with me was that Elm has very nice error screens.
This convinced us we could improve the error screens for the Laravel ecosystem as well. Very early on Marcel and I knew that if we wanted to solve this problem right, we'd need a lot of time and help from our teams. So, we talked a lot with everybody involved, founded a new firm (called Facade) together and started working on this with our team members.
Marcel and I may be the public figures, but we couldn't have done this without our teams. A lot of good ideas came from them as well. Here's a twitter list with all Spatie team members, and here's the twitter account of Beyond Code's Sebastian. Make sure to follow them.
We created a new error tracking service called Flare (you can read more about that in this blog post and a beautiful new error page called Ignition.
In this blog post I'm going to tell you all about Ignition.
From Elm to Ignition
Let's take a look at a few error screens of Elm. Notice that an error screen in Elm doesn't focus solely on the error, but on the solution as well.
Let's take a look at another one. This one provides links to the documentation.
Now let's look at what we have in PHP by default. Without a framework, PHP offers you this. You only get the error: no stack trace, no request or application details.
Symfony's error page is slightly better, it shows you the stack trace, but it isn't very helpful.
The following screenshot is Whoops, which is the standard in Laravel 5. It's way better than the default of Symfony, showing you the stack trace and some info around the request. Even though Whoops is the default in Laravel, it's a framework agnostic screen. It only shows generic info.
And here's a screenshot of Ignition, the new error screen that we built. Because it's Laravel specific, we can do a lot of cool things.
Exploring Ignition
Let's explore all the neat things in Ignition. It's open source, and you can explore the code here.
If you have an error in your views, this is how whoops displays them. Notice that the exception message does not fit the allocated space. You have to hover over it to see it fully. In the stack trace you see that compiled Blade views and content is used. This makes it hard to track down which Blade view file contains the error, and the view content itself is not readable.
Ignition is a Laravel specific error page. So, it can hook into the framework to display the uncompiled view path and your Blade view. There is also enough room at the top to show the entire exception page, no extra clicks required. We also only display the application frames by default because those are the frames you're probably interested in.
If you click on that pencil icon next to the filename on the right hand of the stack trace tab, we'll automatically open the file in your favorite editor. By default it's PhpStorm. You can configure it to your favorite editor in the ignition
config file.
Notice that little "Telescope" link at the top right corner? We'll only display that if you've installed Laravel Telescope, a first-party debug assistant. If you click that link, you'll be taken to the exception in Telescope in which the error occurred.
Dark mode
If our default error screen is too bright for your taste, you'll be happy to know that our error page has a dark mode as well.
Ignition tabs
Let's explore the tabs displayed on an Ignition page.
The request tab
Next to the "Stack trace" tab, you'll see the "Request" tab. It displays all the info you'd expect about the request.
The app tab
The "App" tab is where it gets a little bit more interesting. This tab displays Laravel specific information about your application. First we show you information about the route where the exception happened. This includes the controller which was executed from the current request - if available - as well as the route name.
Another cool feature is the ability to inspect your route parameters.
Let's say you have a route definition like this:
Route::get('/posts/{post}', function (Post $post) {
//
});
When you encounter an exception on this route, we will show you the toArray
representation of that post
model in Ignition as part of your route parameters. The same goes for "simple" route parameters that do not require any bindings. This is a really nice way to easily see what information Laravel received for this particular route.
After the route parameters, we also show you the list of middlewares that were used within this request.
Next we have the "View" section. If the exception occurred within a view, we will show you the view name here. Even more: we’ll also give you a list of all the data that was passed to the view.
The user tab
The "User" tab contains more info around who's using the app and which browser is used.
The context tab
In the "Context" tab we display information on your repo (where is the repo located, what's the checkout commit hash) and the environment (which versions of PHP and Laravel are you using).
The debug tab
In the "Debug" tab we show you things that took place before the exception happened. Things like queries, log, and dumps. Next to a dump, we also display the filename of where you put that dump
statement. A click on the pencil icon takes you right to that file and correct line number in your favorite editor.
Suggesting solutions
Let's take a look at another error. This time we're going to forget importing a class. This is how the Ignition page for such an error looks like.
So Ignition sees that the exception is about a class that is not found. It will try to figure out if there is a class with that name in another namespace. If there is one, it'll suggest to importing it.
Ignition ships with a bunch of solutions for common problems. Here's one for when a Blade view is not found.
You can also add solution suggestions to your exceptions. You should let your exception implement the Facade\IgnitionContracts\ProvidesSolutions
implementation. It'll require you to add a getSolution
function. Here's a possible implementation.
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',
]);
}
}
This is how throwing that exception would look like in Ignition.
Running solutions
In addition to merely suggestion solutions, we can also run them ?. Imagine that, for instance, you've forgotten to set your app key. Here's how that error looks like in Ignition.
If you click the "Generate app key" button, we'll generate and set the app key in the background.
After refreshing the page, your application will work (unless it contains other exceptions).
Here's a video where you can see a couple of runnable solutions in action. We're going to try to save a model, but we forgot to generate the app key and create a DB. The solutions will generate an app, put the right DB credentials in place, and run our migrations.
You can create your runnable solutions by letting your exception implement Facade\IgnitionContracts\ProvidesSolution
, much like the non-runnable solution). The getSolution
method can both return runnable and non-runnable solutions.
namespace App\Exceptions;
use Exception;
use Facade\IgnitionContracts\ProvidesSolution;
class CustomException extends Exception implements ProvidesSolution
{
public function getSolution(): Solution
{
return new MyRunnableSolution();
}
}
Here's the example implementation of 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 [];
}
}
This is how throwing the CustomException
will look like in Ignition.
The run
function will execute when the user has clicked the Fix this for me
button.
You can pass parameters from the request where your exception occurred in to the request where the solution will run in. Just let getRunParameters
return an array. That array will be passed to run
.
Making Ignition smarter
So you have the ability to enhance your own exceptions by using textual or runnable solutions. But sometimes it would be nice to provide solutions for built-in PHP exceptions or even third-party exceptions where you have no control of the code.
We allow you to do this using "Solution Providers". Solution providers are classes that can hook into the solution lookup process from Ignition. When an exception gets thrown and Ignition receives it, your custom solution provider can be called to return one or multiple possible solutions for this exception.
You could for example create a custom "Stack Overflow" solution provider, which will try to find matching stack overflow answers for the given exception and return them as solutions.
We also make use of solution providers in Ignition itself. Here is how one such solution provider looks like:
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()
];
}
}
These solution providers can get automatically registered within Ignition like this:
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);
}
}
Like this, solution providers are going to keep enhancing Ignitions ability to provide solutions for your exceptions and we can't wait to see what the community comes up with!
Customizing Ignition
We've made Ignition extensible. You can add new tabs or replace the default ones.
Let's take a look at the facade/ignition-tinker-tab that we provided. The package is a wrapper around spatie/laravel-web-tinker which allows you to use Artisan tinker in the browser.
With facade/ignition-tinker-tab installed you can use artisan tinker on your error page.
We've also created a second package, called facade/ignition-code-editor. This one replaces the default stack trace tab, with a custom one that allows you to edit your code, right on the error screen. Here it is in action.
To learn how to add custom tabs, visit the documentation on adding tabs.
Sharing local errors on Flare
A neat feature of the error page is the ability to share errors with others. Next to the "Debug" tab, there's a share button. Click on it, and a menu will pop up that allows you to choose which information will be shared.
If you click on it and you'll get shown two URLs.
The public share link is meant to be shared with whoever you want to share your error with. You should keep the admin link to yourself. You can use that link to delete the shared error.
This is what a shared error looks like.
Reporting all errors to Flare
In the screenshot above, you've probably noticed that the shared error lives on flareapp.io. Flare is the new Laravel specific exception tracker that we launched together with Ignition.
Most of the features Ignition brings to the table are valid for Flare as well. If you report an error to flare for an exception that has a solution, we'll also display that solution on Flare.
To read more about Flare, head over to this blog post.
Closing thoughts
If you want to take a look under the hood how the error page works, head over to the Ignition repo on GitHub. The readme of the package lives on the docs pages of Flare.
We've worked for about eight months on this with a team of very talented people. In our opinion, this error page is a significant improvement on the Whoops page you used in Laravel 4 and 5. We hope you like it as much as we do.
What are your thoughts on "Ignition: a new error page for Laravel"?