Continuous Integration for PHP

It has been a very long time since I have used PHP in any capacity for web development. And I am not planning doing so again any time soon (sorry). But I have had the opportunity to apply my practices on a very small, though still useful scale in PHP. From what I could gather it is indeed very possible to properly and continuously integrate PHP projects, provided you use the right tools for the job: a build tool and a dependency manager (optional but always recommended).

A simple project

Disclaimer: I do not condone illegal behaviour… But for research purposes I have created a custom search module for Synology Download Manager, and as it happens this requires PHP. Hence the synology-dlm-rarbg project (https://github.com/mcartoixa/synology-dlm-rarbg). No Laravel or Symfony involved here obviously but I want to believe that the concepts will remain the same on a more consequent project.

The main elements of the project are:

The build file

Given my lack of recent experience with the technology I was a bit worried at first about the state of software development in PHP. But it turned out surprisingly pretty decent: you can find a fine dependency manager in Composer and a very decent build tool in Phing. It is based on the old timer Apache Ant and so probably suffers from the same drawbacks (that I will cover in a subsequent post). But like the original: as long as you respect the tool (which involves understanding it…) you can go pretty far with it.

So Phing it is, and the build.xml file defines the following targets (pretty much consistent with what we have already seen in .NET, node.js and Ruby):

  • clean: cleans the build (the tmp\ directory).
  • build: runs PHPLint on the source code and copies the source code and the dependencies in the tmp\out\bin folder.
    • If I had to do it today I would probably isolate the first action in an analyze target. The second action would do well in the package target. My own consistency evolves over time…
  • test: executes the tests using PHPUnit.
  • package: creates a package for the search module (which is an archive of the source code and its dependencies).
  • rebuild: shortcut for the combination of clean and build.
  • release: shortcut for the combination of clean, build, and package.

Before showing some code it appears that I may have over engineered this build a bit: I created a second build file dlm\build.xml that is called by the first. Obviously I cannot clearly remember why right now but I think it had to do with the possibility of packaging multiple search modules in the same project. Anyway, how do you call another build file then? Simple:

<target name="test" depends="prepare.build">
  <phing phingfile="build.xml" dir="./dlm" target="test" inheritAll="true" haltonfailure="true" />
</target>

The test target must execute PHPUnit on the tests files, and it needs to have the dependencies properly downloaded before that (using Composer):

<target name="test" depends="test.prepare">
  <phpunit printsummary="true" bootstrap="vendor/autoload.php" haltonerror="true" haltonfailure="true">
    <formatter type="plain" usefile="false" />
    <batchtest>
      <fileset dir="./tests">
        <include name="**/*Test*.php"/>
      </fileset>
    </batchtest>
  </phpunit>
</target>

<target name="test.prepare">
  <composer command="update" composer="bin/composer.phar">
    <arg value="-q" />
    <arg value="-n" />
  </composer>
</target>

The key in this project is to be able to package the search module in the format that is expected by the Download Manager, which is explained in a PDF document downloadable somewhere on the Synology website. I would never remember how, but now it has been described in Phing it seems pretty straightforward:

<target name="package" depends="package.prepare,build">
  <tar destfile="tmp/out/bin/mcartoixa_rarbg.dlm" compression="gzip">
    <fileset dir="tmp/bin/dlm">
      <include name="**/**" />
      <exclude name="composer.*" />
    </fileset>
  </tar>
</target>

The script file

The gist of the script file build.bat is simply to install the dependencies, including Phing, and then run the build:

CALL .\bin\composer.bat install -q -n
CALL .\vendor\bin\phing.bat -f %PROJECT% %TARGET%

As you can see Composer is itself stored in the repository as a PHAR archive. I could have chosen to download it instead. The key here is that the repository is ready for development: nothing to configure.

The CI configuration file

For whatever reason I have again chosen Travis CI as my Continuous Integration platform, which configuration is simply:

install:
  - php bin/composer.phar install -n

script:
  - vendor/bin/phing -f build.xml prepare.version release

A more complete project

Nothing much to be found here (as usual?), but I think proper foundations have been laid out above. As with every technology it is essential to delve into the build tool, understand it and use it to its full potential. For instance, if Phing is anything like Apache Ant (and it looks like it is), understanding the core structures (like FileLists of FileSets) is very important.

Also, I would tend to install every dependency locally instead of globally (as part of the build, including frameworks like Laravel for instance). It makes it easier to handle the %PATH% consistently for everyone (including your Continuous Integration platform) and it makes it easier to use different versions on different projects (or even branches).

About Continuous Integration