PHP Continuous Integration with GitLab CI


In this video we are going to take a look at how we can use GitLab CI to do PHP continuous integration. That is, to automatically watch the code in our GitLab repository for changes, and then execute a set of steps (that we define) in order to ensure our submitted code changes have not broken anything.

The idea is straightforward:

We have our code stored in a repository on our GitLab server.

We set up a list of tasks / steps to carry out each and every time we do a git push of our code changes up to our GitLab server. We will cover how to do this shortly.

These steps would check out the code we have just pushed up to GitLab, then for example, run a composer install to ensure we are running exactly the same versions as we have committed in git. And then run our PHPUnit test suite to make sure we haven't accidentally broken anything.

This is great because:

  • The tests are always run, regardless of whether the developer run them before doing a git push
  • Everything runs with the exact same dependency versions that we have in our development environment (thanks to our composer.lock file)
  • You can make the GitLab CI job do anything, including deploying your code if everything passes

PHP Continuous Integration with GitLab CI

From here on out, I am going to assume you have a GitLab, GitLab CI, and at least one GitLab Runner set up and working.

If you are unsure on any of these, I have covered each one in more detail in previous videos.

The first thing we need to do is to create a .gitlab-ci.yml in the root of our project.

For example, if this was a Symfony project, we would create the .gitlab-ci.yml file at the same level as our project composer.json file.

This is the file that GitLab CI will look for to determine what it should do with your code. Examples might be to composer install, or to run PHPUnit, Codeception, or Behat tests against your code.

Gotcha

This is where we hit upon the first major point of confusion.

I have covered this during the GitLab CI Runner video but it is worth mentioning again.

Assuming you have a freshly built Ubuntu / CentOS server for your runner, it will not be able to run composer install or your test suite, as the base operating system does not come with these files installed.

There are a number of ways round this. The way I would suggest you do this is to use Ansible to build your GitLab CI runner boxes, ensuring they have all the required 'stuff' - php, mysql, selenium... or whatever else you need - rather than doing everything by hand.

If you do follow the Ansible route, the nice thing is, your GitLab CI Runner will likely be a very similar configuration to the one you use to build your development server anyway.

A Basic .gitlab-ci.yml File

This assumes your GitLab CI Runner has PHP and Composer installed.

before_script:
  - composer install

stages:
  - test

test:
  script:
  - php vendor/phpunit/phpunit/phpunit

The full documentation for .gitlab-ci.yml is available here. It's not a huge amount of reading, so it's well worth the 5 minutes or so it will take to give it a once over.

This is as simple as it gets.

As developers, we don't need to do anything different here. Assuming our GitLab server is configured in our project as origin, we would do e.g. git push origin our-branch-name as normal.

In the background, before anything happens, our GitLab CI Runner is going to pull down the exact git commit hash we just pushed up to our GitLab server (origin).

Then, the contents of our .gitlab-ci.yml will be taken into account.

Before anything happens (before_script) we want to run composer install.

We have only declared one stage (again, see the GitLab CI yaml readme for more) which we have called test.

The test stage defines a shell script which will be executed on our GitLab CI Runner. In our case, that script will run the php vendor/phpunit/phpunit/phpunit command.

To make this work without any further parameters we need to have a phpunit.xml file in our project root (the same location as our .gitlab-ci.yml and composer.json files).

The contents of this file are as follows:

<!-- /phpunit.xml --> 
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"
    >
    <testsuites>
        <testsuite name="Test Suite">
            <directory suffix=".php">./tests/</directory>
        </testsuite>
    </testsuites>
</phpunit>

I'm not 100% sure where I got this file from originally, but it has served me well across a number of projects.

Getting More Advanced

In the video you will see how to run sudo commands in a restricted fashion.

For example, in the before_script section, we might wish to do:

before_script:
  - sudo composer self-update
  - composer install

* snip *

But this will fail, and rightly so. Watch the video to find out why this fails, and how to fix it.

In the next video we will get a little more real world with our example.

Episodes