Reducing the amount of time you need to debug with Laravel
As a developer one thing you will likely do a lot of debugging. Either when writing new code and to check if it actually works or when you need to fix a bug. Ain’t anything bad about that. Making mistakes is human and under some conditions you’re more likely to lose focus and make easier mistakes. For example when you’re tired or even when you’re hungry. Luckily we developers created a lot of possibilities to being able to debug more effectively. But you realise you’re even more lucky as a developer when you realise there are also a lot of tools to prevent having to debug mistakes.
In this blog I would like to talk about the tools you could use as a Laravel (or PHP) developer. Think about your IDE (or extensions for your IDE), static analysis and automated tests.
Automated tests
When building an application I will always advise you to at least create tests for your most critical features. When I started doing my first projects for clients I didn’t do that yet. Not because I was lazy but because I never really looked into it. I didn’t thought it would be a necessary thing to do. Actually you should and let me tell you why.
Let’s say you’ve built a platform like Dropbox where your users can save files. Your users can login and that kind of stuff but one of the most important functions of your tool is that your users can upload files. When you’re making changes to the code after a while you might accidentally break something. You could test that manually - probably - but that’s not always the case and it can be very time consuming. It also has to been done precisely. Like I mentioned before it's possible that a human makes mistakes because we can be tired sometimes. A computer never gets tired and does exactly what you tell it to do. We can divide test automation of your Laravel application in two parts:
- Unit and feature tests (back-end)
- Browser tests (front-end)
We’ll start with unit and features tests first because most likely you will use these the most. As a Laravel developer you’re most likely always building on the back-end. Most of you will probably also work on the front-end as well but more about that later.
Unit and feature testing
Laravel ships with PHPUnit by default but Pest is another great package (which might replace PHPUnit in the future). Pest offers a more elegant syntax to write tests. With the artisan commands you can easily create unit and feature tests. By running php artisan make:test
you can create your tests which you can find in the tests/Feature
directory. The idea is that you write a test for a specific function / feature within your application. If we take the example mentioned before we should create a test that focuses on handling file uploads.
An example from a project of my own:
public function test_if_user_can_upload_a_file()
{
$user = User::factory()->create();
$this->actingAs($user);
Storage::fake('photos');
$file = UploadedFile::fake()->image('avatar.jpg');
$this->post(route('users.upload', $user), [
'files' => [$file],
])->assertRedirect(route('users.index'));
$this->assertDatabaseHas('files', [
'user_id' => $user->id,
]);
}
Ideally you would make testing a part of your CI/CD pipeline. With Github actions it's possible to run the tests automatically when opening a PR or committing to a branch. This way you get a warning when your code breaks something before you are going to merge that code with your staging or production branch.
Some interesting resources:
- Testing Laravel by Spatie
- Getting Started with PHPUnit in Laravel by Stephen Rees-Carter
- Learn how to start Testing in Laravel with Simple Examples using PHPUnit and PEST on Laravel News
Browser testing
Beside testing the backend with those unit and/or feature tests it's also possible to test your frontend with Laravel Dusk. This first-party package uses a standalone Chrome driver by default. However, you are free to utilise any other Selenium compatible driver you wish.
Because it loads your application in a browser it can actually see what your application would render when a user visits the application. The benefit of this is that it doesn't matter if your frontend is built with React, Vue, Svelte or just HTML. It just shows what you would normally see.
Browser testing is in my opinion not less useful than unit- and feature testing but it takes some time to setup. Browser testing is also something which you can do manually easily. However, investing some time to automate this will probably save you time over time. Also when you make changes on your frontend you probably need to change your tests.
Let's say you want to make sure your user sees a notification after it uploaded a file. Than you would write a test like this, it's syntax is pretty straightforward:
public function testUserSeesNotification()
{
$user = User::factory()->create();
$this->browse(function (Browser $browser) use ($user) {
$browser->loginAs($user)
->visit('/album/update')
->assertSee('Upload files')
->attach('photo', __DIR__.'/photos/mountains.png')
->click('@upload')
->pause(1000)
->assertSee('File(s) uploaded successfully');
}
}
We first login as a user and visit the page where we can upload a file. We then assert that we see the text 'Upload files'.
We can provide any text we want. If you want to check if a certain element is shown you can use any selector like a class, ID or Dusk selector. When referring to one of those selectors you use the same way as usual. Dot notation for classes, a hashtag for IDs and a
@
when using the Dusk selector.
After that we attach a file to a file input (you can find all possible element interactions here). Then we click on the upload button. As you can see I used the Dusk selector here. You might also notice that I added a pause here. This is because the application needs to process the file upload before it returns the view. From my personal experience it's good to always do an additional assertSee()
after an action to assure you're on the right page. If the assert does not succeed you might need to add the pause.
In the end we check if we see the notification text on the page which indicates that the user can see the notification. Instead of asserting that the user sees the text it's also possible to use one of the selectors here to check if the notification is shown.
Some interesting resources:
Static analysis
One of the recent things I recently learned is using static analysis. Static analysis analyses your code to find possible errors that may occur. The best part about this - in my opinion - is that with static analysis you can find errors that your tests may not find. Using both automated tests and static analysis may in theory bring down the amount of times you have to debug unnecessary errors / bugs drastically.
Besides finding code that may not work properly it can also tell you that some code is never reached. Like this bit of code which I took from the The Road to PHP: Static Analysis newsletter:
$condition = /* some kind of boolean expression */;
if ($condition) {
// …
} elseif ($condition) {
// …
}
Error: Elseif condition is always false.
Static analysers don't only analyse your code with the help of the PHP's type system. It's possible to help your analyser understanding your code by using docblocks. For more in-depth information about docblocks I recommend you to read this blog post from Brent.
I may advice you to start with static analysis as soon as possible when starting with a project. I recently implemented it in my own project and I got about 130 errors. It's not a really big application but it started growing bigger which is why I decided to implement it right now. I solved the 130 errors quite fast and some of them were errors which could've led to problems in the future.
Like the error I mentioned earlier there were 3 (!) places where my code would never reach a certain point because the expression used was always false. In some cases my tests didn't notice that or the tests were not written for that specific piece of code. I also changed and renamed a relation within my application. I had to change a few controllers, models and policies because of that. After the changes I ran my tests which gave no errors. When I started using PHPStan it immediately threw errors on some parts of the code where I forgot to make the needed changes. These are both perfect examples that static analysis is a nice addition to prevent bugs.
One of the other benefits of static analysis is that you'll learn to be a better programmer along the way. Because you'll learn from the static analysis to write your code correctly. PHP is not a very strict language even though it has became more stricter in the recent years and will be more strict with PHP 9.0 which will be the version from where dynamic properties will be deprecated. Because PHP is not very strict you have the possibility to write the code not very optimal.
Like automated tests it's also possible to run the static analysis in your CI/CD pipeline with Github actions for example.
Some interesting resources:
- Not Quite My Type by Kai Sassnowski @ Laracon '22
- The Road to PHP: Static Analysis by Brent
- We don't need runtime checks by Brent
IDE
I assume that the most of you will already make use of an IDE but I found it still worth to mention. Your IDE can be really helpful and makes you more productive as it can spot errors very soon. Like static analysers they also rely on PHP's type system and will check for errors at the moment you're writing code.
In the first year I began with writing code I used Notepad++. We used this at school because we needed to spot errors ourself. I think it's not really bad to learn how to spot errors yourself but using an IDE will improve your productivity a lot. Instead of looking for that semicolon that you forgot, the IDE immediately shows you that your code is wrong.
Other features an IDE includes are built-in VCS tools, terminal, (visual) debugging and even connections to your FTP and database(s). It provides you one single environment for different tasks which you would otherwise download a number of applications for.
I'm currently using VS Code myself. Because VS Code is not necessarily for PHP development it doesn't offer all the functionalities PHPStorm gives you out of the box. Hereby I provide you a list of the extensions I'm using for PHP development (I'm using a lot more but those are not specifically for PHP):
- PHP DocBlocker: offers completion when creating docblocks.
- PHP Intelephense: a high performance PHP language server packed full of essential features for productive PHP development.
- PHP Namespace Resolver: can import and expand your class. You can also sort your imported classes by line length or in alphabetical order.
- PHP Static Analysis: extension for PHPStan.
- vscode-phpstan: uses your PHPStan configuration file and analyses your code while you're writing it.
Conclusion
The conclusion is that PHP (and thus Laravel) offers a lot of tools to help you preventing creating unnecessary bugs. Of course it's always possible that a bug slips through but using these tools will drastically decrease the chance of actually adding bugs. Testing the application yourself takes a lot of time, especially when your application grows. Depending on your application you can choose which tools you want to use. If I may give you one advice: test at least your most critical functionalities and use static analysis. It shouldn't take too long to set up properly and will 100% save you time.