How many steps are required to deploy your software? Some people say it shouldn’t be more than one. I’m little bit more relaxed about it so I would say two steps are still fine. If it takes more than two then most likely you need a build script.
Why do you need a build script? Because your time is precious. You don’t want to waste it on repetitive boring tasks. You don’t want to waste other people’s time either. If you forget to tell your collegues that they have to do something after code update (like release a database patch) they might get confused. It’s also not safe. More things to do during a deployment means higher chance something will go wrong. Human is the weakest link in any system.
Every deployment process is unique to an application but there are few common areas:
- Fetching latest code
- Configuration
- File system operations
- Database update
There might be more areas but those are the most popular. Each of them can require multiple actions. All of that and much more can be handled though a build tool.
Phing is a PHP project build system or build tool based on Apache Ant. You can do anything with it that you could do with a traditional build system like GNU make. It use simple XML build files and extensible PHP “task” classes.
There are many ways to install Phing. My recommendation is to go for the composer. After all we want to limit number of deployment steps.
Create or edit composer configuration file.
$ vim composer.json
Add phing to the “require” section.
{ "require": { "phing/phing": "2.5.0" } }
Install Phing.
$ php composer.phar install
After Phing is installed you can access it directly from vendors directory.
$ vendor/phing/phing/bin/phing
If you don’t like the long path you can always create a symlink in “/usr/local/sbin” or any other preferred location.
The build script we are going to create will do three things: generate configuration, create logs file and release database changes. Each of those targets will have own build file. You can obviously keep everything in one script but it’s XML. Even short scripts can easily get unreadable.
First lets create the main build.xml file. It will bound all XML files together.
$ vim build.xml
The script defines two targets: main and database-init. The default target defined in the “project” tag is “main”. If you run Phing without any parameters this is the target which is going to be executed.
The default target doesn’t do anything on it’s own. It calls three other targets: logs, configuration and database from external files. We will create those files in a second.
Build.xml does one very important thing. It loads properties.
$ mkdir -p build/properties/ $ vim build/properties/default.properties
db.host = 127.0.0.1 db.user = root db.password = root db.database = foobar facebook.appId = 1234567890-10 facebook.secret = 1234567890qwertyuiopasdfghjkl
A property file has similar syntax to an ini file. Defined values will be used to build the application. Every environment (for example: production, staging, workdev, homedev, etc) will most likely have own property file. Duplicating everything for a new environment is not the best approach. Imagine you create a new setting which will be the same for all environments. You will have to paste it to every property file. To avoid this scenario my proposition is to have a default file which is always loaded on startup. Settings unique to an environment should overwrite the default one.
$ vim build/properties/production.properties
db.user = "usr01" db.password = "s3cr3t"
Now if you deploy to production set “build.env” property accordingly.
$ phing -Dbuild.env=production
This is simple but powerful strategy.
Lets create now missing XML files.
$ vim build/build-logs.xml
$ vim build/build-configuration.xml
$ vim build/build-database.xml
The easiest target is “logs“. It creates logs directory with application.log file. It also changes ownership of that file to the Apache user “www-data“. Chown requires root permissions. If you need this line you will have to run phing as root.
Next target is inside build-configuration.xml. It fills configuration template with settings from *.properties files and save to config directory.
$ vim build/templates/application.ini.build
db.host = "${db.host}" db.user = "${db.user}" db.password = "${db.password}" db.database = "${db.database}" log.level = "3" log.file = "logs/application.log" facebook.appId = "${facebook.appId}" facebook.secret = "${facebook.secret}"
The last build file releases database changes. Dbdeploy task reads deltas from “database/delta“. Each delta should begin with a number, for example: 1-create-new-table.sql, 2-update-something.sql and so on. Those numbers are compared with local changelog stored in the target database (we will create this table in a second). Dbdeploy creates one patch file which consists of all missing deltas. The file will be saved as “database/deploy-db-${DSTAMP}${TSTAMP}.sql“.
Once the file is created we use good old fashion mysql client to insert it into a database. Phing doesn’t have build-in task to call MySql but you can run any command with “exec” task.
In order to make the above code work you need the “changelog” table. You can create it manually but since we are in the build script business leave that with Phing.
$ vim build/templates/database.sql
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 );
$ phing database-init
That should create the table in your database. In case of any problems you can troubleshoot it with “-verbose” parameter.
$ phing database-init -verbose
You should get an error because the table already exists.
We almost done. The last step is to create example deltas.
$ vim database/delta/1-settings.sql
CREATE TABLE IF NOT EXISTS `settings` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `value` varchar(256) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; --//@UNDO DROP TABLE `settings`; --//
$ vim database/delta/2-settings-data.sql
INSERT INTO `settings` (`id`, `name`, `value`) VALUES (NULL, 'language', 'EN'), (NULL, 'currency', 'EUR'); --//@UNDO DELETE FROM `settings` WHERE `name` IN ('language', 'currency'); --//
You have probably noticed that both SQL files have @UNDO section. It’s not mandatory but some versions of Phing might not work without it. Code from @UNDO will go to “database/undo-db-${DSTAMP}${TSTAMP}.sql” file. It should allow to roll back database release in case of an error.
There is much more you can do with Phing. It has many tasks and extensions. Go though the manual to find out about all its features. XML is not the best language for programming but it does the job. So long you remember to keep it tight you shouldn’t have any problems.
Nice article. Just 1 comment on you database delta naming. You should use a timestamp instead of an incremental number, so when working in teams, your new deltas won’t conflict.
LikeLike
Very good point! We keep having conflicts from time to time but I didn’t give any thought to it. That makes perfect sense, thank you for that Petah.
LikeLike
Thank you! I was looking forward to find a post like this!
Is there anyway to get the SQL undo file automatically built by phing. If I remember correctly Weploy can do it
LikeLike
You can tell Composer where to put binaries when fetching dependencies:
“config”: {
“vendor-dir”: “src/vendor”,
“bin-dir”: “bin”
}
That way you will run phing via `bin/phing` from the project directory.
LikeLike