Dev Talks

Web Development Tutorials

Setting Up Continuous Integration (CI) in Laravel with GitHub Actions
DevOps

Setting Up Continuous Integration (CI) in Laravel with GitHub Actions

Published on Dec 25, 2024

CI/CD is a topic that has been there for sometime. I pit you know what it is, because you may hear it or read something about it many times. In case you didn't, let say it's a shortcut for Continues Integration and Continues Delivery (or Deployment). In a simple words both are about automation, automating product development and integrations and the CD part for automating deployments.

If you'r a developer who didn't apply CI/CD before. Well, I've a good news, it's easier than you think! and it worth it. Wether you work solo or in a team, having some automated checks while you do your PR's or Pushing to the repository, is something will give you confident about the code that has been added.

I'll explain the CI part in this article. And I'll make sure that I don't waste your time. And I believe you'll get almost most of the stuff you need to do or understand about CI.


Why Use GitHub Actions for CI?

GitHub Actions is a powerful tool that integrates seamlessly with GitHub repositories, providing a straightforward way to automate workflows. It is free for public repositories and includes a generous amount of free usage for private repositories.

I'll apply workflows on the repo we have created in the API series.

In case you didn't follow a long, here are the tutorials list:

  1. Laravel REST API Part 1: Install with Docker and Sail
  2. Laravel REST API Part 2: Create Migrations, Models, and Factories
  3. Laravel REST API Part 3: Authentication Using Sanctum
  4. Laravel REST API Part 4: Unit Test Login and Logout APIs
  5. Laravel REST API Part 5: Add Products List API
  6. Laravel REST API Part 6: Unit Testing Products List API
  7. Laravel REST API Part 7: Refactor to Actions
  8. Laravel REST API Part 8: Caching Products List with Redis
  9. Laravel REST API Part 9: Create Order API
  10. Laravel REST API Part 10: Adding Swagger Documentation


Step 1: Create a GitHub Actions Workflow File

  1. In your Laravel project create folder in the root directory and name it .github/workflows
  2. Create new file inside that folder let's call it CI.yml.

The naming of the yaml files doesn't matter but the folder structure matters.


Step 2: Define the Workflow

We will have our first workflow that will run one job for testing our application, in phpunit.yml file add the following code

name: Run Unit Tests

on: [push]

jobs:
  phpunit:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: true
      matrix:
        php: [8.4]

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

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: json, dom, curl, libxml, mbstring
          coverage: none

      - name: Install Dependencies
        run: composer install --no-progress --prefer-dist

      - name: Prepare Laravel Application
        run: |
          cp .env.example .env
          php artisan key:generate

      - name: Run tests
        run: php artisan test


Let's break it down and explain the content of this file.


Wokflow Name

name: Run Unit Tests

This specifies the name of the workflow, which will appear in the Actions tab of the repository.


Trigger

on: [push]

This means the workflow will be triggered every time there is a push to the repository, regardless of the branch.

Ofcourse you can still specify specific branch or trigger it on other event or maybe on multiple events (you may check events that can trigger workflow from here)


Jobs

jobs:
  phpunit:
    runs-on: ubuntu-latest

jobs: Contains a list of tasks to execute. Here, a single job named phpunit is defined.

runs-on: Specifies the virtual machine that the job will run on (the job running on a vps hosted on github, or you can host it on your own servers if you want), in this case, the latest Ubuntu version. 


Strategy

strategy:
   fail-fast: true
   matrix:
     php: [8.4]

fail-fast: If a job in the matrix fails, the workflow will immediately stop running other jobs.

matrix: Allows the workflow to run the job with PHP version (8.4).


Steps

Step 1: Checkout Code

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

This step uses the actions/checkout action to clone the repository's code into the runner's workspace. This is a ready made action repository we can use and you know more about it here


Step 2: Setup PHP

  - name: Setup PHP
    uses: shivammathur/setup-php@v2
    with:
      php-version: ${{ matrix.php }}
      extensions: json, dom, curl, libxml, mbstring
      coverage: none
  • shivammathur/setup-php: A popular action to set up PHP environments in GitHub Actions (you can find more about it here).
  • php-version: Specifies the PHP version to use, which comes from the matrix (8.4 in this case).
  • extensions: Installs additional PHP extensions required by the Laravel application.
  • coverage: Disables code coverage tools for this run.


Step 3: Install Dependencies

  - name: Install Dependencies
    run: composer install --no-progress --prefer-dist
  • Runs composer install to install the PHP dependencies required by the Laravel application.
  • --no-progress: Disables progress output for a cleaner log.
  • --prefer-dist: Downloads distribution archives for dependencies instead of source code.


Step 4: Prepare Laravel Application

  - name: Prepare Laravel Application
    run: |
      cp .env.example .env
      php artisan key:generate
  • cp .env.example .env: Copies the .env.example file to .env to set up the environment configuration.
  • php artisan key:generate: Generates an application key required for the Laravel application to function.


Step 5: Run Tests

  - name: Run tests
    run: php artisan test
  • Finally executing our test by running php artisan test.


Step 3: Add Static Analysis and Code Linting

You may now want to add more checks in your CI. so, we will add PHPStan but it should be installed in your composer.json file and Laravel Pint which comes pre-installed with recent Laravel versions.

So, our final CI.yml file would be something like this:

name: Run CI phpunit, phpstan, Pint

on: [push]

permissions:
  contents: write
  
jobs:
  phpunit:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: true
      matrix:
        php: [8.4]

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

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: json, dom, curl, libxml, mbstring
          coverage: none

      - name: Install Dependencies
        run: composer install --no-progress --prefer-dist

      - name: Prepare Laravel Application
        run: |
          cp .env.example .env
          php artisan key:generate

      - name: Run tests
        run: php artisan test

      - name: Run PHPStan
        run: vendor/bin/phpstan analyse app tests --error-format=github

      - name: Run Laravel Pint
        run: vendor/bin/pint

      - name: Commit linted files
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          commit_message: "Fixes coding style"


Note that:  we have added two more runs at the end of the file for PHPStan and Laravel Pint, and an important line at the beginning:

permissions:
  contents: write

This line added because we have added an auto commit capability at the end of the file as well. so, github actions will add an automatic commit for the none styled code. So, we need to give it a permission to be bale to do that.


Step 4: Push your code 

Now, push your code and check on Actions tab on the repo and watch the running job, if it fails you'll be able to see the reason and hopefully you fix it.


Finally 

With this automated setup, it will make you more confident in the code you push to the repo before you deploy. And it could be very handy process with code reviews while adding PR's.


Happy Coding!👨‍💻

Stay tuned and pray for Palestine 🇵🇸