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