Building and Deploying PHP apps with Phing

4,541 views

Published on

Slides of the talk that I gave during PHP Johannesburg 2014

https://joind.in/talk/view/10411

Manually creating builds and running deployments can be scary, tedious, error-prone, boring, stressful (check all that apply). What you need is a tool that helps automate the necessary steps to build, test, package and deploy your app.

During this talk you will be introduced to the workings of Phing, it's rich set of out-of-the-box tasks and easy extensibility. Step by step, you will learn how to write a comprehensive deployment script. A number of demonstrations will cover testing, packaging, database migration, continuous integration, multi-server deployments and other real-world use cases.

Published in: Software, Technology

Building and Deploying PHP apps with Phing

  1. 1. Building and deploying PHP apps with >< >< Michiel Rook PHP Johannesburg April 2014
  2. 2. About me • Freelance PHP & Java contractor / consultant • PHP since '99 • Phing project lead • Dutch Web Alliance • http://www.linkedin.com/in/michieltcs • @michieltcs
  3. 3. This talk • Why a build tool • What is Phing • Usage • Various examples • Deployments • Extending Phing
  4. 4. Why a build tool?
  5. 5. This is PHP!?
  6. 6. Repetitive tasks • Version control • Database changes • Testing • Minifying • Packaging • Uploading • Deploying • Configuring
  7. 7. Good programmers are lazy
  8. 8. Good programmers automate repeatable things
  9. 9. Automate! • Easier handover • Improves quality • Reduces errors • Saves time • Consolidate scripts, reduce technical debt
  10. 10. What is Phing?
  11. 11. Phing is AWESOME
  12. 12. What is Phing? • “PHing Is Not GNU make; it's a PHP project build system or build tool based on Apache Ant.” • XML build files • Mostly cross-platform • Integrates various popular (PHP) tools • Lots. Of. Tasks.
  13. 13. "Phing is good glue"
  14. 14. The basics
  15. 15. Installing Phing • PEAR installation $ pear channel-discover pear.phing.info $ pear install [--alldeps] phing/phing • Optionally, install the documentation package $ pear install phing/phingdocs • Composer • Phar package
  16. 16. Build file • XML • Contains standard elements • Task: performs a specific function (copy, git commit, etc.) • Target: collection of tasks, can optionally depend on other targets • Project: root node, contains multiple targets
  17. 17. Example build file <project name="Example" default="world"> <echo>Hi!</echo> <target name="hello"> <echo>Hello</echo> </target> <target name="world" depends="hello"> <echo>World!</echo> </target> </project>
  18. 18. Example build file $ phing [-f build.xml] Buildfile: /home/michiel/phing/build.xml [echo] Hi! Example > hello: [echo] Hello Example > world: [echo] World! BUILD FINISHED
  19. 19. Properties • Simple key-value pairs • ${attribute} • Replaced by actual value
  20. 20. Properties <project name="Example" default="default"> <target name="default"> <property file="build.properties" /> <property name="foo" value="bar" /> <echo>${foo}</echo> </target> </project>
  21. 21. Properties $ phing Buildfile: /home/michiel/phing/build.xml Example > default: [echo] bar BUILD FINISHED
  22. 22. Fileset • Denotes a group of files • Include or exclude files based on patterns • References: define once, use many
  23. 23. Fileset <copy todir="build"> <fileset dir="./application"> <include name="**/*.php" /> <exclude name="**/*Test.php" /> </fileset> </copy> <fileset dir="./application" includes="**"/> <fileset dir="./application" includes="**" id="files"/> <fileset refid="files"/>
  24. 24. Fileset • Selectors allow fine-grained matching on certain attributes • contains, date, file name & size, ... <fileset dir="${dist}"> <and> <filename name="**"/> <date datetime="01/01/2011" when="before"/> </and> </fileset>
  25. 25. Conditions • Nested elements that evaluate to booleans • Used in "condition", "if" and "waitfor" tasks
  26. 26. Conditions <if> <equals arg1="${foo}" arg2="bar" /> <then> <echo message="The value of property foo is bar" /> </then> <else> <echo message="The value of property foo is not bar" /> </else> </if>
  27. 27. Conditions <if> <available file="composer.json" /> <then> <exec checkreturn="true" command="composer install" passthru="true" logoutput="true" dir="build" /> </then> </if>
  28. 28. Examples
  29. 29. Examples • Version control • Unit testing • Packaging • Deployment • Database migration • Continuous integration
  30. 30. Version control • Git • SVN • CVS
  31. 31. Version control <svnexport repositoryurl="svn://localhost/project/trunk/" todir="/home/michiel/dev"/> <svnlastrevision repositoryurl="svn://localhost/project/trunk/" propertyname="lastrev"/> <echo>Last revision: ${lastrev}</echo>
  32. 32. Version control <gitcommit repository="/home/michiel/dev/phing" message="Update documentation" allFiles="true"/> <gitpush repository="/home/michiel/dev/phing" refspec="master" tags="true" />
  33. 33. PHPUnit • Built-in support for most configuration options • Gathers code coverage information • Various output formats / reports • PHPUnit 4.x support soon!
  34. 34. PHPUnit • Stop the build when a test fails <phpunit haltonfailure="true" haltonerror="true" bootstrap="my_bootstrap.php" printsummary="true"> <batchtest> <fileset dir="src"> <include name="**/*Test.php"/> </fileset> </batchtest> </phpunit> Buildfile: /home/michiel/phpunit/build.xml Demo > test: [phpunit] Total tests run: 1, Failures: 1, Errors: 0, Incomplete: 0, Skipped: 0, Time elapsed: 0.00591 s Execution of target "test" failed for the following reason: /home/michiel/phpunit/build.xml:3:44: Test FAILURE (testSayHello in class HelloWorldTest): Failed asserting that two strings are equal.
  35. 35. PHPUnit example • Determine which files to include in the coverage report <coverage-setup database="reports/coverage.db"> <fileset dir="src"> <include name="**/*.php"/> <exclude name="**/*Test.php"/> </fileset> </coverage-setup> • Gather code coverage and other data during the test run <phpunit codecoverage="true"> <formatter type="xml" todir="reports"/> <batchtest> <fileset dir="src"> <include name="**/*Test.php"/> </fileset> </batchtest> </phpunit>
  36. 36. PHPUnit example • Generate some reports <phpunitreport infile="reports/testsuites.xml" format="frames" todir="reports/tests"/> <coverage-report outfile="reports/coverage.xml"> <report todir="reports/coverage" title="Demo"/> </coverage-report>
  37. 37. Documentation • Phing currently integrates with popular documentation tools • phpDocumentor (2) • ApiGen • Also supports r(e)ST (reStructuredText) <phpdoc2 title="Phing API Documentation" output="docs"> <fileset dir="../../classes"> <include name="**/*.php"/> </fileset> </phpdoc2>
  38. 38. phpDocumentor
  39. 39. Packaging • Create bundles or packages • tar • zip • phar • PEAR
  40. 40. Tar / zip <tar compression="gzip" destFile="package.tgz" basedir="build"/> <zip destfile="htmlfiles.zip"> <fileset dir="."> <include name="**/*.html"/> </fileset> </zip>
  41. 41. Phar packages <pharpackage compression="gzip" destfile="test.phar" stub="stub.php" basedir="."> <fileset dir="hello"> <include name="**/**" /> </fileset> <metadata> <element name="version" value="1.0" /> <element name="authors"> <element name="John Doe"> <element name="e-mail" value="john@example.com" /> </element> </element> </metadata> </pharpackage>
  42. 42. SSH <ssh username="john" password="smith" host="webserver" command="ls" /> <scp username="john" password="smith" host="webserver" todir="/www/htdocs/project/"> <fileset dir="test"> <include name="*.html"/> </fileset> </scp>
  43. 43. Jenkins
  44. 44. Jenkins
  45. 45. Jenkins
  46. 46. Putting it all together
  47. 47. Build & deploy script Objectives: • Perform syntax check • Run tests • Create package • Deploy via SSH • To selectable target / environment • Update database • Roll back
  48. 48. Syntax checks & tests <phplint haltonfailure="true"> <fileset dir="."> <include name="src/**" /> </fileset> </phplint> <phpunit haltonfailure="true"> <batchtest> <fileset dir="."> <include name="src/**/*Test.php" /> </fileset> </batchtest> </phpunit>
  49. 49. Packaging <tstamp> <format property="build.timestamp" pattern="%Y%m%d%H%M%S"/> </tstamp> <property name="build.release" value="${project.name}-${build.timestamp}" /> <property name="package.name" value="${build.release}.tar.gz" /> <tar destfile="artifacts/${package.name}" basedir="${build.dir.project}" />
  50. 50. Multiple targets • Several deployment targets: testing, staging, production, ... • One property file per target • Select based on input ssh.host=127.0.0.1 ssh.username=phing ssh.key.private=development-ssh ssh.key.public=development-ssh.pub deploy.location=/home/phing/apps
  51. 51. Multiple targets <input propertyname="deploy.target" validArgs="testing,staging,production"> Enter target name </input> <property file="${deploy.target}.properties"/>
  52. 52. Uploading <ssh host="${ssh.host}" username="${ssh.username}" privkeyfile="${ssh.key.private}" pubkeyfile="${ssh.key.public}" command="mkdir -p ${deploy.location.project}/${build.release}" failonerror="true" /> <echo>Copying package</echo> <scp host="${ssh.host}" port="${ssh.port}" username="${ssh.username}" privkeyfile="${ssh.key.private}" pubkeyfile="${ssh.key.public}" todir="${deploy.location.project}/${build.release}" file="${package.name.full}" />
  53. 53. Uploading <echo>Extracting package</echo> <ssh ... command="cd ${deploy.location.project}/${build.release}; tar xzf ${package.name}" failonerror="true" />
  54. 54. Symbolic links • All releases stored in separate directories • Symlink "current" to latest release • Allows for easy (code) rollbacks <echo>Creating symbolic link</echo> <ssh ... command="cd ${deploy.location.project}; if [ -h &quot;current&quot; ]; then rm -f previous; mv current previous; fi; ln -s ${build.release} current" />
  55. 55. Database migration • Set of delta SQL files (1-create-post.sql) • Tracks current version of your db in changelog table • Generates do and undo SQL files CREATE TABLE changelog ( change_number BIGINT NOT NULL, delta_set VARCHAR(10) NOT NULL, start_dt TIMESTAMP NOT NULL, complete_dt TIMESTAMP NULL, applied_by VARCHAR(100) NOT NULL, description VARCHAR(500) NOT NULL )
  56. 56. Database migration • Delta scripts with do (up) & undo (down) parts --// CREATE TABLE `post` ( `title` VARCHAR(255), `time_created` DATETIME, `content` MEDIUMTEXT ); --//@UNDO DROP TABLE `post`; --//
  57. 57. Database migration <dbdeploy url="sqlite:test.db" dir="deltas" outputfile="deploy.sql" undooutputfile="undo.sql"/> <pdosqlexec src="deploy.sql" url="sqlite:test.db"/> [dbdeploy] Getting applied changed numbers from DB: mysql:host=localhost;dbname=demo [dbdeploy] Current db revision: 0 [dbdeploy] Checkall: [pdosqlexec] Executing file: /home/michiel/dbdeploy/deploy.sql [pdosqlexec] 3 of 3 SQL statements executed successfully
  58. 58. Database migration -- Fragment begins: 1 -- INSERT INTO changelog (change_number, delta_set, start_dt, applied_by, description) VALUES (1, 'Main', NOW(), 'dbdeploy', '1-create_initial_schema.sql'); --// CREATE TABLE `post` ( `title` VARCHAR(255), `time_created` DATETIME, `content` MEDIUMTEXT ); UPDATE changelog SET complete_dt = NOW() WHERE change_number = 1 AND delta_set = 'Main'; -- Fragment ends: 1 --
  59. 59. Database migration -- Fragment begins: 1 -- DROP TABLE `post`; --// DELETE FROM changelog WHERE change_number = 1 AND delta_set = 'Main'; -- Fragment ends: 1 --
  60. 60. Restart services <ssh ... command="sudo service apache2 reload" failonerror="true" />
  61. 61. Rolling back <trycatch> <try> <ssh ... command="cd ${deploy.location.project}; rm -f current; mv -f previous current" failonerror="true" /> <echo>Rollback complete</echo> </try> <catch> <echo>No previous version deployed!</echo> </catch> </trycatch>
  62. 62. Extending Phing
  63. 63. Writing your own task • Extend from Task • Contains main() method and optionally init() • Setter method for each attribute in the build file
  64. 64. Our new task should • Accept filesets • Count number of lines in each file • Fail the build if a file with zero lines is found
  65. 65. Our new task <?php require_once 'phing/Task.php'; class CountLinesTask extends Task { public function main() { $foundEmpty = false; if ($foundEmpty) { throw new BuildException("One or more files have zero lines"); } } }
  66. 66. Injecting file sets private $_filesets = array(); /** * Creator for _filesets * * @return FileSet */ public function createFileset() { $num = array_push($this->_filesets, new FileSet()); return $this->_filesets[$num-1]; }
  67. 67. Injecting file sets foreach ($this->_filesets as $fileset) { $files = $fileset->getDirectoryScanner($this->project) ->getIncludedFiles(); $dir = $fileset->getDir($this->project)->getAbsolutePath(); foreach ($files as $file) { $path = realpath($dir . DIRECTORY_SEPARATOR . $file); $lines = count(file($path)); $this->log($path . ": " . $lines . " line(s)"); if ($lines == 0) { $foundEmpty = true; } } }
  68. 68. Using the task <project name="Count Lines" default="count"> <target name="count"> <taskdef name="countlines" classpath="/home/michiel/tasks" classname="CountLinesTask" /> <countlines> <fileset dir="."> <include name="**/*.txt" /> </fileset> </countlines> </target> </project>
  69. 69. Using the task Buildfile: /home/michiel/examples/count.xml Count Lines > count: [countlines] /home/michiel/examples/empty.txt: 0 line(s) [countlines] /home/michiel/examples/lines.txt: 3 line(s) Execution of target "count" failed for the following reason: /home/michiel/examples/count.xml:7:20: One or more files have zero lines BUILD FAILED /home/michiel/examples/count.xml:7:20: One or more files have zero lines Total time: 0.0454 seconds
  70. 70. Alternative: Ad Hoc <target name="main"> <adhoc-task name="foo"><![CDATA[ class FooTask extends Task { private $bar; public function setBar($bar) { $this->bar = $bar; } public function main() { $this->log("In main(): " . $this->bar); } } ]]></adhoc-task> <foo bar="TEST"/> </target>
  71. 71. Where to go from here • Tool versions • Performance • Documentation • PHP 5.3/5.4/5.5 • IDE support • CI integration
  72. 72. Questions? Example code at https://github.com/mrook/phing-examples Please leave feedback at https://joind.in/10411 Contact us on: http://www.phing.info #phing (freenode) @phingofficial Thank you!

×