Laravel Codebase Maintenance: Best Practices

Keeping your codebase up to date is essential, but with busy schedules and project deadlines, it can feel like a low-priority task. Often, developers get stuck using outdated versions or maintaining legacy systems, making updates seem like an overwhelming challenge.

Neglecting updates doesn't just deprive you of powerful new features—it can expose your project to security risks and make future upgrades far more complex and stressful than they need to be.

In this article, we'll explore practical strategies to ensure your Laravel app stays current. With a few proactive steps, updating doesn't have to be a hassle.

Laravel Codebase Maintenance: Best Practices

Table of Contents

Causes of Outdated Code

Before diving into the tips, it's important to understand why codebases become outdated:

  • Time Constraints: Deadlines and deliverables take precedence, pushing updates down the priority list.
  • Fear of Breaking Things: The "if it ain't broke, don't fix it" mentality leads to complacency.
  • Lack of Automation: Without automated updates, it's easy to forget or postpone them.
  • Dependency Conflicts: One outdated package can prevent updating others, creating a chain reaction.
  • Manual Processes: Relying on manual updates increases the chance of missing critical updates.

Understanding these causes helps in crafting strategies to counteract them.

Recommendations for Keeping Your Codebase Up to Date

Here are eight essential tips to help you maintain a current and maintainable codebase.

1. Leverage Dependabot for Automated Updates

Automating updates is a key step toward maintainable code. Dependabot is a GitHub tool that automatically checks for outdated dependencies and opens pull requests to update them. It's free and easy to set up.

Create a configuration file by adding a .github/dependabot.yml file to your repository and configure it.

.github/dependabot.yml
yaml
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: weekly
      day: wednesday
      time: "06:00" # 08:00 AM in Oslo (CET/CEST), adjusted for UTC

  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "weekly"
      day: wednesday
      time: "06:00"

  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: wednesday
      time: "06:00"

This setup tells Dependabot to check for updates to your Github Actions workflows, Composer (PHP) and npm (JavaScript) dependencies every week on wednesdays at 06:00 in the morning.

2. Automatically Merge Pull Requests from Dependabot

To further automate the process, you can configure Dependabot to automatically merge minor and patch updates. To enable Auto-Merge you should add or update your dependabot.yml with:

.github/dependabot.yml
yaml
name: "Dependabot auto-merge"
on: pull_request_target

permissions:
  pull-requests: write
  contents: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    if: ${{ github.actor == 'dependabot[bot]' }}
    steps:

      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v2.2.0
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"

      - name: Auto-merge Dependabot PRs for semver-minor updates
        if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

      - name: Auto-merge Dependabot PRs for semver-patch updates
        if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

In the example above you'll need to add a GITHUB_TOKEN to your repository secrets. This token is used to authenticate Github CLI / gh that is used to merge the pull requests.

Pros:

  • Saves time by reducing manual intervention.
  • Keeps dependencies up to date regularly.

Cons:

  • Risk of introducing breaking changes if test-suite is not comprehensive.
  • May not align with strict code review policies.

It's important to have up-to-date tests and that all checks pass before merging. You should also be careful with automatically deploying these changes to production without human oversight.

3. Cases When You Shouldn't Automatically Merge Pull Requests

Automatic merging isn't always the best choice. Here are some situations where you might want to avoid auto-merge:

  • Strict Policies: Organizations that require human code reviews.
  • Critical Applications: Projects where stability is of the highest importance.
  • Complex Dependencies: Updates that could introduce significant changes requiring manual testing.

In these scenarios, it's better to review and merge updates manually.

4. Use Version Constraints Wisely in PHP

Setting appropriate version constraints in your composer.json ensures you receive updates without unexpected breaking changes. Example of Version Constraints:

composer.json
json
{
  "require": {
    "php": "^8.3",
    "laravel/framework": "^11.23.2",
    "laravel/tinker": "^2.9.0"
  }
}

The caret (^) allows updates that do not change the leftmost non-zero digit. In this case, for laravel/framework it allows any version from 11.0 up to but not including 12.0.

Another command that you should use regularly is the composer bump command. It updates all dependencies and adjusts the version numbers in your composer.json. This simplifies the process of keeping your PHP dependencies current. And it allows you to easily see which versions you're using.

bash
composer bump

5. Manage JavaScript Dependencies

Keeping your frontend dependencies up to date is equally important. Set Version Constraints in package.json. Use the caret (^) or tilde (~) to define acceptable version ranges.

composer.json
json
{
  "dependencies": {
    "prettier": "^3.3.3",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "vite": "^5.4.8"
  }
}

Also with npm you can use a tool to update the versions in package.json, similar to composer bump. Updating Dependencies with npm-check-updates:

  1. Install npm-check-updates:
bash
npm install -g npm-check-updates
  1. Update package.json:
bash
ncu -u
  1. Install Updated Packages:
bash
npm install

6. Block Vulnerable Dependencies

The team at Roave has created the package Security Advisories which prevents the installation of Composer dependencies with known security vulnerabilities. Instead of reacting after you've installed vulnerable packages, this tool stops them from being added in the first place.

To get started, simply add it as a development dependency:

bash
composer require --dev roave/security-advisories:dev-latest

7. Maintain and Automate a Robust Test Suite

A strong test suite is your safety net when updating dependencies.

  • Write Comprehensive Tests: Cover critical paths and functionalities.
  • Automate Testing: Integrate with CI/CD pipelines like GitHub Actions.
  • Require Passing Tests: Configure your repository to prevent merging if tests fail.

Automate your test workflow to ensure updates are tested efficiently. Create a Github Actions workflow at .github/workflows/phpunit.yml:

.github/workflows/phpunit.yml
yaml
name: Run PHPUnit

on: [push, pull_request]

jobs:
  phpunit:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Get Composer Cache Directory
        id: composer-cache
        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

      - name: Cache composer dependencies
        uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: "8.3"
          tools: composer:v2
          coverage: none

      - name: Install Composer dependencies
        run: composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --optimize-autoloader

      - name: Test with phpunit
        run: php artisan test

If any of your current dependencies have known vulnerabilities, the installation will fail, and Composer will show you the specific problematic packages. Going forward, when you run composer require or composer update, Roave Security Advisories will block the installation of any vulnerable packages, ensuring your project stays secure.

8. Validating Your composer.json for Easier Maintainability and Updates

One powerful, but often overlooked, step in keeping your Laravel application maintainable and up to date is validating your composer.json file. Running validation checks ensures your dependency configuration file is syntactically correct.

Why Composer Validation is Important

  • Avoid Broken Dependency Files: A malformed or misconfigured composer.json can prevent your dependencies from installing correctly. Validating the file ensures that everything is configured properly before installation.
  • Strict Adherence to Standards: The composer validate --strict command checks for not just basic syntax errors but also enforces adherence to best practices, like having required fields such as name, description, and license.
  • Easier Troubleshooting: Composer validation can catch issues early on, preventing them from cascading into bigger problems down the line. This makes future debugging and updates easier.

Here is a Github Actions workflow that validates your composer.json file:

.github/workflows/composer-validate.yml
yaml
name: Run composer validate

on: [push, pull_request]

jobs:
  composer-validate:
    name: Validate composer.json
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Get Composer Cache Directory
        id: composer-cache
        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

      - name: Cache Composer dependencies
        uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          tools: composer:v2
          coverage: none

      - name: Validate composer.json
        run: composer validate --strict

      - name: Install Composer dependencies
        run: composer install --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

      - name: Check namespaces
        run: composer dump-autoload --optimize --strict-psr

Conclusion

Keeping your Laravel codebase up to date is crucial for security, performance, and maintainability. Regular updates help you leverage new features, improve performance, and reduce technical debt.

By implementing the tips in this article, you're doing your future self and your clients a favor. You'll be setting the stage for a robust, easily maintainable Laravel application that stands the test of time. In the long run, that's what truly matters.

If you're looking to learn more about keeping your codebase in top shape, consider subscribing to my newsletter. I share weekly articles packed with developer tips and best practices. Here are some resources that you might find useful:

Happy coding, and may your projects always be up to date!

Stay up to date

Get notified when I publish something new, and unsubscribe at any time.