SubversionSubversion (called “SVN”) is a version control system that seeks to be the replacement for Concurrent Versioning System (CVS). If you’re a CVS user, there are many reasons to migrate to Subversion, including features like atomic commits, database (Berkeley DB) back-end, and the ability to easily rename files. Best of all, Subversion has a very similar interface to CVS so it’s usually a comfortable transition.
Overview of VersionControl_SVNThe
VersionControl_SVN package provides the ability to interface with Subversion directly from PHP. The package requires Subversion client (svn) binaries to be installed on your system, and allows you to configure the location of
svn.
Installing the PackagesUse the PEAR package manager to install the
VersionControl_SVN package:
pear install VersionControl_SVN-alphaThe results will look like this, as long as the package has been installed successfully:
downloading VersionControl_SVN-0.3.0alpha1.tgz ...
Starting to download VersionControl_SVN-0.3.0alpha1.tgz (33,829 bytes)
.........done: 33,829 bytes
install ok: channel://pear.php.net/VersionControl_SVN-0.3.0alpha1It’s important to use
-alpha after the name of the package if your PEAR configuration for
preferred_state is
stable, which is most likely the case. To verify the setting, type:
pear config-showYou will see the configuration value for
preferred_state:
Preferred Package State preferred_state stable Additionally, I use the
Date package to format the date returned by Subversion’s
log command into something a little more readable. To install the
Date package, type:
pear install DateThe Date package is in stable status.
Making the RSS_Base classThe
RSS_Base class has a couple properties and variables that are common to both the
RSS_Channel and
RSS_Item classes featured in the first article. Both classes have three common properties – the title, description, and link. The
RSS_Base now contains these variables and methods to set and get the link, and code for setting the title and description is in the constructor of
RSS_Channel and
RSS_Item. The
RSS_Channel and
RSS_Itemclasses have been refactored to extend the base class. The full class is included in Listing 1.
The
RSS_Channel class declaration now looks like this:
class RSS_Channel extends RSS_Base
{
...
}Listing 1
class RSS_Base
{
/**
*@var string The title of the RSS object.
*@access private
*/
var $_title = ‘’;
/**
*@var string Description of the RSS object.
*@access private
*/
var $_description = ‘’;
/**
*@var string The URL of the RSS object.
*@access private
*/
var $_link = ‘’;
/**
*Returns the link of the RSS object.
*@return string URL of the RSS object
*@access public
*/
function getLink() {
return $this->_link;
}
/**
*Sets the link of the RSS feed object.
*@param string $url The URL of the object.
*@return void
*@access public
*/
function setLink($url) {
$this->_link =$url;
}
}
Cleaning up RSS_Channel and RSS_ItemSince the
RSS_Channel class extends
RSS_Base, the constructor works exactly as it did before except it sets the variables inside the base class (
RSS_Base) instead of itself (
RSS_Channel). So no modifications other than the class declaration and removing the duplicate var declarations and functions are necessary. The constructor in the
RSS_Channel class looks like this:
function RSS_Channel($title, $link, $description, $item)
{
$this->_title = $title;
$this->_link = $link;
$this->_description = $description;
if ( != null)
{
$this->addItem($item);
}
}The same modifications are made to the
RSS_Item class, and after the changes have been made both classes, which used to duplicate code, now are cleaner and leaner than before.
Getting the SVN LogThe
VersionControl_SVN class has a
factory() method that creates a Subversion command object that runs the appropriate command. The command that I’m using in this article is log, which gets a log of the comments that are input whenever a commit is made to the repository. This log will be the subject of the RSS feed written by the newly refactored classes. The following two lines show how to create the object using the
factory() method, with an array of options containing a single element:
$options = array(‘fetchmode’ => VERSIONCONTROL_SVN_FETCHMODE_ASSOC);
$svn = VersionControl_SVN::factory(‘Log’, $options);The
fetchmode option specifies what the output of the command (Log) is going to look like. The
VERSIONCONTROL_SVN_FETCHMODE_ASSOC constant specifies an associative array, which will make the output easy to get to when assigning parts of it to the RSS classes. Other options and their descriptions are included in Table 1.
Aside from the fetchmode option, another option that can be passed is the
svn_path option, which specifies the full path and filename of the svn command (the default value is
/usr/local/bin/ svn). The name of the command is passed to
factory(), which can be one of the following values:
- Add
- Blame
- Cat
- Checkout
- Cleanup
- Commit
- Copy
- Delete
- Diff
- Export
- Import
- Info
- List
- Log
- Merge
- Mkdir
- Move
- Parsers
- Propdel
- Propget
- Proplist
- Propset
- Resolved
- Revert
- Status
- Switch
- Update
These names match up with the command names that you would see if you type
svn help at the command line. The explanation of each command can be found by Subversion’s
help command, such as
svn help add.
Table 1: VersionControl_SVN Class Options
The command used by the class that writes the RSS feed uses the
log command. The
log command prints the comments, dates, revision number, and username for each commit that was made to the project’s repository. The RSS feed will expose these properties, offering the information to anyone with access to the feed and with some way to read it.
After setting up the command using the
factory() method, two other arrays are set up that include switches and arguments to the command:
$cmdSwitches = array(
‘username’ => ‘user1’,
‘password’ => ‘secret’,
‘verbose’ => ‘true’);
$args = array(“svn://server1.example.local/var/lib/svn/$project”);The
$cmdSwitches array contains the
username and
password switches that allow you to set the name and password for the account to use when accessing Subversion. Also, verbose is set to ‘true’ so the output of the command contains as much information as possible.
Lastly, the
$args array contains a single argument, which is the name of the SVN repository. It is important to note that the name of the repository and path can be specified in other ways, such as in the option array passed to the factory.
Getting the OutputNow that the SVN command object is all set up, the next step is to run the command and grab the output. The command is actually executed and the output parsed given the
fetchmode, when the
run() method is called on the
$svn object built by the factory. The
$args and
$cmdSwitches arrays, which were set up previously, are passed to
run() as shown here:
// Get the log entries for the project.
if ($output = $svn->run($args,$cmdSwitches)) {
...
}If
$output is assigned to a value, the code inside the condition will be executed. This is where the code for building the RSS objects belongs. The RSS entries will be very simple, including only the required properties of title and description for each of the entries. I’ve decided that in my version of the RSS feed, the title will be a formatted string that includes the name of the date of the entry with the name of the author following in parenthesis. The description will be the version number, followed by the text entered as a comment when the commit was made to the repository. The code to build the RSS entries looks like that shown in Listing 2.
A
RSS_Channel object is built and assigned to
$rssChannel. Then, for each entry found in the output, a new
RSS_Item object is created with title and description provided in the constructor. Then the item is added to the channel. Finally, an
RSS_Feed object is created and the XML feed is built using the
xmlSerialize() method. The resulting feed looks something like this:
<rss version=”2.0”>
<channel>
<title>phpnet_rss</title>
<description>Project Subversion Log</description>
<link>http://www.example.com/projectDetails</link>
<item>
<title>07/06/2006 03:11:11 AM (Author: user1)</title>
<description>43 - Added new example for SVN and RSS classes.</description>
<source url=””/>
<category domain=””/>
</item>
...
</rss>
Handling ErrorsIf no output is assigned, that could mean errors have occurred while trying to run the command. It’s not useful to try to create the RSS feed if errors occurred, but it’s a good idea to print the errors in a user-friendly manner so something can be done with them. This code merely demonstrates printing them to the output of the script:
} else {
print “No output found!\n”;
if (count($errs = $svnErrors->getErrors())) {
foreach ($errs as$err) {
printf(“Error: %s%n”, $err[‘message’]);
printf(“Command used: %s%n”, $err[‘params’][‘cmd’]);
}
}
}Putting it all togetherThe entire listing of the script for writing the RSS feed is shown in Listing 3.
With relatively few modifications, other Subversion commands can be exposed using RSS feeds. One such command is the ‘list’ command, which can list all the files and directories inside a project’s repository. A benefit of the object-oriented design provided by the
VersionControl_SVN class is that ‘List’ needs only to be passed to the
factory() method.
Listing 2
$rssChannel = new RSS_Channel($project,
‘http://www.example.com/projectDetails’,
‘Project Subversion Log’,
null);
foreach ($output as $logEntry) {
$date = new Date($logEntry[‘DATE’]);
/* Build the new RSS item */
$rssItem = new RSS_Item(sprintf(“%s (Author: %s)”, $date-
>format(‘%D %r’), $logEntry[‘AUTHOR’]),
sprintf(“%s - %s”, $logEntry[‘REVISION’], $logEntry[‘MSG’]));
$rssChannel->addItem($rssItem);
}
$rss = new RSS_Feed($rssChannel);
print $rss->xmlSerialize() . “\n”;
Listing 3
<?php
/* Shows how to build an RSS feed from the SVN project log. */
require_once ‘Date.php’;
require_once ‘RSS_Feed.php’;
require_once ‘RSS_Channel.php’;
require_once ‘RSS_Item.php’;
require_once ‘VersionControl/SVN.php’;
$projectName = ‘phpnet_rss’;
$svnErrors = &PEAR_ErrorStack::singleton(‘VersionControl_SVN’);
$options = array(‘fetchmode’ => VERSIONCONTROL_SVN_
FETCHMODE_ASSOC);
$svn = VersionControl_SVN::factory(‘Log’, $options);
$cmdSwitches = array(
‘username’ => ‘user1’,
‘password’ => ‘secret’,
‘verbose’ => ‘true’);
$args = array(“svn://server1.example.local/var/lib/svn/$projectName”);
// Get the log entries for the project.
if ($output = $svn->run($args, $cmdSwitches)) {
$rssChannel = new RSS_Channel($projectName,
‘http://www.example.com/projectDetails’,
‘Project Subversion Log’,
null);
foreach ($output as $logEntry) {
$date = new Date($logEntry[‘DATE’]);
/* Build the new RSS item */
$rssItem = new RSS_Item(sprintf(“%s (Author: %s)”,$date-
>format(‘%D %r’), $logEntry[‘AUTHOR’]),
sprintf(“%s - %s”, $logEntry[‘REVISION’], $logEntry[‘MSG’]));
$rssChannel->addItem($rssItem);
}
$rss = new RSS_Feed($rssChannel);
print $rss->xmlSerialize() . “\n”;
} else {
print “No output found!\n”;
if (count($errs = $svnErrors->getErrors())) {
foreach ($errs as $err) {
printf(“Error: %s%n”, $err[‘message’]);
printf(“Command used: %s%n”, $err[‘params’][‘cmd’]);
}
}
}
?>
SummaryUsing PHP 5’s ability to create object-oriented code, it can be fairly trivial to create re-usable objects that hide the implementation details of doing relatively mundane tasks such as serializing or printing RSS feeds. Combining that ability with PEAR packages
can allow developers to quickly write useful scripts. As I’ve done more and more PHP development, I’ve written less of my own code—looking first now to PEAR packages and to refactoring my own code to be cleaner and smaller.