PHP Deployment: PEAR

This is an article that is based off of a talk I did covering various deployment mechanisms.  The slides can be found at Slideshare

Before I get into this one I would like to note that while I have presented several options (and will presenting one more after this) that none of these are given with the assumption that they are the only way or even the best way to do things.  Each of these options is provided as a starting point.  What that means is that you need to try it yourself and modify what I present here to fit what you need.

With that; PEAR.  PEAR stands for PHP Extension and Application Repository.  What it does is allow you to maintain very PHP-friendly packages in a manner that is native for PHP developers.  What that means is that you are able to package up your application in a way that is easy for you to understand.  But in order to deploy your application we need to discuss a few concepts.

Concepts

First PEAR2.  There is a PEAR 2.  It looks a little light right now in terms of packages, documentation and links that work on the site. So for that reason I will stick with PEAR 1 as an example.

Next, channels.  In order to make your application available to your production servers you need a channel.  A channel is just an HTTP server that has some data in pre-defined areas so the PEAR binary knows where to look for it.  There are several options that you can have to setting up a PEAR channel. Chiara and Pirum are the two that I have most heard about.  I chose Pirum because it is really easy to get up and running.

The first thing we will do is get our channel up and running.  To do that we need to follow the instructions on the Pirum page, which I won't bother going over (since they're already there).  But I will show how to set up the channel.

Creating the Channel

First create the directory on your deployment server where you want the repository to be located.  Then you need to place a file called pirum.xml in that directory, which has a simple XML format that is used to initialize the channel.

<server>
     <name>testing/pear</name>
     <summary>OurOrg PEAR channel</summary>
     <alias>helloworld</alias>
     <url>http://testing/pear/</url>
</server>

Then you need to run the "pirum build" command.

[[email protected] www]# pirum build /var/www/pear
Pirum 0.9.9 by Fabien Potencier
Available commands:
  pirum build target_dir
  pirum add target_dir Pirum-1.0.0.tgz

Running the build command:
   INFO   Building channel
   INFO   Building maintainers
   INFO   Building categories
   INFO   Building packages
   INFO   Building releases
   INFO   Building index
   INFO   Building feed
   INFO   Updating PEAR server files
   INFO   Command build run successfully

If we look at the directory structure we see several new files added.

[[email protected] www]# find pear
pear
pear/feed.xml
pear/channel.xml
pear/pirum.css
pear/get
pear/rest
pear/rest/r
pear/rest/c
pear/rest/c/Default
pear/rest/c/Default/packages.xml
pear/rest/c/Default/packagesinfo.xml
pear/rest/c/Default/info.xml
pear/rest/c/categories.xml
pear/rest/p
pear/rest/p/packages.xml
pear/rest/m
pear/rest/m/allmaintainers.xml
pear/pirum.xml
pear/index.html

These are the files needed to have a basic PEAR repository set up.

But a repository is useless without something to repos… er.. distribute.  For that what we will do is simply export our Subversion repository to a directory somewhere where we can start building our package.

[[email protected] www]# svn export svn://testing/repos/HelloWorld/tags/0.0.3 application
A    application
A    application/tests
A    application/tests/application
A    application/tests/application/bootstrap.php
/snip/
A    application/public
A    application/public/.htaccess
A    application/public/index.php
Exported revision 23.

Building the Package

The next thing we need to do is build the package.  But how does one do that with PEAR?  With a package.xml file.  That's how.  The problem is that a package.xml file can actually be a little lengthy.  Another problem is that it needs to have very exact details about each and every file that you want to deply.  The way I worked around that is to create a base package.xml file that I use as a template and then use a simple script to find all the files and add them in there.  First the package.xml.

<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.9.0" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0     http://pear.php.net/dtd/tasks-1.0.xsd     http://pear.php.net/dtd/package-2.0     http://pear.php.net/dtd/package-2.0.xsd">
 <name>HelloWorld</name>
 <channel>xen2/pear</channel>
 <summary>HelloWorld</summary>
 <description>This is a description</description>
 <lead>
  <name>Kevin</name>
  <user>kevin</user>
  <email>[email protected]</email>
  <active>yes</active>
 </lead>
 <date>2010-06-15</date>
 <time>14:27:49</time>
 <version>
  <release>0.0.3</release>
  <api>0.0.3</api>
 </version>
 <stability>
  <release>stable</release>
  <api>stable</api>
 </stability>
 <license uri="http://www.apache.org/licenses/">Apache</license>
 <notes>
No notes
 </notes>
 <contents>
        <dir baseinstalldir="/" name="/">
                <file baseinstalldir="/application"  role="www" name="/ignore.html" />
        </dir>
 </contents>

 <dependencies>
  <required>
   <php>
    <min>5</min>
   </php>
   <pearinstaller>
    <min>1.4.0</min>
   </pearinstaller>
  </required>
 </dependencies>
 <phprelease />
 <changelog>
  <release>
   <version>
    <release>0.0.3</release>
    <api>0.0.3</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2010-06-15</date>
   <license uri="http://www.apache.org/licenses/">Apache</license>
   <notes>
No notes
   </notes>
  </release>
 </changelog>
</package>

Like I said.  Long.  However, I've highlighted the interesting part.  That is where we are going to place our file listing.  How, you ask?  With a simple script.


$deployDirs = array(
        'application',
        'library',
        'public'
);

$simpleXml = simplexml_load_file('package.xml');
$dirEntry = $simpleXml->contents->dir;
unset($dirEntry->file); // Get rid of all the files
foreach ($deployDirs as $dir) {
        foreach (glob("$dir") as $dirNode) {
                addDirectory($dirEntry, $dirNode);
        }
}
file_put_contents('package.xml', $simpleXml->asXML());

function addDirectory(SimpleXMLElement $dirEntry, $dirNode)
{
        echo "Reading directory $dirNoden";
        foreach (glob("$dirNode/*") as $fileName) {

                if (is_dir($fileName)) {
                        addDirectory($dirEntry, $fileName);
                } else {

                        $file = $dirEntry->addChild('file');
                        $file['baseinstalldir'] = '/application';
                        $file['name'] = $fileName;
                        $file['role'] = 'www';
                }
        }
}

What this script does is state which directories to search, loads up the package.xml file into a simpleXML object and iterates over every file and adds it to the content node.  If we run this code we get the following output.

[[email protected] application]# php build-pear.php
Reading directory application
Reading directory application/configs
Reading directory application/controllers
Reading directory application/models
Reading directory application/views
Reading directory application/views/helpers
Reading directory application/views/scripts
Reading directory application/views/scripts/error
Reading directory application/views/scripts/index
Reading directory library
Reading directory public

Deploying to the Repository

If we look at our package.xml file now we will now see a list of all of our files.  Now what we need to do is have pear package it.  How do we do that?  With "pear package".  However, before we actually build the package we need to tell pear that our repository exists.  "pear package" will refuse to create a package for a repository that it does not know about.  So we need to tell PEAR about our repository.  We do that by issuing the following command.

[[email protected] application]# pear channel-discover xen2/pear
Adding Channel "ourour" succeeded
Discovery of channel "xen2/pear" succeeded

What?  What's this "xen2/pear" thing?  Think of it like http://xen2/pear.  Or, with a fully qualified domain name xen2.eschrade.com/pear.  It's basically the URL that is used to locate the repository.  Now that we have our channel discovered we can package our package.

[[email protected] application]# pear package

Package HelloWorld-0.0.3.tgz done

Now we need to tell Pirum to install it in the repository

[[email protected] application]# pirum add /var/www/pear/ HelloWorld-0.0.3.tgz
Pirum 0.9.9 by Fabien Potencier
Available commands:
  pirum build target_dir
  pirum add target_dir Pirum-1.0.0.tgz

Running the add command:
   INFO   Parsing package 0.0.3 for HelloWorld
   INFO   Building channel
   INFO   Building maintainers
   INFO   Building categories
   INFO   Building packages
   INFO   Building package HelloWorld
   INFO   Building releases
   INFO   Building releases for HelloWorld
   INFO   Building release 0.0.3 for HelloWorld
   INFO   Building index
   INFO   Building feed
   INFO   Updating PEAR server files
   INFO   Command add run successfully

Installation

We now have our PEAR channel set up to deploy our application.  Now all that's left is to set up production server.

The first thing to do is discover the channel, just like we did before.  However, we need to do something before we actually do the deployment.  If you look back at our build-pear.php code there was a line where I stated the role of the files.  While you can have multiple roles I defined all of mine as being www.  That's important because PEAR likes to deploy files to the PEAR root, which is probably not where you want them to go.  To define that, change the following setting for PEAR.

[[email protected] www]# pear config-set www_dir /var/www
config-set succeeded

Now that we've done that we can deploy our application quite easily.

[[email protected] www]# pear install helloworld/HelloWorld
downloading HelloWorld-0.0.3.tgz ...
Starting to download HelloWorld-0.0.3.tgz (2,971 bytes)
....done: 2,971 bytes
install ok: channel://xen2/pear/HelloWorld-0.0.3

With that, we're installed.

Rolling Back

With PEAR you don't have the option of rolling back your installation.  What you need to do is uninstall your current installation and then install an older one.

[[email protected] www]# pear uninstall helloworld/HelloWorld
uninstall ok: channel://xen2/pear/HelloWorld-0.0.3
[[email protected] www]# pear install helloworld/HelloWorld-0.0.2
downloading HelloWorld-0.0.2.tgz ...
Starting to download HelloWorld-0.0.2.tgz (2,971 bytes)
....done: 2,971 bytes
install ok: channel://xen2/pear/HelloWorld-0.0.2

Conclusion

PEAR is a pretty good option for you to deploy your PHP applications with.  It takes a fair amount of work up front (perhaps PEAR2 will make this easier) but all of that is scriptable.  And since it's written in PHP you, as a developer, you have a lot of control.  The problem is that you, as a developer, should not have access to the production environment.  That is almost always the domain of system administrators. Why?  You might be tempted to fix something.  But PEAR is really good when you need cross-platform deployment and a lot of developer control over the deployment.

Related posts

Leave a Comment