(This page has no text content)
Table of Contents Introduction 6 ........................................................................................................................ Section 1: Auditing 8 ............................................................................................................ What's Covered? 8 ............................................................................................................ Planning Your Auditing Strategy 9 .................................................................................... Getting Into the Right Mindset 9 ................................................................................ Recording Your Findings 10 ........................................................................................ Automated Tooling 12 ....................................................................................................... PHP Insights 12 .......................................................................................................... Enlightn 17 ................................................................................................................. Larastan 25 ................................................................................................................ Style CI 34 .................................................................................................................. Code Coverage 35 ...................................................................................................... Manual Auditing 41 ........................................................................................................... Investigating "raw" Database Queries 41 .................................................................. Finding Incorrect Authorisation 44 ............................................................................. Checking Validation 55 .............................................................................................. Finding "Fake Facades" 59 ......................................................................................... Finding Business Logic in Helpers 63 ......................................................................... Finding N+1 Queries 65 ............................................................................................. Finding Controllers That Use Other Controllers 70 ..................................................... Finding Logic and Queries in Blade Views 75 ............................................................. Finding Hard-Coded Credentials 78 ............................................................................ Check Open Package Routes 82 ................................................................................. Reviewing Project Documentation 84 ........................................................................
Section 2: Testing 87 ........................................................................................................... What's Covered? 87 .......................................................................................................... Planning Your Testing Strategy 88 .................................................................................... The Benefits of Writing Tests 89 ....................................................................................... Spotting Bugs Early 89 ............................................................................................... Making Future Work and Refactoring Easier 89 ......................................................... Changing the Way You Approach Writing Code 89 .................................................... Tests-As-Documentation 90 ....................................................................................... Prove That Bugs Exist 90 ........................................................................................... Structuring Your Tests 91 .................................................................................................. Directory Structure 91 ............................................................................................... Choosing What To Test 91 ......................................................................................... Test Structure 94 ....................................................................................................... Data Providers 97 ....................................................................................................... Writing the Tests 103 ........................................................................................................ Prioritising Mission-Critical Tests First 103 ................................................................. Writing the Rest of the Tests 103 ............................................................................... Benefits of Writing the Easy Tests First 104 ............................................................... Preventing Test Fatigue 104 ...................................................................................... Testing Your UI with Laravel Dusk 106 .............................................................................. Installation 106 .......................................................................................................... Testing a Simple Form 107 ........................................................................................ Dusk Pages and Selectors 109 ................................................................................... Running Failed Tests and Groups 119 ........................................................................ Creating a CI Workflow Using GitHub Actions 121 ............................................................ Using an .env.ci File 121 ............................................................................................ Running the Test Suite 122 ........................................................................................ Larastan 128 .............................................................................................................. Laravel Dusk 129 .......................................................................................................
Output 131 ................................................................................................................. Section 3: Fixing 133 ........................................................................................................... What's Covered? 133 ........................................................................................................ Planning Your Fixing Strategy 134 .................................................................................... Using an Error Reporting System 135 ............................................................................... Types of Errors 135 .................................................................................................... The Benefits of an Automated Error Reporting System 135 ....................................... Error Reporting Using Flare 136 ................................................................................. Uptime Checking, Queue Monitoring, and Scheduler Monitoring 146 ............................... Uptime Monitoring with Oh Dear 147 ......................................................................... Scheduler Monitoring with Oh Dear 147 .................................................................... Queue Monitoring with Oh Dear 150 .......................................................................... Updating PHP, Laravel, and Packages 155 ........................................................................ Upgrading in Small Increments 155 ........................................................................... Automating the Upgrade Using Laravel Shift 156 ...................................................... Planning Upgrades Early 156 ..................................................................................... Using a Suitable Local Development Environment 158 ..................................................... Using Tests to Fix Bugs 160 .............................................................................................. What is Test-Driven Development? 160 ..................................................................... The Advantages of Test-Driven Development 161 ..................................................... The Disadvantages of Test-Driven Development 162 ................................................ Fixing a Real Bug Using Test-Driven Development 163 ............................................. Safely Removing Dead Code 169 ...................................................................................... Checking the Version Control History 169 .................................................................. Scream Test 169 ........................................................................................................ Logging or Reporting the Usage 170 .......................................................................... Removing the Code with an Atomic Commit 171 ....................................................... Section 4: Improving 172 ....................................................................................................
What's Covered? 172 ........................................................................................................ Planning Your Improvement Strategy 173 ........................................................................ Making the Most of PHP's Type System 174 ...................................................................... Using Type Hints and Return Types 174 .................................................................... Union Types 176 ........................................................................................................ Type Hints and Return Types in Closures 177 ............................................................ DRYing Up Your Code 179 ................................................................................................. Advantages of DRYing Up Your Code 184 .................................................................. When to DRY Up Your Code 185 ................................................................................. Refactoring Conditions 186 ............................................................................................... Reducing Indented Code 186 ..................................................................................... Replacing if and elseif with match 193 ...................................................................... Using the Nullsafe Operator 196 ................................................................................ Using Database Transactions 198 ..................................................................................... Adding the Database Transactions 199 ..................................................................... Manually Using Database Transactions 199 ............................................................... Tips for Interacting with Third-Party Services 202 ...................................................... Using Automatic or Manual Transactions 202 ............................................................ Dispatching Queued Jobs inside Database Transactions 204 ..................................... Improving the Testability of Your Code 208 ...................................................................... Using Objects Over Arrays 218 ......................................................................................... Final Words 226 .................................................................................................................... Discount Codes 227 .............................................................................................................. Flare 227 ........................................................................................................................... Oh Dear 227 ...................................................................................................................... StyleCI 227 ........................................................................................................................ Laravel Security In Depth 228 ...........................................................................................
6 Introduction Hey there! My name is Ash Allen and I'm a web developer based in the United Kingdom. Over the past five years, I've worked as a freelancer on Laravel projects and have had the chance to work on so many cool and different projects, from simple websites to complex SaaS (software as a service) platforms. While freelancing, I've realised that I have a passion for writing clean, maintainable, and extensible code. So on almost every project I've worked on, I've always ended up becoming "the test guy" or the "he'll fix it for you guy". This means that on pretty much every project I work on, it has been my responsibility to try to improve the overall quality of the codebase; whether through refactoring or building entire test suites from scratch. In fact, since I started freelancing, I've written just over 10,000 tests! So I've honed in on this small niche of testing and refactoring and typically use it when getting new clients. It's not very often that I work on greenfield projects anymore; I usually work on existing projects. I'm usually brought on board to work with clients to maintain their existing system, fix bugs, remove technical debt, build test suites, and add new features. In this book, I'm going to take you through the same process that I would typically use when being brought on board to fix Laravel applications. I'll show you my usual auditing process that I use to find places where the code can be improved. I'll then cover how I build out a testing strategy that can be used to incrementally improve the test coverage of the code. After that, I'll cover the different ways that I approach fixing bugs in Laravel applications. Finally, we'll look at different ways that we can improve our codebase. I won't be covering every single possible auditing tool, code issue, and improvement approach. If I did that, this book would likely be thousands of pages long and it would never get released. But I'll be covering the most common issues that I've found after working on many projects, and the ways that I would typically find and then remove them. Of course, depending on the types of projects that you're working on, you might not be able to implement all the ideas covered in this book. If you're a developer who's looking for a few quick fixes for projects you work on, you might only be able to implement some of the ideas from individual sections. If you're a developer who has some extra time to dedicate to removing technical debt and improving the codebase, you might be able to implement all ideas. Either way, there should be something in here to cover your situation.
7 By the end of the book, you should feel more confident as a Laravel web developer. I hope that I'll be able to show you how you can effectively audit, test, fix, and improve your applications to make them more maintainable, testable, and performant.
8 Section 1: Auditing What's Covered? In this section, we're going to cover steps you can take to audit your Laravel applications. We'll be stepping through some automated tools (such as Enlightn, Larastan, PHP Insights, and StyleCI) that you can use to quickly get an understanding of your code's quality and spot any obvious bugs. After that we'll take a look at the steps that I would typically take when manually looking through the code. I'll show the common errors I usually come across when auditing projects and different ways you can spot them. By the end of this section, you should hopefully have a better understanding of the different things to look out for in your code and ways of spotting them. Remember that this section isn't a substitute for a penetration test. You'll still need one; but this can help you find some common security-related problems.
9 Planning Your Auditing Strategy Before we start auditing our application, it's important to make sure that you have a clear strategy in place. This is particularly important if you're working as part of a team and you have multiple team members auditing the codebase at once. Getting Into the Right Mindset First off, I like to try to be relaxed before starting any type of audit. It's almost inevitable that as you're looking through the code, you're going to find some pieces of code that will make you think "What on Earth were they thinking when they wrote this?". So it's important to enter into the audit with a calm and open mind. As you likely know, there can be a lot of pressure on developers to hit deadlines and budgets, and sometimes features will get rushed out to keep stakeholders or managers happy. So when the developers were writing these features they might not have written them in a way that was suitable for long term use, and may have rushed the code. Of course, in an ideal world, this would never happen and the features should always be fully thought out before being implemented. But unfortunately, this isn't always the case. It's also important to take into account how old the project is. If the project was originally built with an older version of the language (like PHP 5.4), there might be some places where the code isn't as "clean" as it could be if it was written now. If you do come across places where the code doesn't look very clean, it could be due to constraints that the original developers had when the project was built. You need to remember to look at the code with a critical mindset but without taking personal hits at anyone. So make sure that you don't nitpick at the smallest things, because this could lead to tension between you and the other team members. Just because you would have written a particular feature differently, doesn't necessarily mean that it's wrong. When we're looking through the code, we're looking for code that: Has bugs Is vulnerable to security exploits Makes testing difficult Makes it difficult to extend in the future. If you come across a piece of code where you think "This works, but I could have written it better", that doesn't necessarily mean that we should flag it (unless it's causing issues or might make future development more difficult, of course).
10 Recording Your Findings As you're working through your audit, you'll need to find a place to record your findings so that you can tackle the issues later on. This is because you typically won't want to start changing any code until you've got an overview of the entire system (or section of the system) and the changes that should be made. If you want to start making improvements as soon as you spot issues, this is entirely your choice. However, I typically wait until the end of my audit so that I can be more confident that I have a better understanding of the project as a whole. Where you record the findings is all down to how you work as a team and what fits your workflow the best. In the past, I've recorded my findings in spreadsheets or taken a more Kanban-like approach and put them into a Trello board. The benefit of doing this is that if you're working in a team, the other team members can see what issues have been found and what needs to be done to solve each one. When I've audited projects that I've been working on by myself, I've used TODOs directly in the code. For example, I might leave a comment like this in my code: // TODO Add typehint. // TODO Add return type. public function getUser($id) { // ... } Doing this means that I get to couple my actions to the codebase itself. If you're using an IDE (integrated development environment) such as PhpStorm, you'll be able to see a list of all the places you've left TODOs. However, please remember that this approach might not work very well if you're working as part of a team because you won't be able to actively see who's working on each issue. It might also prevent project managers from getting a clear view on the progress you're making. If you come across any potential vulnerabilities that could have been exploited in the past, it's really important to make sure that these findings are investigated. For example, if you find any routes that don't have the correct authentication or authorisation checks, you'll want to investigate and find out if there's any sign of a malicious person having visited this route. Any security-related issues that you find will likely need to be prioritised after you've
11 finished your audit. Leaving vulnerable code in your project can be dangerous and costly if they are exploited (depending on the severity of the vulnerability).
12 Automated Tooling There are many automated tools we can use to get started with auditing our code and looking for obvious improvements. We're going to step through the tools I would typically use and what I would do with the findings. It's worth noting that some of these tools overlap in the features that they offer and the analysis that they'll provide. However, each of them have their own unique checks that can provide a great base for your audit. PHP Insights The first tool that we'll use to get an overview of our application is PHP Insights. PHP Insights is a tool that has built-in checks that you can use to help make your code reliable, loosely coupled, simple, and clean. Out of the box, it works with Laravel, Symfony, WordPress, and more. So this tool can be useful in more than just your Laravel projects. At the time of writing, PHP Insights gives you insights into 4 distinct categories: Code (105 insights) Architecture (20 insights) Complexity (1 insight) Style (84 insights) The results that PHP Insights generates are also presented in a nice, easy-to-read format in your terminal after running. After the command has run, you'll be presented with an overview of the results like so:
13 PHP Insights provides a huge amount of customisation, so if you're interested in tailoring the analysis to fit your project needs more, you can check out the documentation at https://phpinsights.com/. Installation To get started, we'll first need to install PHP Insights using the following command: composer require nunomaduro/phpinsights --dev After that, you can publish the package's config file using the following command: php artisan vendor:publish -- provider="NunoMaduro\PhpInsights\Application\Adapters\Laravel\Insi ghtsServiceProvider"
14 Analysing Your Code with PHP Insights That's it! You should now be ready to run PHP Insights using the following command: php artisan insights Running the command above will analyse your entire codebase. However, if you'd prefer to only analyse a specific file or directory, you can pass a path to the insights Artisan command. For example, if we only wanted to analyse the code in our app/Http/Controllers directory, we could run the following command: php artisan insights app/Http/Controllers Or for example, if we wanted to analyse a specific controller (such as app/Http/Controllers/UserController.php), we could run the following command: php artisan insights app/Http/Controllers/UserController.php Automatically Fixing Issues By default, PHP Insights will only analyse your code and won't change anything about it. However, some checks that PHP Insights ships with have support for automatically fixing issues for you by using the --fix option in the command. To automatically fix the issues, you can run the following command: php artisan insights --fix Although this can be really useful to run, as mentioned previously, it can sometimes be helpful to start making changes to your codebase after you've completed your entire audit. After you've completed your audit, you may wish to come back to this tool and run it to get some easy wins and improvements.
15 Example - Finding Unused Method Parameters When you're running PHP Insights, you'll be able to step through the different insights and see what checks failed. To give a brief look into a few of the checks that it offers, let's take a look at a few examples. In the example project that I ran the command in, PHP Insights detected that we had an unused parameter that could possibly be deleted: It's important to remember that this does not always mean that the parameter is unused and not needed. However, it can usually give a good indication of the obvious fields that aren't being used. For example, let's take a look at where this error is being thrown from: /** * Get the cards available for the request. * * @param \Illuminate\Http\Request $request * @return array */ public function cards(Request $request) { return []; } In this case, the unused $request field is being passed because it is required so that the method matches its parent that it's extending. But the method is also an exact copy of its parent and doesn't provide any different behaviour. So this could potentially suggest to us that we can actually delete this method entirely from our app/Nova/User.php class. As you can see, PHP Insights can provide a good indication of places where code can be improved. But with an extra bit of manual investigation, you can take the improvement further.
16 Example - Finding Vulnerable Dependencies Laravel projects use packages and dependencies from developers all around the world. This means that the code running under the hood of your application (whether it be part of the Laravel framework or a package you've installed) is maintained by a third party, and you don't always have control over any changes. There may be times when you're using a package within your project that contains a vulnerability that can be exploited. So it's important that you periodically check that your dependencies are up to date and aren't vulnerable. There are some automated tools like Dependabot you can use that automatically check to see if you're using any dependencies with known security issues or CVEs (Common Vulnerabilities and Exposures). If you're using a platform like GitHub to host your code, this tool can usually be enabled for free. In the example project, PHP Insights detected that it was using a dependency with a known security vulnerability. In this case, it was guzzlehttp/guzzle@7.4.2. By flagging the dependency issues to us, we can prioritise updating the packages and removing the vulnerabilities. If you find any vulnerable dependencies in your project, it's important to investigate its severity and whether it may have been exploited in your application. Some vulnerabilities can lead to user data being leaked or malicious users getting access to parts of the system they aren't supposed to be able to. Example - Finding Unused Imports In the example project, PHP Insights also detected that we had some unused use statements in some of our classes. By detecting that classes' imports aren't being used, we can remove them from our classes
17 to reduce the amount of unnecessary code that is present in the codebase. Enlightn Another great tool that we can use to get insight into our project is Enlightn. Enlightn is a CLI (command-line interface) application that you can run to get recommendations about how to improve the performance and security of your Laravel projects. Not only does it provide some static analysis tooling, it also performs dynamic analysis using tooling that was built specifically to analyse Laravel projects. So the results that it generates can be incredibly useful. At the time of writing, Enlightn offers a free version and paid versions. The free version offers 64 checks, whereas the paid versions offer 128 checks. One useful thing about Enlightn is that you can install it on your production servers (which is recommended by the official documentation) as well as your development environment. It doesn't incur any overhead on your application, so it shouldn't affect your project's performance and can provide additional analysis into your server's configuration. For the purpose of this guide, we'll be using the free, open-source version and installing it in our local development environment to analyse our code. However, if you want to run it on your production server and get a more in-depth analysis of your project, you can follow the same steps and check out the full documentation at: https://www.laravel-enlightn.com. Installation To start using Enlightn, we'll need to install it using the following command: composer require enlightn/enlightn You'll then be able to publish the package's config file using the following command: php artisan vendor:publish --tag=enlightn
18 Running Enlightn To run Enlightn and analyse your Laravel application, you can then run the following command: php artisan enlightn By running this command, you'll be presented with an easy-to-read overview displaying the results of the checks that were run, like so: It's important to remember that some checks will likely fail because they're set up to test against a production environment. This is completely normal and not anything that you should be worried about. So make sure to just record the errors that are related to the codebase rather than the infrastructure.
19 Notice: Enlightn also offers a "Web UI" you can use to view your results. You'll need to create an account at https://www.laravel-enlightn.com/register and follow the documentation to set this up using the API token that they provide. This can be useful if you manually run (or schedule) Enlightn in your CI/CD pipeline or on your production servers. Example - Unnecessary Collection Calls Enlightn is very useful at detecting method calls on Collections that could have been called against the underlying database query. For example, let's take the following block of code: use App\Models\User; $userCount = User::all()->count(); The code above does its job and assigns userCount with the amount of User models that exist in the database. However, to achieve this, it's actually fetching all the users from the database, with all of their properties, hydrating the models (assigning the fields returned from the query to the User models) and then creating a Collection of the models. It's then counting how many models exist in the Collection. As you can imagine, this approach isn't very optimised and depending on how complex the query is and where it's used, it could potentially increase the execution time of your code (and frustrate your users). For our particular example above, Enlightn would flag the query and let us know that we can optimise it by changing it to the following: use App\Models\User; $userCount = User::count(); Changing the query to the above version removes the steps where we were fetching all the data from the table and then counting it in a Collection. Instead, the SQL query used will be
20 changed so that it only returns the count from the database as an integer. To give an example of the types of speed improvements you could see by making this improvement, here are some execution times (in seconds) from a local machine when running locally with different amounts of rows in the users database table: User::all()->count() User::count() 1,000 0.014s 0.001s 10,000 0.064s 0.003s 100,000 0.862s 0.006s 1,000,000 12.934s 0.150s As you can see from the table above, there's a clear performance improvement; especially as the number of rows in the database grows. Please note that the execution times above are based on running an example project locally using PHP 8.1 and MySQL 8 and are solely to be used as an indication of speed differences, rather than being statistics generated in a fully-controlled environment. Example - Mass Assignment Analysis Another useful analysis that Enlightn performs is the "Mass Assignment Analyzer". It scans through your application's code to find potential mass assignment vulnerabilities. Mass assignment vulnerabilities can be exploited by malicious users to change the state of data in your database that isn't meant to be changed. To understand this issue, let's take a quick look at a potential vulnerability that I have come across in projects in the past. Assume that we have a User model that has several fields: id, name, email, password, is_admin, created_at, and updated_at. Imagine our project's user interface has a form a user can use to update the user. The form only has two fields: name and password. The controller method to handle this form and update the user might like something like the following:
Comments 0
Loading comments...
Reply to Comment
Edit Comment