Working with installed packages and channels: The Registry API

Introduction

Pyrus provides a very simple API for accessing its registry. Pyrus stores meta-information on installed packages in redundant registries. There are three kinds of registries that Pyrus recognizes, Sqlite3, Xml and the legacy Pear1 registry. A pyrus-based installation can have up to all three kinds of registries redundantly storing the installed packages and the known channels. By default the Sqlite3 registry is the primary registry used for querying information, with the Xml registry as a backup.

When Pyrus is used to manage an installation, it checks to see which registries are already present, if any, and will use the existing registries. This fact can be used to provide more flexible installation options. For instance, Pyrus can be used to manage an existing legacy PEAR installation without any special configuration, it will simply detect that the legacy registry is present and use it. If a package is extracted into a bundled location, Pyrus will detect its extracted package.xml as belonging to the Xml registry, and will use only that registry for installation purposes, which allows upgrading the extracted package and avoids placing any absolute paths into the installation.

Pyrus provides full atomic installation transactions for all of its registry types, including the legacy Pear1 registry, unlike the PEAR installer. In addition, each registry provides a single method which can be used to remove it from disk, and there is also a single method which can be used for converting from one registry type to another. Another method is available for repairing a corrupted registry from one of its redundant registries.

Pyrus provides a separate logical registry for storing channels from the registry that stores packages. Each registry handles this slightly differently. The Sqlite3 registry, for instance, stores all information in a single database. The Xml registry stores information in separate files, like the legacy Pear1 registry.

Basic Registry principles

All registry classes implement the PEAR2\Pyrus\IChannel interface, and all channelregistry classes implement the PEAR2\Pyrus\ChannelRegistry interface. The PEAR2\Pyrus\Registry class acts as an aggregator of underlying registries, and implements the ability to cascade to parent registries, as does the PEAR2\Pyrus\ChannelRegistry class.

The simplest way to retrieve a registry object is to use the one strongly tied to the PEAR2\Pyrus\Config object:

<?php
$reg 
PEAR2\Pyrus\Config::current()->registry;
$creg PEAR2\Pyrus\Config::current()->channelregistry;
?>

Accessing a specific installed package retrieves an object that is API-identical to a PackageFile object. The registry is implemented logically as an associative array. By requesting a package's logical name, which is channel/packagename, we get an object that can be manipulated just as if it were the package prior to installation

<?php
$package 
PEAR2\Pyrus\Config::current()->registry->package['pear2.php.net/PEAR2_Pyrus_Developer'];
$remotepackage = new PEAR2\Pyrus\Package('pear2.php.net/PEAR2_Pyrus_Developer');
// both packages can be queried with the same API
?>

The same principle applies to channels:

<?php
$channel 
PEAR2\Pyrus\Config::current()->channelregistry['pear2.php.net'];
$localchannel = new PEAR2\Pyrus\ChannelFile('channel.xml');
// both channels can be queried with the same API
?>

Iteration also works with both just as it would for an array:

<?php
foreach (PEAR2\Pyrus\Config::current()->registry->package as $name => $package) {
    
// $name is channel/package
    // $package is a packagefile object
}
foreach (
PEAR2\Pyrus\Config::current()->channelregistry as $name => $channel) {
    
// $name is the channel name
    // $channel is a channelfile object
}
?>

Installation-related API tasks

There are 4 installation-related methods, as well as 3 transaction methods. These methods are:

  • install() and replace()
  • uninstall()
  • exists()
  • begin(), commit() and rollback().

The install() method registers a package as installed, and sets its date/time to the current time so that the installation time can be tracked. The replace() method registers a package as installed, but does not modify its date/time. This is useful for repairing a corrupted entry, or simply storing a package as it is. Both methods accept a PEAR2\Pyrus\IPackageFile object. A packagefile object can be retrieved from a PEAR2\Pyrus\Package object by calling its getPackageFileObject() method. A PEAR2\Pyrus\Registry\Exception is thrown on any errors.

The uninstall() method accepts two parameters, the name of the package, and the package's channel. A PEAR2\Pyrus\Registry\Exception is thrown on any errors.

The exists() method also accepts two parameters, and returns TRUE or FALSE depending on whether the package exists. If severe errors occur such as registry corruption, a PEAR2\Pyrus\Registry\Exception object is thrown.

Note that array access can also be used to handle installation-related tasks:

<?php
$reg 
PEAR2\Pyrus\Config::current()->registry;

$package = new PEAR2\Pyrus\Package('/path/to/package.xml');

// equivalent to $reg->install($package)
$reg->package[] = $package;

// equivalent to $reg->uninstall('Foo', 'pear2.php.net')
unset($reg->package['pear2.php.net/Foo']);

// equivalent to $reg->exists('Foo', 'pear2.php.net');
isset($reg->package['pear2.php.net/Foo']);
?>

When performing any installation or uninstallation task, it is recommended to use the registry's built-in transaction support. The Sqlite3 registry uses the database's native transaction support. Both the Xml and Pear1 registries use Pyrus's PEAR2\Pyrus\AtomicFileTransaction for its transaction support. Thus, it is always best to do a transaction by first enabling the registry transaction, and then the atomic file transaction within this registry transaction:

<?php
$reg 
PEAR2\Pyrus\Config::current()->registry;
$package = new PEAR2\Pyrus\Package('Whatever');
try {
    
$reg->begin();
    
PEAR2\Pyrus\AtomicFileTransaction::begin();
    
$reg->install($package);
    
PEAR2\Pyrus\AtomicFileTransaction::commit();
    
$reg->commit();
} catch (
Exception $e) {
    
$reg->rollback();
    
PEAR2\Pyrus\AtomicFileTransaction::rollback();
    throw 
$e;
}
?>

If using the Installer API, the transactions and installation to registry is all automatic, this code is only needed for customizing installation.

Specialized querying of the registry

Other methods for querying the registry include:

  • info()
  • listPackages()
  • getDependentPackages()
  • detectFileConflicts()
  • detectRegistries()
  • removeRegistry()

info()

The info() method provides a way of peeking at a single attribute of a package. When used with the Sqlite3 registry, it is extremely efficient both in terms of memory use and speed. Both the Xml and Pear1 registries are far slower because they must load the complete packagefile into memory for every query. For these registries, it is better to simply retrieve a packagefile and query it using the PackageFile API.

Parameters to info() are the package name, package channel, and the field name to retrieve.

All of the Basic package.xml properties can be directly accessed using info(). In addition, two special properties, installedfiles and dirtree are available.

installedfiles returns a list of files and their properties as they have been installed. Here is a sample return value:

<?php
array(
      
'/full/path/todocs/PEAR2_SimpleChannelServer/pear2.php.net/examples/update_channel.php' =>
      array(
        
'role' => 'doc',
        
'name' => 'examples/update_channel.php',
        
'installed_as' => '/full/path/to/docs/PEAR2_SimpleChannelServer/pear2.php.net/examples/update_channel.php',
        
'relativepath' => 'PEAR2_SimpleChannelServer/pear2.php.net/examples/update_channel.php',
        
'configpath' => '/full/path/to/docs',
           ),
      
// ... and so on
     
);
?>

dirtree returns a list of every directory that would have been created if installing the package in a new installation. This can be used to prune empty directories after uninstalling. Here is a sample return value:

<?php
array (
      
'/full/path/to/php/PEAR2/SimpleChannelServer/REST',
      
'/full/path/to/php/PEAR2/SimpleChannelServer/Categories',
      
'/full/path/to/php/PEAR2/SimpleChannelServer',
      
'/full/path/to/php/PEAR2',
      
'/full/path/to/php',
      
'/full/path/to/docs/PEAR2_SimpleChannelServer/pear2.php.net/examples',
      
'/full/path/to/docs/PEAR2_SimpleChannelServer/pear2.php.net',
      
'/full/path/to/docs/PEAR2_SimpleChannelServer',
      
'/full/path/to/docs',
      
'/full/path/to/bin',
    );
?>

listPackages()

This method accepts a channel name as an argument, and returns an array of the names of installed packages from that channel.

getDependentPackages()

getDependentPackages() requires a single argument, a PEAR2\Pyrus\IPackageFile object. This method returns an array of PEAR2\Pyrus\Package objects representing installed packages that depend upon the package passed in. If the optional second boolean parameter is set to true (which it is by default), performance is improved when querying an Sqlite3 database by returning packages containing only the name of the package and its dependencies.

detectFileConflicts()

This method is used to implement file conflict detection to prevent overwriting installed files with those from another package. It accepts a single argument, a PEAR2\Pyrus\IPackageFile object. The Pear1 registry is the most efficient at this operation (at the expense of drastically decreased efficiency at installation or uninstallation), the Sqlite3 is the next most efficient, and the Xml registry is the least efficient, and in fact is so inefficient, this method should only be called on an Xml registry that is for a very small installation.

detectRegistries()

This static method accepts a string containing the path to check for registries, and returns an array containing the names of registries found. The possible return values include Sqlite3, Xml and Pear1. Note that only a call to PEAR2\Pyrus\Registry::detectRegistries() will return a list of all registries found. A call to PEAR2\Pyrus\Registry\Sqlite3::detectRegistries() will only return either array() or array('Sqlite3') depending on whether the registry exists.

removeRegistry()

This static method accepts a string containing the path to remove a registry from. A call to PEAR2\Pyrus\Registry::removeRegistry() will completely remove all traces of a PEAR installation. A call to an individual registry's removeRegistry, such as a call to PEAR2\Pyrus\Registry\Pear1::removeRegistry() will only remove that registry from the installation path.

Channel registry