As developers, it is our responsibility to keep our code in tip-top shape as much as possible. This is helpful in maintaining and extending code, as well as in avoiding bugs.

In PHP, a number of tools can help us achieve quality and bug-free code, including PHP Mess detector, PHP CodeSniffer, PHPStan, and Psalm. These are all great tools, but they're not easy to integrate on a Laravel project.

In this article, well be looking at five tools specifically made to help you catch bugs early, improve the quality of your Laravel code, ensure uniform coding style, and make it easy to follow best practices while working with Laravel projects.

Specifically, we will be covering the following tools:

  • Laravel Pint
  • PHP Insights
  • Larastan
  • Laravel Psalm plugin
  • Enlightn

We'll also cover text-editor integration and continuous integration using those tools.

Prerequisites

This tutorial assumes that you have set up your PHP development environment. It also assumes experience with PHP and Laravel.

To make full use of this tutorial, I highly recommend following along so that you can see and test for yourself how these tools work.

To aid with this, please clone the following repo: food-order-app-laravel-tdd:

git clone https://github.com/anchetaWern/food-order-app-laravel-tdd
cd food-order-app-laravel-tdd
git checkout tdd

Next, rename the .env.example file to .env and update the DB_PASSWORD with your local database password.

Once that's done, set up the project by executing the following commands:

composer install
npm install
php artisan config:cache
php artisan migrate
php artisan db:seed

That should install all the composer and frontend dependencies, update the config, and migrate and seed the database.

I recommend installing GitHub for desktop or any other Git GUI tool of your choice so that you can see what the tools does to your code.

What is static code analysis?

Static analysis allows you to check code for issues without running it. It basically reads the code, understands it, and compares it against a certain rule to verify correctness. Here's an example:

function calculateSalary(int $days_worked, int $hours_worked_per_day): int
{
    ...
}

calculateSalary('20', 8);

Just by looking at it, we already know that the code will fail because we've supplied a string instead of an integer for the first argument. This is essentially how static code analysis works. However, for it to do its job well, you need to already be using types in your code (either via type declarations or DocBlocks). This will make the checks more effective at helping to find issues in your code.

Note that static analyzers are smart enough to infer most types, so adding types to your old project isn't required to take advantage of them.

Static analyzers scan your whole codebase at once. This means that the context in which they work differs from that of humans doing a code review. They know how the different files are connected all at once, which is why they're capable of detecting issues that can't normally be caught in a code review.

You might be wondering why static analyzers are needed when we already use tools like PHPUnit or Pest to test our code. Well, it's because those tools aren't usable right away. You have to write the tests first before you can use them to detect issues in your code. Often, it is also time-consuming to run them because they need to execute your code. This is the main difference between static and dynamic analysis. The former scans the code, while the latter runs the code to detect issues.

Using a code analysis tool has the following benefits:

  • Enforce coding standards set by your team.
  • Ensure that a uniform coding style is followed.
  • Spot potential issues early, which means fewer bugs are shipped to production.
  • Improve code quality.
  • Detect security vulnerabilities.

Laravel pint

Laravel Pint is the newest tool for checking code quality. Starting with Laravel 9, Pint is automatically installed when you create a new Laravel app. It also comes with a default configuration for the checks. This makes it easy to get started with since you don't have to do anything else. The only disadvantage is that it's only supported for PHP 8 projects.

Installation

If you're on an older Laravel version, you can install Pint with the following command:

composer require laravel/pint --dev

Usage

To use Pint, execute the following:

./vendor/bin/pint --test

This will show all the issues that Pint detects in your code:

Output of running the Pint test command, showing several code style issues

That should give you an idea what rules it's using. The output isn't simple to read, so it's recommended to set Pint to automatically fix the issues for you instead:

./vendor/bin/pint

You can then view it on your Git GUI tool of choice to see what sort of changes it's making:

Changes made by Pint, displayed on a diff checker tool

In the screenshot above, you can see that it automatically removes unused imports and includes additional whitespace to improve readability. However, it does more than that depending on your code. Be sure to review the changes it's making before committing them. If something doesn't feel right, then you can simply revert the changes and manually implement the changes it recommends.

If you don't have a Git GUI tool installed, you can have Pint print the changes it makes for you:

./vendor/bin/pint -v

Here's a sample output:

Output of changes made by pint, displayed in the terminal

Configuration

Pint uses the laravel preset rules by default, so you don't have to do anything else if you want to use the rules within it. You can configure Pint by creating a pint.json file at the root of your project directory:

{
    "preset": "laravel"
}

Other supported presets are symfony and psr12. You can change it like so:

pint --preset psr12

If you don't want to use any of those presets, your only option is to specify the all the rules by hand. Pint is built on top of PHP CS Fixer so you can use all the rules that it has. You can use the PHP-CS-Fixer configurator to cherry-pick your own set of rules.

There's a lot, so you'll most likely base your rules on an existing set. For example, there's the CS Fixer set:

PHPCS fixer set in PHPCS fixer configurator

Here's a sample pint.json file:

{
    "preset": "laravel",
    "rules": {
        "simplified_null_return": true,
        "braces": false,
        "new_with_braces": {
            "anonymous_class": false,
            "named_class": false
        }
    }
}

Note that you can only disable rules from an existing preset. You can't specify one or two rules and expect Pint to only check for those rules.

You can read more about Pint in the Laravel docs.

PHP insights

PHP Insights is a code quality analysis tool that checks for code quality, coding style, complexity, and architecture. Just like Pint, it's also opinionated, so you'll have to customize it to fit your needs.

Installation

You can install PHP Insights with the following command:

composer require nunomaduro/phpinsights --dev

Usage

You can run PHP Insights by executing the following:

./vendor/bin/phpinsights

If you want to be able to call insights using artisan and configure it, then you have to add its config to your project:

php artisan vendor:publish --provider="NunoMaduro\PhpInsights\Application\Adapters\Laravel\InsightsServiceProvider"

This would then allow you to execute Insights with php artisan insights, which should give you a nicely formatted report across four areas of your code:

  • Code
  • Complexity
  • Architecture
  • Style

php insights issues displayed on the terminal

You can also specify which files you want to check. For example, you only want to check for controllers:

php artisan insights app/Http/Controllers

This can also be a specific file:

php artisan insights app/Http/Controllers/CartController.php

If you want your issues automatically fixed, use the following:

php artisan insights --fix

PHP Insights also checks a fifth area: security issues found in your dependencies. Note that it's only checking for dependencies and not your actual code. Therefore, it won’t highlight OWASP issues for you. Nevertheless, this is a pretty useful addition to a code-analysis tool so that you can address it as soon as possible.

Terminal output of PHP insights security issues

Configuration

To configure PHP Insights, update the config/insights.php file.

Just like Pint, you can only override the rules applied to the preset specified. By default, it uses laravel, but it also supports symfony, magento2, and drupal:

return [
    'preset' => 'laravel',
]

Next, you can either add or remove certain rules from the preset:

<?php
// config/insights.php

use NunoMaduro\PhpInsights\Domain\Insights\ForbiddenFinalClasses;
use SlevomatCodingStandard\Sniffs\TypeHints\DeclareStrictTypesSniff;
use SlevomatCodingStandard\Sniffs\TypeHints\DisallowMixedTypeHintSniff;

return [
    'add' => [
        Classes::class => [
                ForbiddenFinalClasses::class,
        ],
    ],

    'remove' => [
        DeclareStrictTypesSniff::class,
        DisallowMixedTypeHintSniff::class,
    ],
];

PHP Insights supports sniffs from PHP CodeSniffer and fixers from PHP CS Fixer. This allows you to add any sniff or fixer as long as it implements PHP_CodeSniffer\Sniffs\Sniff or PhpCsFixer\Fixer\FixerInterface.

If you go through the imports in the config/insights.php file, you'll see that it mostly uses rules from NunoMaduro\PhpInsights and SlevomatCodingStandard\Sniffs. The former is PHP Insight's in-house insights and the latter is the sniffs from the Slevomat coding standard. In most cases, you'll only ever reach for those two.

Here's a list of rules you can use:

However, you can just as easily use the documentation to look for classes to import:

For example, you don't care that imports are alphabetically arranged. The corresponding sniff for that is under the namespaces folder → AlphabeticallySortedUsesSniff. You can then remove it like so:

<?php
// config/insights.php

use SlevomatCodingStandard\Sniffs\Namespaces\AlphabeticallySortedUsesSniff;

return [
    "remove" => [
        AlphabeticallySortedUsesSniff::class,
    ]
];

You can learn more about PHP Insights on their website.

PHPStan

PHPStan is a static analysis tool for PHP. Larastan is a package built on top of PHPStan to make it easier to integrate with Laravel projects. PHPStan works by "sniffing" the code using the various rulesets applied to it and determine if it follows the standards. However, Larastan doesn't fully follow the definition of "static analysis" since it has to boot the application's container and resolve the types that can only be computed at runtime. This is why Larastan is called a "code analysis" tool rather than a "static analysis" tool.

Installation

Larastan requires PHP 8 and Laravel 9, so if you have a project that uses an older version, you'll need to update your dependencies first before using it. Execute the following command to install it:

composer require nunomaduro/larastan --dev

Then, create a phpstan.neon file at the root of your project directory. Add the following config to it:

includes:
    - ./vendor/nunomaduro/larastan/extension.neon

parameters:

    paths:
        - app/

    # Level 9 is the highest level
    level: 5

The includes directive includes the base configuration required by PHPStan. It contains everything required by PHPStan to work with Laravel, as well as some default rules for checking your code.

Usage

To analyze your code, run the following command:

./vendor/bin/phpstan analyse

Something like the following will be returned; it should show the file name and the line:

phpstan level 5 errors displayed on the terminal

That's the only issue we get since the level is only set to 5. Try playing around with this value in the phpstan.neon file by increasing it in increments of 1. Here's what it looks like when it’s increased to 6:

phpstan level 6 errors displayed on the terminal

If you try to trigger a different error by using an incorrect type, it detects what the correct type should be:

<?php
public function index(CartService $cart): void
{
    $cart_items = $cart->get();
    return view('cart', compact('cart_items'));
}

public function store(CartService $cart): void
{
    $cart->add(request('id'));
    return redirect('/cart');
}

public function destroy(CartService $cart): void
{
    $id = request('id');
    $cart->remove($id);

    return redirect('/cart');
}

public function update(CartService $cart): void
{
    $cart->update(request('id'), request('qty'));
    return redirect('/cart');
}

This is where the type loading kicks in. With just static analysis, PHPStan won't really know what type these should be:

phpstan missing return type issues

With PHPStan, you can also specify the folder or file you want to analyze:

./vendor/bin/phpstan analyse app/Http/Controllers
./vendor/bin/phpstan analyse app/Http/Controllers/CheckoutController.php

You can also easily switch the rule level:

./vendor/bin/phpstan analyse -l 1

Configuration

In the phpstan.neon file, the directories you want to analyze can be specified. For example, you're only interested in analyzing controllers, models, and services:

parameters:
    paths:
        - app/Http/Controllers
        - app/Models
        - app/Services

We've briefly touched on the rule level earlier. This can have a value between 0 to 9. PHPStan is mostly concerned about return types, so the kinds of issues you'll see will mostly relate to type checks. You can read more about the rule levels here.

Be sure to check out the PHPStan config reference to learn more about the available configuration options.

In addition to the checks that PHPStan provides, Larastan has checks specific to Laravel. You can check out the rules doc for that.

For relatively new projects adopting PHPStan, this would return fewer issues. However, for older projects, it can be overwhelming to deal with all the issues. Therefore, it's good to start with level 1 and then progressively increase the level as you deal with the easier issues.

If the issues gets too overwhelming, you can always generate a baseline file:

./vendor/bin/phpstan analyse --generate-baseline

This generates a phpstan-baseline.neon file at the root of your project directory. Include it on your phpstan.neon file:

includes:
    - ./vendor/nunomaduro/larastan/extension.neon
    - ./phpstan-baseline.neon

If you check the contents of the file, you'll see that it ignores all the errors returned on the current rule level. This effectively allows you to run subsequent analysis on new code only. If you try to run the analyse command now, it will return no issues. Use this sparingly, and make sure you've already dealt with the most crucial issues.

A big part of making static analyzers like PHPStan really useful is their ability to extend PHP's type system via custom doc types. We won't be covering that in this article, but adding those doc types will provide more context to PHPStan so that it can understand your codebase better and make more informed decisions regarding the issues plaguing your codebase. These doc types are particularly useful if you're not already using PHP 8. Here's a quick example:

class Student
{
    /** @readonly */
    public float $grade;
}

(new Student())->grade = 80.5;

In the above code, we used the annotation @readonly to indicate that the $grade should be a read-only property. This language feature wasn't added until PHP 8.1, so it's extremely useful for providing more info to PHPStan if the project is using something older than PHP 8.1.

You can learn more about PHPStan and Larastan on their GitHub and website.

Psalm

Psalm is a static analysis tool for PHP. Much like PHPStan is to Larastan, Psalm has a plugin called Laravel Psalm that allows you to easily use it in Laravel projects.

Installation

The first step is installing Psalm:

composer require --dev vimeo/psalm

Next, generate its config file. This creates a psalm.xml file in the root of your project directory:

./vendor/bin/psalm --init

Once that's done, you can now install the Laravel Psalm plugin and enable it:

composer require --dev psalm/plugin-laravel
./vendor/bin/psalm-plugin enable psalm/plugin-laravel

Usage

You can run Psalm using the following command:

./vendor/bin/psalm

By default, Psalm doesn't show you the details of the issues that it finds:

psalm no issues displayed on the terminal

To show the details of the issues it found, use the following:

./vendor/bin/psalm --show-info=true

You'll then get something like this:

Psalm with info displayed on the terminal

As you can see from the screenshot above, it mostly checks for types, just like PHPStan.

To automatically fix the issues, set the --alter and --issues options and specify which issues you want to fix. If you want to fix all the issues detected, then set it to all:

./vendor/bin/psalm --alter --issues=all

You can execute the above command in a new project without getting into problems. However, for old projects, you'll most likely have issues that will break functionality once they’re fixed by Psalm. For these, you can supply the --dry-run option.

./vendor/bin/psalm --alter --issues=all --dry-run

This will show you the fixes it will be making to your code, so you'll already have an idea whether it will break something:

Psalm fix issues dry run displayed on the terminal

If you want to specify the issues you want to check for, you can do so by specifying the supported fixes as comma-separated values:

./vendor/bin/psalm --alter --issues=MissingReturnType,UnusedMethod,UnusedProperty

Psalm also comes with a taint analysis feature. This checks for the usual suspects when it comes to security vulnerabilities, such as SQL injection and insecure HTML output. Run the taint analysis tool like so:

./vendor/bin/psalm --taint-analysis

Configuration

To configure Psalm, you need to update the psalm.xml file at the root of your project directory. By default, it looks like this:

<?xml version="1.0"?>
<psalm
    errorLevel="7"
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="app"/>
        <directory name="database/factories"/>
        <directory name="database/seeders"/>
        <ignoreFiles>
            <directory name="vendor"/>
        </ignoreFiles>
    </projectFiles>
<plugins><pluginClass class="Psalm\LaravelPlugin\Plugin"/></plugins></psalm>

<directory> allows you to specify which directories Psalm should check. By default, it checks for the app, database/factories and database/seeders directory. As expected, it's set to ignore the vendor directory.

You can also ignore specific directories by supplying them under <ignoreFiles>. For example, you want to include everything inside the app directory but exclude HTTP middleware:

<projectFiles>
    <directory name="app"/>
    <ignoreFiles>
        <directory name="vendor"/>
                <directory name="app/Http/Middleware"/>
    </ignoreFiles>
</projectFiles>

Specific files can also be ignored:

<file name="app/Services/IgnoreMe.php" />

Just like PHPStan, Psalm also has different levels of strictness, with 1 being the strictest and 8 being more relaxed. By default, it uses level 2. You can read more about the various levels here: Error Levels.

Even though level 8 is very permissive, there some things are always treated as errors, including DuplicateArrayKey, ImpurePropertyAssignment, and InvalidGlobal. Basically, these are practices that should be avoided regardless of how relaxed your coding standards are. The strictness level can be specified using the errorLevel attribute:

<psalm
  errorLevel="[int]"
/>

You can also suppress specific issues via the config file. For example, you don't want the InvalidMethodCall issue to be triggered at all:

<psalm>
    <issueHandlers>
        <InvalidMethodCall errorLevel="suppress" />
    </issueHandlers>
</psalm>

Just like PHPStan, Psalm also supports custom docblock annotations so that it can further understand your codebase.

You can find more information about how to configure Psalm in their Configuration docs.

Enlightn

Enlightn scans your code to check whether it follows best practices in performance, security, and reliability. It's a paid tool, but it also has free checks you can use. At the time of writing, it has 64 checks in the free version and 128 checks in the paid version. For the purposes of this article, we'll only be using the free version.

Installation

You can install Enlightn with the following command:

composer require enlightn/enlightn --dev

Then publish its config:

php artisan vendor:publish --tag=enlightn

Usage

You can then run Enlightn using artisan:

php artisan enlightn

It does take a bit of time compared to the other tools, but here's what the output looks like:

Enlightn issues displayed on the terminal

The results are grouped into three sections: performance, security, and reliability. It outputs the issue along with the result. The result can have three values: passed, failed, or not applicable. There's also a fourth value, error, which happens when Enlightn encounters an error while trying to run a specific check.

The checks are all Laravel-specific and ensure you're not missing any best practices that need to be addressed. While some of these checks may be obvious to some (e.g., minifying assets in production), it's useful that these checks are automated. We're only human after all, and we might miss things even if we've been coding for many years.

At the end of the report, it shows the percentage of passed, failed, not applicable, and errors in each of the three areas:

Enlightn report card output displayed on the terminal

You can also view the report in a web UI, but you need to create an Enlightn account. Once registered, create a new project. Then, in your project, view the API token. Update your .env file with the Enlightn credentials config:

ENLIGHTN_USERNAME="YOUR ENLIGHTN EMAIL"
ENLIGHTN_API_TOKEN="YOUR PROJECT API TOKEN"

Don't forget to refresh the config:

php artisan config:cache

Once that's done, run the enlightn command with the --report option:

php artisan enlightn --report

This should give you the URL where you can view the report, which looks like this:

enlightn web ui

It presents the issues nicely with all the information and documentation links required for you to resolve it.

You can read more about how to use Enlightn in their docs.

Configuration

You can configure Enlightn by updating the config/enlightn.php file.

By default, Enlightn scans the app, migrations, and seeders folders. If you only want to scan controllers, use the following:

'base_path' => [
    app_path('Http/Controllers'),
],

If the issues get too overwhelming, and you'd rather not fix them at this point in time, you can also generate a baseline:

php artisan enlightn:baseline

This works the same way as the baseline in PHPStan and Psalm. It updates the config/enlightn.php file so that it ignores all the existing issues. Thus, all subsequent runs will have no issues, so only newly written code will be checked for issues. Take note of how the dont_report and ignore_errors changes before and after running php artisan enlightn:baseline, so you’ll know which checks have been disabled.

You can read more about how to configure Enlightn in their docs.

Text-editor integration

Running the tools via the command-line isn't that bad. It provides valuable feedback on how to improve your code. However, wouldn't it be nice if this functionality were already integrated with the text-editor or IDE you're using? With IDEs specifically created for PHP development, such as PhpStorm, you already get this for free. I understand that everyone isn’t lucky enough to have their company pay for development tools like PhpStorm, though. PhpStorm isn't low-cost, so we'll be going through alternative text-editors instead. Note that we won't be going through all the text-editors, so you'll have to find plugins for your text-editor or IDE of choice if they're not covered below. Additionally, note that although these plugins offer faster feedback when using static analysis in your projects, don’t expect them to operate in real time. They have to scan the whole codebase, so some delay is to be expected, even for IDEs like PHPStorm.

VS Code

For VS Code, you can use the following extensions to check for code quality in real time as you're typing the code:

  • PHP Static Analysis - static analysis via PhpStan.
  • PHP Sniffer - code style linter and formatter via PHP_CodeSniffer.
  • Psalm - the official VS Code plugin for adding Psalm support.

Vim

Support for code quality tools are provided by the ALE plugin. These are supported for PHP:

  • php-cs-fixer
  • Laravel Pint
  • PhpStan
  • Psalm
  • PHP Mess Detector

Sublime Text

For Sublime Text, the following tools are available:

  • Sublime-phpcs - adds support for PHP_CodeSniffer, PHP Code Beautifier, PHP CS Fixer, PHP Linter, and PHP Mess Detector.
  • Psalm - support for Psalm can be added via the LSP plugin. More information about it can be found in Psalm's documentation.

Continuous integration

The real power of using PHP code-quality tools is when it’s added to your continuous integration process, which means it automatically checks the code every time someone makes a push or pull request to your project repo. In this section, we'll be looking at how to do just that. GitHub actions is available for free so we'll use it for demo purposes. Note that there are some limits to private repos, so set your test repo to public if you can.

To keep things simple, we'll only add steps for checking the code and running the tool. Thus, the developers who pushed the changes will receive some feedback on what's wrong with their code. They can then fix the issues and push their changes until no issues are detected.

You can add the workflows below by creating a .github/workflows folder in the root of your project directory. Then, create a file for adding the workflow (e.g., static-analysis.yml).

The workflows below follow the same general steps. Only the commands for the specific tool changes. Here are the general steps to perform within a workflow:

  1. Declare the name of the workflow:
name: Laravel Pint CI GitHub Action
  1. Set the event that triggers the action to execute. In this case, we want to execute it every time the repo receives a pull request or new code is pushed:
on: [push, pull_request]
  1. Specify the jobs and the steps to execute. It uses the following format:
[Name of job]:
    runs-on: [operating system]
    steps:
            - name: [Name of the task]
            - uses: [Name of another workflow] or run: [Command to execute]

In the workflows below, we always need to check the code. This is how the runner gets access to the repo:

    - name: Checkout code
    uses: actions/checkout@v3

Next, we also need to set up PHP, Composer, and Laravel:

- name: Setup PHP with PECL extension
  uses: shivammathur/setup-php@v2
  with:
    php-version: '8.1'

- name: Composer install
  run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist

- name: Copy .env
  run: php -r "file_exists('.env') || copy('.env.example', '.env');"

- name: Generate key
  run: php artisan key:generate

- name: Directory Permissions
  run: chmod -R 777 storage bootstrap/cache

Once those are all done, execute the command for the code quality tool:

- name: Run Pint
  run: ./vendor/bin/pint --test

If you're new to GitHub actions, I recommend reading the Quickstart for GitHub Actions guide.

Note that in the workflows below, the initial setup for checking the code, setting up PHP, Composer, and Laravel is omitted to avoid repetition. Therefore, don't forget to include them if you plan to try these out on your own.

Laravel Pint

Here's the workflow for Laravel Pint. In this case, we're using a ready-made action. Since Laravel Pint is so simple, we can get away with using something like this. Instead of run, you supply the name of the action. You can find these on GitHub Marketplace or simply search for "[name of code quality tool] github action" on Google:

name: Laravel Pint CI GitHub Action

on: [pull_request, push]

jobs:
  pint:
    runs-on: ubuntu-latest
    steps:

      - name: Run Laravel Pint
        uses: aglipanci/laravel-pint-action@0.1.0
        with:
          preset: laravel
          verboseMode: true
          testMode: true

The options can then be supplied using with. In this case, we're using laravel preset, verboseMode: true to display more details, and testMode: true so that it shows the errors but doesn’t fix them. There's no point in fixing them unless you also commit the changes back to your repo. I'll leave that for you to figure out.

PHP Insights

In the CI environment, you no longer need to do the initial setup since the PHP Insights config is already in your repo. All you have to do is run the php artisan insights command. However, this time, you'll also need to set a minimum level for each check because, if you just run php artisan insights as-is, it won't return an error code. It will simply proceed with the next step. We want it to fail and stop the execution if it doesn't meet the minimum that we've set:

name: PHP Insights CI GitHub Action

on: [push, pull_request]

jobs:
    phpinsights:
        runs-on: ubuntu-latest
        steps:

            - name: Run PHP Insights
                run: php artisan insights --no-interaction --min-quality=70 --min-complexity=80 --min-architecture=75 --min-style=95

PHPStan

It's assumed that you already have a phpstan.neon file in your repo, so it will use this config file when you run the following:

./vendor/bin/phpstan analyse:

name: Larastan CI GitHub Action

on: [pull_request, push]

jobs:
  phpstan:
    name: Larastan
    runs-on: ubuntu-latest
    steps:

      - name: Run Phpstan
        run: ./vendor/bin/phpstan analyse

Psalm

It's also assumed that you already have the psalm.xml file in your repo, so there's no need supply extra configuration options when using Psalm in a CI environment:

name: Psalm CI GitHub Action

on: [pull_request, push]

jobs:
  psalm:
    name: Psalm
    runs-on: ubuntu-latest
    steps:

      - name: Run Psalm
        run: ./vendor/bin/psalm --show-info=true

Enlightn

As discussed earlier, Enlightn checks more than just the code. It also checks things like the database and PHP configuration, which is why it's recommended to run Enlightn on the production server at least once. Alternatively, run it every time you make changes to the software that runs your web application (e.g., upgrading PHP versions or updating the PHP configuration). These kinds of tests are relatively useless in a CI environment if they don't have the same configuration as your production server.

Here's how to run Enlightn on a CI environment:

name: Enlightn CI GitHub Action

on: [pull_request, push]

jobs:
    phpinsights:
        runs-on: ubuntu-latest
        steps:

            - name: Run Enlightn
                run: php artisan enlightn --ci --report
                env:
          ENLIGHTN_USERNAME: '${{ secrets.ENLIGHTN_USERNAME }}'
          ENLIGHTN_API_TOKEN: '${{ secrets.ENLIGHTN_API_TOKEN }}'

In the above workflow, we used the --report option and that requires us to have the ENLIGHTN_USERNAME and ENLIGHTN_API_TOKEN in our environment variables. Since we can't directly add them to our .env file for security reasons, we need to use Actions Secrets. You can add secrets on this page: https://github.com/yourGitHubUsername/yourRepoName/settings/secrets/actions. Click on the New repository secret button to add your secret:

GitHub action secrets

Conclusion

That's it! In this article, we've gone through five tools you can use in Laravel projects to ensure code quality and best practices are followed. We've covered how to use each tool from the command-line and text-editor, as well as add it to your continuous integration process.

There's no right or wrong answers when it comes to choosing tools to adopt on your projects. Each tool has its strengths.

Laravel Pint is the friendliest when it comes to Laravel since it's the official tool supported by Laravel for code quality analysis. This makes it the easiest one to get started using.

PHP Insight's strength is its ability to analyze code in four different aspects: code, complexity, architecture, and style. Among the tools we've covered today, it has the most checks available, and your own checks can be added.

PHPStan and Psalm's strength is in their type-checking, so you can probably use them interchangeably. If you're still using PHP 7 for your projects and want to upgrade your codebase to use some of the PHP 8 features and new syntax, as well as make it more modern, then PHPStan is the way to go. You'll also get some security benefits when using Psalm because of its taint checks.

Enlightn's strength is the detection of bad practices across three different areas: performance, security, and reliability. Although most of the really useful checks are under their paid plan, their free plan provides a ton of value. Their documentation is quite detailed, and they provide documentation for the checks under their "pro" plan. This means we can actually do manual auditing on our code.

You don't have to choose between the tools; you can set them all up in your project. Although they may have some overlap, each does one thing well. You might decide to start with Laravel Pint and later on switch to a combination of PHP Insights, PHPStan, and Enlightn. Alternatively, you could stick to Laravel Psalm if you only need to migrate your codebase to PHP 8 and check for security issues.

Get the Honeybadger newsletter

Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo
    Wern Ancheta

    Wern is a freelance full stack developer primarily working with Laravel and React Native. He loves sharing the things he has learned as a means of documentation and giving back to the community.

    More articles by Wern Ancheta
    An advertisement for Honeybadger that reads 'Turn your logs into events.'

    "Splunk-like querying without having to sell my kidneys? nice"

    That’s a direct quote from someone who just saw Honeybadger Insights. It’s a bit like Papertrail or DataDog—but with just the good parts and a reasonable price tag.

    Best of all, Insights logging is available on our free tier as part of a comprehensive monitoring suite including error tracking, uptime monitoring, status pages, and more.

    Start logging for FREE
    Simple 5-minute setup — No credit card required