Stephan Hochdörfer // 20.11.2013
It is a PHP project build system or
build tool based on Apache Ant.
$> pear channel-discover pear.phing.info $> pear install phing/phing
$> phing -v Phing 2.5.0
Phing is "just another"
dependency for your project.
{ "require": { "phing/phing": "2.5.0" } }
$> php composer.phar install Loading composer repositories with package information Installing dependencies - Installing phing/phing (2.5.0) Downloading: 100% Writing lock file Generating autoload files
/tmp/myproject |-vendor |---bin |-----@phing |---composer |---phing |-----phing |-------bin |---------phing |-------build |-------classes |-------docs |-------etc |-------test
$> ./vendor/bin/phing -v Phing 2.5.0
PHING_PATH=vendor/bin:bin function phing { TEST_PATHS=($PHING_PATH) for TEST_PATH in "${TEST_PATHS[@]}" do if [ -x "${TEST_PATH}/phing" ] then "${TEST_PATH}/phing" "$@" return $? fi done PHING_ON_PATH="$(type -P phing )" if [ -n "${PHING_ON_PATH}" ]; then "${PHING_ON_PATH}" "$@" return $? else echo "phing not found!" return 127 fi }
$> phing -v Phing 2.5.0
Root node of a build file
containing one or more targets.
A group of tasks that
run as an entity.
Custom piece of code to
perform a specific function.
Properties (variables) help
to customize execution.
A lot of built-in properties e.g. host.os,
line.separator, phing.version, php.version, ...
<?xml version="1.0"?> <project name="myproject" default="init"> <target name="init"> <!-- insert logic here --> </target> </project>
<?xml version="1.0"?> <project name="myproject" default="hello"> <target name="hello" description="Says Hello, world!"> <echo msg="Hello, world!" /> </target> </project>
<?xml version="1.0"?> <project name="myproject" default="hello"> <target name="init" description="Property initialization"> <property name="Hello" value="Hello, world!" /> </target> <target name="hello" depends="init"> <echo msg="${Hello}" /> </target> </project>
$> ./vendor/bin/phing -f build.xml hello /tmp/myproject/build.xml myproject > init: myproject > hello: [echo] Hello, world! BUILD FINISHED Total time: 0.0474 seconds
$> ./vendor/bin/phing -f build.xml init /tmp/myproject/build.xml myproject > init: BUILD FINISHED Total time: 0.0476 seconds
$> ./vendor/bin/phing -l Buildfile: /tmp/myproject/build.xml Default target: --------------------------------------------------------- hello Main targets: --------------------------------------------------------- init Property initialization Subtargets: --------------------------------------------------------- hello
Internal targets are just helpers
like private methods.
<?xml version="1.0"?> <project name="myproject" default="hello"> <target name="-init" description="Property initialization"> <property name="Hello" value="Hello, world!" /> </target> <target name="hello" depends="-init"> <echo msg="${Hello}" /> </target> </project>
$> ./vendor/bin/phing -f build.xml -init Unknown argument: -init phing [options] [target [target2 [target3] ...]] Options: -h -help print this message -l -list list available targets -v -version print the version information -q -quiet be extra quiet -verbose be extra verbose -debug print debugging information Report bugs to <dev@phing.tigris.org>
Are the targets really hidden?
$> ./vendor/bin/phing -l Buildfile: /tmp/myproject/build.xml Default target: --------------------------------------------------------- hello Main targets: --------------------------------------------------------- -init Property initialization Subtargets: --------------------------------------------------------- hello
<?xml version="1.0"?> <project name="myproject" default="hello"> <target name="-init" description="Property initialization" hidden="true"> <property name="Hello" value="Hello, world!" /> </target> <target name="hello" depends="-init"> <echo msg="${Hello}" /> </target> </project>
$> ./vendor/bin/phing -l Buildfile: /tmp/myproject/build.xml Default target: --------------------------------------------------------- hello Subtargets: --------------------------------------------------------- hello
Phing can do way more
than simple exec calls!
<?xml version="1.0"?> <project name="myproject" default="hello"> <target name="init"> <adhoc-task name="mytask"><![CDATA[ class MyTask extends Task { /** * (non-PHPdoc) * @see \Task::main() */ public function main() { // Custom code here... } } ]]></adhoc-task> </target>
<target name="hello" depends="init"> <mytask /> </target> </project>
<?php require_once 'phing/Task.php'; class MyTask extends Task { /** * (non-PHPdoc) * @see \Task::main() */ public function main() { // Custom code here... } }
<?xml version="1.0"?> <project name="myproject" default="hello"> <target name="init"> <taskdef name="mytask" classpath="${project.basedir}/src/" classname="MyApp.Common.Phing.MyTask" /> </target> <target name="hello" depends="init"> <mytask /> </target> </project>
<?php require_once 'phing/Task.php'; class MyTask extends Task { protected $file; /** * @param string $file */ public function setFile($file) { $this->file = $file; } /** * @see \Task::main() */ public function main() { // Custom code here... } }
<?xml version="1.0"?> <project name="myproject" default="hello"> <target name="init"> <taskdef name="mytask" classpath="${project.basedir}/src/" classname="MyApp.Common.Phing.MyTask" /> </target> <target name="hello" depends="init"> <mytask file="myfile.txt" /> </target> </project>
Turn your custom tasks into first-class citizens
of your code base: Ship them with your code!
Create a Composer package
of your "common tasks"!
Use external properties to
customize your build behaviour.
<?xml version="1.0"?> <project name="myproject" default="hello"> <target name="hello" description="Says whatever you want to say"> <property file="./build.properties" /> <echo msg="${Hello}" /> </target> </project>
Hello=Hello, world!
$> phing Buildfile: /tmp/myproject/build.xml myproject > hello: [property] Loading /tmp/myproject/build.properties [echo] Hello, world! BUILD FINISHED Total time: 0.0601 seconds
Externalize properties and
offer customization capabilities
<?xml version="1.0"?> <project name="myproject" default="hello"> <target name="hello" depends="init"> <echo msg="${Hello}" /> </target> <target name="init" depends="prop, local-prop"> <!-- some more init logic --> </target> <target name="prop"> <echo message="Loading default build.properties"/> <property file="build.properties" /> </target>
<target name="local-prop" if="local-prop.exists" depends="local-prop-check"> <echo message="Loading custom properties!"/> <property file="local.properties" override="true"/> </target> <target name="local-prop-check"> <available file="local.properties" property="local-prop.exists" /> </target> </project>
$> phing Buildfile: /tmp/myproject/build.xml myproject > prop: [echo] Loading default build.properties [property] Loading /tmp/myproject/build.properties myproject > local-prop-check: myproject > local-prop: myproject > init: myproject > hello: [echo] Hello, world! BUILD FINISHED Total time: 0.1383 seconds
$> phing Buildfile: /tmp/myproject/build.xml myproject > prop: [echo] Loading default build.properties [property] Loading /tmp/myproject/build.properties myproject > local-prop-check: myproject > local-prop: [echo] Loading custom properties! [property] Loading /tmp/myproject/local.properties myproject > init: myproject > hello: [echo] Hello my world! BUILD FINISHED Total time: 0.0493 seconds
phpunit.path=vendor/bin/phpunit phpunit.junit.log=build/logs/junit.xml phpunit.coverage.clover=build/logs/clover.xml phpunit.coverage.html=build/coverage phpcs.path=vendor/bin/phpcs phpcs.ignore=/Phing/ phpcs.log=build/logs/checkstyle.xml sencha.senchaCmd=/user/local/lib/sencha/sencha comass.path=/var/lib/gems/1.8/bin/compass
Use distinct naming conventions
for your properties.
Duplicating configuration code
<?php require_once 'phing/Task.php'; class ConfigMapperTask extends Task { /** * @see \Task::main() */ public function main() { // will import $APP_CONF in local context require_once('src/bootstrap.php'); $project = $this->project; $project->setProperty('db.host', $APP_CONF['db_host']); $project->setProperty('db.user', $APP_CONF['db_user']); $project->setProperty('db.password', $APP_CONF['db_passwd']); $project->setProperty('db.database', $APP_CONF['db_database']); } }
<?xml version="1.0"?> <project name="myproject" default="hello"> <taskdef name="readAppConfig" classpath="${phing.dir}/src/" classname="MyApp.Common.Phing.AppConfigTask" /> <target name="init" depends="prop, local-prop"> <readAppConfig /> </target> <target name="prop"> <echo message="Load default build.properties"/> <property file="build.properties" /> </target> <target name="local-prop" if="local-prop.exists" depends="local-prop-check"> <!-- […] --> </project>
<?xml version="1.0"?> <project name="myproject" default="app:run"> <!-- The following target namespaces exist: db:* - Database specific targets app:* - Application specific tasks ci:* - CI server specific tasks --> <import file="build/build.db.xml" /> <import file="build/build.app.xml" /> <import file="build/build.ci.xml" /> </project>
<?xml version="1.0"?> <project name="myproject" default="app:run"> <!-- The following target namespaces exist: lib1:* - Targets imported from lib1 lib2:* - Targets imported from lib2 app:* - Local application targets --> <import file="vendor/vendor1/lib1/build/build.xml" /> <import file="vendor/vendor2/lib2/build/build.xml" /> <import file="build/build.app.xml" /> </project>
Be aware that imports behave
like include in PHP!
<?xml version="1.0"?> <project name="myproject" default="lib1:run"> <!-- The following target namespaces exist: lib1:* - Targets imported from lib1 --> <import file="vendor/lib1/build/build.xml" /> </project>
<?xml version="1.0"?> <project name="lib1" default="lib1:run"> <target name="lib1:run"> <echo msg="Local dir: ${phing.dir.lib1}" /> <echo msg="Global dir: ${phing.dir}" /> </target> </project>
$> ./vendor/bin/phing Buildfile: /tmp/myproject/build.xml myproject > lib1:run: [echo] Local dir: /tmp/myproject/vendor/lib1/build [echo] Global dir: /tmp/myproject BUILD FINISHED Total time: 0.0411 seconds
Be aware to always(!) use the
projects name in lowercase format!
It`s ${phing.dir.myproject}
not ${phing.dir.MyProject}!
<?xml version="1.0"?> <project name="myproject" default="ci:run-tests"> <target name="app:clean-cache"> </target> <target name="app:create-cache"> </target> <target name="db:migrate"> </target> <target name="js:minifiy"> </target> <target name="ci:lint"> </target> <target name="ci:run-tests"> </target> </project>
<?xml version="1.0"?> <project name="myproject" default="app:create-cache"> <target name="app:clean-cache" description="Removes all cache files"> </target> <target name="app:create-cache" description="Build the cache files for the xml configuration"> </target> </project>
$> phing -l Buildfile: /tmp/myproject/build.xml Default target: ----------------------------------------------------- app:create-cache Build the cache files for the xml configuration Main targets: ------------------------------------------------------ app:clean-cache Removes all cache files app:create-cache Build the cache files for the xml configuration
<?xml version="1.0"?> <project name="myproject" default="run"> <target name="run"> <input propertyname="tag" defaultValue="mytag">Tag to create?</input> <liquibase-tag tag="${tag}" jar="/opt/liquibase/liquibase.jar" classpathref="/opt/liquibase/lib/mysql.jar" changelogFile="${project.basedir}/diff.xml" username="liquibase" password="liquibase" url="jdbc:mysql://localhost/myproject"/> </target> </project>
<?xml version="1.0"?> <project name="myproject" default="run"> <target name="run"> <!-- Returns canonicalized absolute pathname --> <php function="realpath" returnProperty="app.dir"> <param value="${app.dir}"/> </php> </target> </project>
<?xml version="1.0"?> <project name="myproject" default="run"> <target name="run"> <!-- Check for root user --> <if> <not> <equals arg1="${env.USER}" arg2="root" /> </not> <then> <fail message="Wrong user!" /> </then> </if> </target> </project>
<?xml version="1.0"?> <project name="myproject" default="ci:phpunit"> <!-- ... --> <target name="ci:phpunit" depends="-init, -ci:prepare"> <resolvepath propertyName="phpunit.path.abs" dir="${phing.dir}" file="${phpunit.path}"/> <exec executable="${phpunit.path.abs}" /> </target> </project>
Install the Jenkins Phing plugin
Install the EnvInject plugin!
Phing expects your build file to be called build.xml
and the builds properties file build.properties
Pick meaningful, human-readable
names for targets and properties.
Make build files self-contained.
Thank you!
Do not forget to rate the talk:
https://joind.in/10045