Continuous integration(CI) for laravel using github actions

Srinath Reddy • January 3, 2021

php laravel github

Github provides continuous integration(CI) build pipelines using their service called github actions.

Github actions CI builds are called workflows, and a workflow can be triggered when a certain event happens on your github repository.

Events like a commit is made, a pull request is opened, etc will trigger workflow runs on github actions.

If you are working with a team of developers, Github actions can help you validate the pull requests by running required test cases against a pull request, So we can confidently merge a pull request when it is opened and all the test cases are passed.

Github actions provides unlimited build minutes for public repositories. For private repositories, The free account gives us 2000 build minutes/month. For a solo developer, This is more than enough.

Laravel CI generally consists of various steps to make sure the application runs smoothly when it goes to production. We will create a github actions workflow to perform the following tasks when a pull request is opened.

Once the above tasks are successfully executed without any issues, Then we can confidently merge the pull request if the code changes looks good.

Create Workflow File For Laravel

All the workflow files should reside inside .github/workflows directory inside your project root.

So first, Create these two directories.

mkdir .github && mkdir .github/workflows

Workflow files uses yaml syntax to define the tasks. A single workflow file can contain multiple tasks.

Let's create our workflow file inside .github/workflows directory. I am going to call this file laravel-ci.yml. You can use any file name you want. Just make sure the file extension is .yml or .yaml.

touch .github/workflows/laravel-ci.yml

Open the file in your favourite code editor and add the following code in it.

name: Laravel CI

on:
  pull_request:
    branches: [ master, staging ]

jobs:
  laravel-tests:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/[email protected]

The above file provides a basic skeleton for our github actions laravel ci build. It is configured to run the workflow on the latest version of ubuntu operating system when a pull request is opened against master or staging branches.

Our workflow is currently not running any tasks. So let's start by adding some tasks to this workflow.

1. Setup PHP(Optional)

The ubuntu-latest image comes with the latest version of php installed already. If your application requires php version of 7.3 and lower, Add the below code to steps section in your workflow file to switch to the php version your application is using.

    - name: Setup PHP
      uses: shivammathur/[email protected]
      with:
        php-version: 7.2 # Change to the php version your application is using
        extensions: mbstring, bcmath # Install any required php extensions

2. Create .env file

Every laravel application should have a .env file in it to manage its environment variables. The first task we run on our workflow is copying .env.example file to .env.

Update the steps section in the workflow file with below

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

3. Install composer dependencies

The next step is installing composer dependencies. By testing the installation of composer dependencies, We are making sure there are no broken packages in the pull request.

    - name: Install composer dependencies
      run: composer install

4. Set our directory permissions correctly

Laravel requires certain directories to be writable by the web server. We can set these directory permissions to 777 as this is just a CI server.

You should never set directory permissions to 777 in your actual server. Setting this might result in unauthorized access to restricted files.

    - name: Set required directory permissions
      run: chmod -R 777 storage bootstrap/cache

5. Generate application encryption key

Since we are creating our .env file freshly, It will not have an encryption key. So let's create and set an encryption key.

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

6. Create temporary database

In the next steps, We are going to run database migrations and unit tests. We need a temporary database to run these tasks. We can create a temporary sqlite database by adding the following task.

    - name: Create temporary sqlite database
      run: |
        mkdir -p database
        touch database/database.sqlite

7. Run database migrations

We can run database migrations on the temporary database we created. Doing so we can make sure that the new and existing migration files still work and makes necessary schema changes.

Before we run the migrations, We need to set our DB_CONNECTION and DB_DATABASE environment variables to the newly created temporary sqlite database. We can set these environment variables using the env command as shown below.

    - name: Run laravel database migrations
      env:
        DB_CONNECTION: sqlite
        DB_DATABASE: database/database.sqlite
      run: php artisan migrate --force

8. Install NPM Dependencies

The next step is installing node dependencies. By testing the installation of npm dependencies, We are making sure there are no broken npm packages in the pull request.

    - name: Install NPM dependencies
      run: npm install

9. Minify CSS and JS files

We can run npm run prod command to test and make sure our frontend build command works as expected and minifies our css and js files.

    - name: Minify CSS and JS files
      run: npm run prod

10. Run Unit tests

Finally, Our application is completely ready with all the necessary composer and node packages installed. It has a temporary database, env file, and the frontend assets are compiled and minified.

Now we can dive into running our unit tests to make sure the new code changes did not break existing functionality.

    - name: Run unit tests via PHPUnit
      env:
        DB_CONNECTION: sqlite
        DB_DATABASE: database/database.sqlite
      run: ./vendor/bin/phpunit

Completed workflow file

If you follow all the steps correctly, Your completed workflow file should look like below.

name: Laravel CI

on:
  pull_request:
    branches: [ master, staging ]

jobs:
  laravel-tests:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/[email protected]
    - name: Copy .env.example to .env
      run: php -r "file_exists('.env') || copy('.env.example', '.env');"
    - name: Install composer dependencies
      run: composer install
    - name: Set required directory permissions
      run: chmod -R 777 storage bootstrap/cache
    - name: Generate encryption key
      run: php artisan key:generate
    - name: Create temporary sqlite database
      run: |
        mkdir -p database
        touch database/database.sqlite
    - name: Run laravel database migrations
      env:
        DB_CONNECTION: sqlite
        DB_DATABASE: database/database.sqlite
      run: php artisan migrate --force
    - name: Install NPM dependencies
      run: npm install
    - name: Minify CSS and JS files
      run: npm run prod
    - name: Run unit tests via PHPUnit
      env:
        DB_CONNECTION: sqlite
        DB_DATABASE: database/database.sqlite
      run: ./vendor/bin/phpunit

After you commit the above file to your github repository, Github will run the said tasks when ever a new pull request is opened against master and staging branches.

Failure / Success status of the workflow run will be displayed on the pull request on which it ran.

You can view all the workflow runs triggered in a specific repository by clicking on the actions tab in the navigation menu of a repository page.

Did you enjoy reading the above article? share your thoughts with me on twitter. Also be sure to check out my recent posts.