push PHP to the Java-ecosystem: phpunit with maven

Jumieges Abbey France, by Christoph Burmeister (own photo)

Jumieges Abbey France, by Christoph Burmeister (own photo)


I like Java… and I also like the PHP-language which seems … more straight-forward then Java. If you just want to script some php-sites, there is no good reason for a full-equipped build-environment.
But if your project gets larger, you definitely will need a professional build-tool like ant or maven, a continous-integration server like Jenkins/Hudson or Teamcity, backended by a repository like Artifactory, Nexus or Archiva and a quality-assuring-system like Sonar.
The first step in this project is the usage of Maven to unit-test and assemble the php-project. I like to use the Netbeans-IDE for PHP-development. Following the basic setup:

maven_php_project

If you don’t already have, you will need to download and unzip the latest php-distribution and set the PHP_HOME-variable. You can get the distribution here: http://php.net/downloads.php . Just unzip the archive and set PHP_HOME and take it into classpath. Open a shell and with „php -v“ you should see a valid result:

C:\Users\christoph>php -v
PHP 5.4.11 (cli) (built: Jan 16 2013 20:26:26)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies

First there is the FooClass which should be tested in the build-process:

<?php

class FooClass {
    public function __construct() {
        //nop
    }

    public function bar($option) {
        return $option;
    }
}

?>

The test for the FooClass is by default the class-name postfixed by „Test“:

<?php

require_once 'eu/christophburmeister/libs/FooClass.php';

class FooClassTest extends PHPUnit_Framework_TestCase {

	public function testBarPostive()
	{
		$expectedResult = True;
		$classUnderTest = new FooClass();
		$actualResult = $classUnderTest->bar(True);
		
		$this->assertEquals($expectedResult,$actualResult);
	}

	public function testBarNegative()
	{
		$expectedResult = False;
		$classUnderTest = new FooClass();
		$actualResult = $classUnderTest->bar(False);
		
		$this->assertEquals($expectedResult,$actualResult);
	}
}

After successful testing we provide an maven-assembly-descriptor for simply zipping the php-sources as „application“:

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <id>application</id>
    <formats>
        <format>zip</format>
    </formats>
    <fileSets>
        <fileSet>
            <directory>src/main/php</directory>
            <outputDirectory>/</outputDirectory>
        </fileSet>
    </fileSets>
</assembly>

And finally: the pom.xml which we all know from our Java-projects:

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.phpmaven</groupId>
        <artifactId>php-parent-pom</artifactId>
        <version>2.0.2</version>
    </parent>

    <properties>
        <phpmaven.release.number>2.0.2</phpmaven.release.number>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <phpunit.version>3.6.10</phpunit.version>
    </properties>

    <groupId>eu.christophburmeister</groupId>
    <artifactId>maven-php-example</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>php</packaging>
    
    <pluginRepositories>
        <pluginRepository>
            <id>release-repo1.php-maven.org</id>
            <name>PHP-Maven 2 Release Repository</name>
            <url>http://repos.php-maven.org/releases</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </pluginRepository>
        <pluginRepository>
            <id>snapshot-repo1.php-maven.org</id>
            <name>PHP-Maven 2 Snapshot Repository</name>
            <url>http://repos.php-maven.org/snapshots</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
    <repositories>
        <repository>
            <id>release-repo1.php-maven.org</id>
            <name>PHP-Maven 2 Release Repository</name>
            <url>http://repos.php-maven.org/releases</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
        <repository>
            <id>snapshot-repo1.php-maven.org</id>
            <name>PHP-Maven 2 Snapshot Repository</name>
            <url>http://repos.php-maven.org/snapshots</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
	
    <build>
        <plugins>
            <plugin>
                <groupId>org.phpmaven</groupId>
                <artifactId>maven-php-plugin</artifactId>
                <version>${phpmaven.plugin.version}</version>  
                <!-- http://www.php-maven.org/branches/2.0.2/maven-php-plugin/test-mojo.html -->
                <configuration>
                    <phpExe>${env.PHP_HOME}/php.exe</phpExe>
                    <resultFolder>${project.basedir}/target/surefire-reports</resultFolder>
                    <skipTests>false</skipTests>
                    <testPostfix>Test</testPostfix>
                </configuration>
            </plugin>			

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <descriptors>
                        <descriptor>src/main/assembly/application.xml</descriptor>
                    </descriptors>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>de.phpunit</groupId>
            <artifactId>PHPUnit</artifactId>
            <version>${phpunit.version}</version>
            <type>phar</type>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

Taken all this together, you can simply do a „mvn clean test“:

[INFO] Scanning for projects...
[WARNING] 
[WARNING] Some problems were encountered while building the effective model for eu.christophburmeister:maven-php-example:php:1.0-SNAPSHOT
[WARNING] 'build.pluginManagement.plugins.plugin.(groupId:artifactId)' must be unique but found duplicate declaration of plugin org.phpmaven:maven-php-plugin @ org.phpmaven:php-parent-pom:2.0.2, C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\org\phpmaven\php-parent-pom\2.0.2\php-parent-pom-2.0.2.pom, line 114, column 25
[WARNING] 
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING] 
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING] 
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building maven-php-example 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ maven-php-example ---
[INFO] Deleting C:\Users\christoph\Dropbox\Christoph\dev\NetBeans_PHP_7.2.1_Portable-workspace\php-maven-example\target
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:set-sources (default-set-sources-1) @ maven-php-example ---
[INFO] 
[INFO] --- maven-plugin-plugin:3.2:descriptor (default-descriptor) @ maven-php-example ---
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:extractDependencies (default-extractDependencies) @ maven-php-example ---
[INFO] Unpacking dependencies...
[INFO] 
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ maven-php-example ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Users\christoph\Dropbox\Christoph\dev\NetBeans_PHP_7.2.1_Portable-workspace\php-maven-example\src\main\resources
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:resources (default-resources) @ maven-php-example ---
[INFO] Copying source files and performing LINT validation...
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:extractTestDependencies (default-extractTestDependencies) @ maven-php-example ---
[INFO] Unpacking dependencies...
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\PHPUnit\3.6.10\PHPUnit-3.6.10.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\File_Iterator\1.3.1\File_Iterator-1.3.1.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\Text_Template\1.1.1\Text_Template-1.1.1.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\PHP_CodeCoverage\1.1.2\PHP_CodeCoverage-1.1.2.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\PHP_TokenStream\1.1.3\PHP_TokenStream-1.1.3.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\PHP_Timer\1.0.2\PHP_Timer-1.0.2.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\PHPUnit_MockObject\1.1.1\PHPUnit_MockObject-1.1.1.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\com\symfony-project\YAML\1.0.6\YAML-1.0.6.phar to target directory
[INFO] 
[INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ maven-php-example ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Users\christoph\Dropbox\Christoph\dev\NetBeans_PHP_7.2.1_Portable-workspace\php-maven-example\src\test\resources
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:testResources (default-testResources) @ maven-php-example ---
[INFO] Copying source files and performing LINT validation...
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:test (default-test) @ maven-php-example ---
[INFO] 
-------------------------------------------------------
T E S T S
-------------------------------------------------------

[INFO] Starting tests.
[INFO] 

Results :

TEST SUCCESS
  SUCCESS [FooClassTest] 0.015987s / 2 Tests, 0 Failures, 0 Errors

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.106s
[INFO] Finished at: Tue Feb 12 19:12:49 CET 2013
[INFO] Final Memory: 8M/20M
[INFO] ------------------------------------------------------------------------

A packaging after successfull testing can be executed via „mvn clean package“:

[INFO] Scanning for projects...
[WARNING] 
[WARNING] Some problems were encountered while building the effective model for eu.christophburmeister:maven-php-example:php:1.0-SNAPSHOT
[WARNING] 'build.pluginManagement.plugins.plugin.(groupId:artifactId)' must be unique but found duplicate declaration of plugin org.phpmaven:maven-php-plugin @ org.phpmaven:php-parent-pom:2.0.2, C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\org\phpmaven\php-parent-pom\2.0.2\php-parent-pom-2.0.2.pom, line 114, column 25
[WARNING] 
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING] 
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING] 
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building maven-php-example 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ maven-php-example ---
[INFO] Deleting C:\Users\christoph\Dropbox\Christoph\dev\NetBeans_PHP_7.2.1_Portable-workspace\php-maven-example\target
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:set-sources (default-set-sources-1) @ maven-php-example ---
[INFO] 
[INFO] --- maven-plugin-plugin:3.2:descriptor (default-descriptor) @ maven-php-example ---
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:extractDependencies (default-extractDependencies) @ maven-php-example ---
[INFO] Unpacking dependencies...
[INFO] 
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ maven-php-example ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Users\christoph\Dropbox\Christoph\dev\NetBeans_PHP_7.2.1_Portable-workspace\php-maven-example\src\main\resources
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:resources (default-resources) @ maven-php-example ---
[INFO] Copying source files and performing LINT validation...
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:extractTestDependencies (default-extractTestDependencies) @ maven-php-example ---
[INFO] Unpacking dependencies...
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\PHPUnit\3.6.10\PHPUnit-3.6.10.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\File_Iterator\1.3.1\File_Iterator-1.3.1.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\Text_Template\1.1.1\Text_Template-1.1.1.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\PHP_CodeCoverage\1.1.2\PHP_CodeCoverage-1.1.2.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\PHP_TokenStream\1.1.3\PHP_TokenStream-1.1.3.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\PHP_Timer\1.0.2\PHP_Timer-1.0.2.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\de\phpunit\PHPUnit_MockObject\1.1.1\PHPUnit_MockObject-1.1.1.phar to target directory
[INFO] Extracting C:\Users\christoph\Dropbox\Christoph\dev\apache-maven-3.0.3-repo\com\symfony-project\YAML\1.0.6\YAML-1.0.6.phar to target directory
[INFO] 
[INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ maven-php-example ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Users\christoph\Dropbox\Christoph\dev\NetBeans_PHP_7.2.1_Portable-workspace\php-maven-example\src\test\resources
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:testResources (default-testResources) @ maven-php-example ---
[INFO] Copying source files and performing LINT validation...
[INFO] 
[INFO] --- maven-php-plugin:2.0.2:test (default-test) @ maven-php-example ---
[INFO] 
-------------------------------------------------------
T E S T S
-------------------------------------------------------

[INFO] Starting tests.
[INFO] 

Results :

TEST SUCCESS
  SUCCESS [FooClassTest] 0.011674s / 2 Tests, 0 Failures, 0 Errors

[INFO] 
[INFO] --- maven-php-plugin:2.0.2:phar (default-phar) @ maven-php-example ---
[INFO] 
-------------------------------------------------------
P A C K A G E    P H A R
-------------------------------------------------------
[INFO] phar filename: maven-php-example-1.0-SNAPSHOT.phar
[INFO] 
[INFO] --- maven-plugin-plugin:3.2:addPluginArtifactMetadata (default-addPluginArtifactMetadata) @ maven-php-example ---
[INFO] 
[INFO] --- maven-assembly-plugin:2.4:single (default) @ maven-php-example ---
[INFO] Reading assembly descriptor: src/main/assembly/application.xml
[INFO] Building zip: C:\Users\christoph\Dropbox\Christoph\dev\NetBeans_PHP_7.2.1_Portable-workspace\php-maven-example\target\maven-php-example-1.0-SNAPSHOT-application.zip
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.334s
[INFO] Finished at: Tue Feb 12 19:14:58 CET 2013
[INFO] Final Memory: 10M/25M
[INFO] ------------------------------------------------------------------------

I think the most important part of the pom is the usage of the correct parent-pom, which induces many of the php-relevant stuff. Second the guyes from phpmaven.org provide the „maven-php-plugin“ which simply does all the work for us. Within this plugin-configuration you can specify the output-directory for the phpunit-tests or change the default postfix of the test-classes. There are reporting-functions which are documented separately on their website.

With this setup you can easily expand you simple project into a full-featured ci-ready environment with all the great services the maven-universe provides. In the next steps I will include this project into a ci-system.

References: