Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
<?php/*** PEAR_DependencyDB, advanced installed packages dependency database** PHP versions 4 and 5** LICENSE: This source file is subject to version 3.0 of the PHP license* that is available through the world-wide-web at the following URI:* http://www.php.net/license/3_0.txt. If you did not receive a copy of* the PHP License and are unable to obtain it through the web, please* send a note to license@php.net so we can mail you a copy immediately.** @category pear* @package PEAR* @author Tomas V. V. Cox <cox@idecnet.com>* @author Greg Beaver <cellog@php.net>* @copyright 1997-2006 The PHP Group* @license http://www.php.net/license/3_0.txt PHP License 3.0* @version CVS: $Id: DependencyDB.php,v 1.35 2007/01/06 04:03:32 cellog Exp $* @link http://pear.php.net/package/PEAR* @since File available since Release 1.4.0a1*//*** Needed for error handling*/require_once 'PEAR.php';require_once 'PEAR/Config.php';$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array();/*** Track dependency relationships between installed packages* @category pear* @package PEAR* @author Greg Beaver <cellog@php.net>* @author Tomas V.V.Cox <cox@idec.net.com>* @copyright 1997-2006 The PHP Group* @license http://www.php.net/license/3_0.txt PHP License 3.0* @version Release: 1.5.1* @link http://pear.php.net/package/PEAR* @since Class available since Release 1.4.0a1*/class PEAR_DependencyDB{// {{{ properties/*** This is initialized by {@link setConfig()}* @var PEAR_Config* @access private*/var $_config;/*** This is initialized by {@link setConfig()}* @var PEAR_Registry* @access private*/var $_registry;/*** Filename of the dependency DB (usually .depdb)* @var string* @access private*/var $_depdb = false;/*** File name of the lockfile (usually .depdblock)* @var string* @access private*/var $_lockfile = false;/*** Open file resource for locking the lockfile* @var resource|false* @access private*/var $_lockFp = false;/*** API version of this class, used to validate a file on-disk* @var string* @access private*/var $_version = '1.0';/*** Cached dependency database file* @var array|null* @access private*/var $_cache;// }}}// {{{ & singleton()/*** Get a raw dependency database. Calls setConfig() and assertDepsDB()* @param PEAR_Config* @param string|false full path to the dependency database, or false to use default* @return PEAR_DependencyDB|PEAR_Error* @static*/function &singleton(&$config, $depdb = false){if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$config->get('php_dir', null, 'pear.php.net')])) {$a = new PEAR_DependencyDB;$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$config->get('php_dir', null, 'pear.php.net')] = &$a;$a->setConfig($config, $depdb);if (PEAR::isError($e = $a->assertDepsDB())) {return $e;}}return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$config->get('php_dir', null, 'pear.php.net')];}/*** Set up the registry/location of dependency DB* @param PEAR_Config|false* @param string|false full path to the dependency database, or false to use default*/function setConfig(&$config, $depdb = false){if (!$config) {$this->_config = &PEAR_Config::singleton();} else {$this->_config = &$config;}$this->_registry = &$this->_config->getRegistry();if (!$depdb) {$this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') .DIRECTORY_SEPARATOR . '.depdb';} else {$this->_depdb = $depdb;}$this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock';}// }}}function hasWriteAccess(){if (!file_exists($this->_depdb)) {$dir = $this->_depdb;while ($dir && $dir != '.') {$dir = dirname($dir); // cd ..if ($dir != '.' && file_exists($dir)) {if (is_writeable($dir)) {return true;} else {return false;}}}return false;}return is_writeable($this->_depdb);}// {{{ assertDepsDB()/*** Create the dependency database, if it doesn't exist. Error if the database is* newer than the code reading it.* @return void|PEAR_Error*/function assertDepsDB(){if (!is_file($this->_depdb)) {$this->rebuildDB();} else {$depdb = $this->_getDepDB();// Datatype format has been changed, rebuild the Deps DBif ($depdb['_version'] < $this->_version) {$this->rebuildDB();}if ($depdb['_version']{0} > $this->_version{0}) {return PEAR::raiseError('Dependency database is version ' .$depdb['_version'] . ', and we are version ' .$this->_version . ', cannot continue');}}}/*** Get a list of installed packages that depend on this package* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array* @return array|false*/function getDependentPackages(&$pkg){$data = $this->_getDepDB();if (is_object($pkg)) {$channel = strtolower($pkg->getChannel());$package = strtolower($pkg->getPackage());} else {$channel = strtolower($pkg['channel']);$package = strtolower($pkg['package']);}if (isset($data['packages'][$channel][$package])) {return $data['packages'][$channel][$package];}return false;}/*** Get a list of the actual dependencies of installed packages that depend on* a package.* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array* @return array|false*/function getDependentPackageDependencies(&$pkg){$data = $this->_getDepDB();if (is_object($pkg)) {$channel = strtolower($pkg->getChannel());$package = strtolower($pkg->getPackage());} else {$channel = strtolower($pkg['channel']);$package = strtolower($pkg['package']);}$depend = $this->getDependentPackages($pkg);if (!$depend) {return false;}$dependencies = array();foreach ($depend as $info) {$temp = $this->getDependencies($info);foreach ($temp as $dep) {if (strtolower($dep['dep']['channel']) == strtolower($channel) &&strtolower($dep['dep']['name']) == strtolower($package)) {if (!isset($dependencies[$info['channel']])) {$dependencies[$info['channel']] = array();}if (!isset($dependencies[$info['channel']][$info['package']])) {$dependencies[$info['channel']][$info['package']] = array();}$dependencies[$info['channel']][$info['package']][] = $dep;}}}return $dependencies;}/*** Get a list of dependencies of this installed package* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array* @return array|false*/function getDependencies(&$pkg){if (is_object($pkg)) {$channel = strtolower($pkg->getChannel());$package = strtolower($pkg->getPackage());} else {$channel = strtolower($pkg['channel']);$package = strtolower($pkg['package']);}$data = $this->_getDepDB();if (isset($data['dependencies'][$channel][$package])) {return $data['dependencies'][$channel][$package];}return false;}/*** Determine whether $parent depends on $child, near or deep* @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2* @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2*/function dependsOn($parent, $child){$c = array();$this->_getDepDB();return $this->_dependsOn($parent, $child, $c);}function _dependsOn($parent, $child, &$checked){if (is_object($parent)) {$channel = strtolower($parent->getChannel());$package = strtolower($parent->getPackage());} else {$channel = strtolower($parent['channel']);$package = strtolower($parent['package']);}if (is_object($child)) {$depchannel = strtolower($child->getChannel());$deppackage = strtolower($child->getPackage());} else {$depchannel = strtolower($child['channel']);$deppackage = strtolower($child['package']);}if (isset($checked[$channel][$package][$depchannel][$deppackage])) {return false; // avoid endless recursion}$checked[$channel][$package][$depchannel][$deppackage] = true;if (!isset($this->_cache['dependencies'][$channel][$package])) {return false;}foreach ($this->_cache['dependencies'][$channel][$package] as $info) {if (isset($info['dep']['uri'])) {if (is_object($child)) {if ($info['dep']['uri'] == $child->getURI()) {return true;}} elseif (isset($child['uri'])) {if ($info['dep']['uri'] == $child['uri']) {return true;}}return false;}if (strtolower($info['dep']['channel']) == strtolower($depchannel) &&strtolower($info['dep']['name']) == strtolower($deppackage)) {return true;}}foreach ($this->_cache['dependencies'][$channel][$package] as $info) {if (isset($info['dep']['uri'])) {if ($this->_dependsOn(array('uri' => $info['dep']['uri'],'package' => $info['dep']['name']), $child, $checked)) {return true;}} else {if ($this->_dependsOn(array('channel' => $info['dep']['channel'],'package' => $info['dep']['name']), $child, $checked)) {return true;}}}return false;}/*** Register dependencies of a package that is being installed or upgraded* @param PEAR_PackageFile_v2|PEAR_PackageFile_v2*/function installPackage(&$package){$data = $this->_getDepDB();unset($this->_cache);$this->_setPackageDeps($data, $package);$this->_writeDepDB($data);}/*** Remove dependencies of a package that is being uninstalled, or upgraded.** Upgraded packages first uninstall, then install* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have* indices 'channel' and 'package'*/function uninstallPackage(&$pkg){$data = $this->_getDepDB();unset($this->_cache);if (is_object($pkg)) {$channel = strtolower($pkg->getChannel());$package = strtolower($pkg->getPackage());} else {$channel = strtolower($pkg['channel']);$package = strtolower($pkg['package']);}if (!isset($data['dependencies'][$channel][$package])) {return true;}foreach ($data['dependencies'][$channel][$package] as $dep) {$found = false;if (isset($dep['dep']['uri'])) {$depchannel = '__uri';} else {$depchannel = strtolower($dep['dep']['channel']);}if (isset($data['packages'][$depchannel][strtolower($dep['dep']['name'])])) {foreach ($data['packages'][$depchannel][strtolower($dep['dep']['name'])] as$i => $info) {if ($info['channel'] == $channel &&$info['package'] == $package) {$found = true;break;}}}if ($found) {unset($data['packages'][$depchannel][strtolower($dep['dep']['name'])][$i]);if (!count($data['packages'][$depchannel][strtolower($dep['dep']['name'])])) {unset($data['packages'][$depchannel][strtolower($dep['dep']['name'])]);if (!count($data['packages'][$depchannel])) {unset($data['packages'][$depchannel]);}} else {$data['packages'][$depchannel][strtolower($dep['dep']['name'])] =array_values($data['packages'][$depchannel][strtolower($dep['dep']['name'])]);}}}unset($data['dependencies'][$channel][$package]);if (!count($data['dependencies'][$channel])) {unset($data['dependencies'][$channel]);}if (!count($data['dependencies'])) {unset($data['dependencies']);}if (!count($data['packages'])) {unset($data['packages']);}$this->_writeDepDB($data);}/*** Rebuild the dependency DB by reading registry entries.* @return true|PEAR_Error*/function rebuildDB(){$depdb = array('_version' => $this->_version);if (!$this->hasWriteAccess()) {// allow startup for read-only with older Registryreturn $depdb;}$packages = $this->_registry->listAllPackages();foreach ($packages as $channel => $ps) {foreach ($ps as $package) {$package = $this->_registry->getPackage($package, $channel);$this->_setPackageDeps($depdb, $package);}}$error = $this->_writeDepDB($depdb);if (PEAR::isError($error)) {return $error;}$this->_cache = $depdb;return true;}/*** Register usage of the dependency DB to prevent race conditions* @param int one of the LOCK_* constants* @return true|PEAR_Error* @access private*/function _lock($mode = LOCK_EX){if (!eregi('Windows 9', php_uname())) {if ($mode != LOCK_UN && is_resource($this->_lockFp)) {// XXX does not check type of lock (LOCK_SH/LOCK_EX)return true;}$open_mode = 'w';// XXX People reported problems with LOCK_SH and 'w'if ($mode === LOCK_SH) {if (!file_exists($this->_lockfile)) {touch($this->_lockfile);} elseif (!is_file($this->_lockfile)) {return PEAR::raiseError('could not create Dependency lock file, ' .'it exists and is not a regular file');}$open_mode = 'r';}if (!is_resource($this->_lockFp)) {$this->_lockFp = @fopen($this->_lockfile, $open_mode);}if (!is_resource($this->_lockFp)) {return PEAR::raiseError("could not create Dependency lock file" .(isset($php_errormsg) ? ": " . $php_errormsg : ""));}if (!(int)flock($this->_lockFp, $mode)) {switch ($mode) {case LOCK_SH: $str = 'shared'; break;case LOCK_EX: $str = 'exclusive'; break;case LOCK_UN: $str = 'unlock'; break;default: $str = 'unknown'; break;}return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)");}}return true;}/*** Release usage of dependency DB* @return true|PEAR_Error* @access private*/function _unlock(){$ret = $this->_lock(LOCK_UN);if (is_resource($this->_lockFp)) {fclose($this->_lockFp);}$this->_lockFp = null;return $ret;}/*** Load the dependency database from disk, or return the cache* @return array|PEAR_Error*/function _getDepDB(){if (!$this->hasWriteAccess()) {return array('_version' => $this->_version);}if (isset($this->_cache)) {return $this->_cache;}if (!$fp = fopen($this->_depdb, 'r')) {$err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'");return $err;}$rt = get_magic_quotes_runtime();set_magic_quotes_runtime(0);clearstatcache();fclose($fp);$data = unserialize(file_get_contents($this->_depdb));set_magic_quotes_runtime($rt);$this->_cache = $data;return $data;}/*** Write out the dependency database to disk* @param array the database* @return true|PEAR_Error* @access private*/function _writeDepDB(&$deps){if (PEAR::isError($e = $this->_lock(LOCK_EX))) {return $e;}if (!$fp = fopen($this->_depdb, 'wb')) {$this->_unlock();return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing");}$rt = get_magic_quotes_runtime();set_magic_quotes_runtime(0);fwrite($fp, serialize($deps));set_magic_quotes_runtime($rt);fclose($fp);$this->_unlock();$this->_cache = $deps;return true;}/*** Register all dependencies from a package in the dependencies database, in essence* "installing" the package's dependency information* @param array the database* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2* @access private*/function _setPackageDeps(&$data, &$pkg){$pkg->setConfig($this->_config);if ($pkg->getPackagexmlVersion() == '1.0') {$gen = &$pkg->getDefaultGenerator();$deps = $gen->dependenciesToV2();} else {$deps = $pkg->getDeps(true);}if (!$deps) {return;}if (!is_array($data)) {$data = array();}if (!isset($data['dependencies'])) {$data['dependencies'] = array();}if (!isset($data['dependencies'][strtolower($pkg->getChannel())])) {$data['dependencies'][strtolower($pkg->getChannel())] = array();}$data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())]= array();if (isset($deps['required']['package'])) {if (!isset($deps['required']['package'][0])) {$deps['required']['package'] = array($deps['required']['package']);}foreach ($deps['required']['package'] as $dep) {$this->_registerDep($data, $pkg, $dep, 'required');}}if (isset($deps['optional']['package'])) {if (!isset($deps['optional']['package'][0])) {$deps['optional']['package'] = array($deps['optional']['package']);}foreach ($deps['optional']['package'] as $dep) {$this->_registerDep($data, $pkg, $dep, 'optional');}}if (isset($deps['required']['subpackage'])) {if (!isset($deps['required']['subpackage'][0])) {$deps['required']['subpackage'] = array($deps['required']['subpackage']);}foreach ($deps['required']['subpackage'] as $dep) {$this->_registerDep($data, $pkg, $dep, 'required');}}if (isset($deps['optional']['subpackage'])) {if (!isset($deps['optional']['subpackage'][0])) {$deps['optional']['subpackage'] = array($deps['optional']['subpackage']);}foreach ($deps['optional']['subpackage'] as $dep) {$this->_registerDep($data, $pkg, $dep, 'optional');}}if (isset($deps['group'])) {if (!isset($deps['group'][0])) {$deps['group'] = array($deps['group']);}foreach ($deps['group'] as $group) {if (isset($group['package'])) {if (!isset($group['package'][0])) {$group['package'] = array($group['package']);}foreach ($group['package'] as $dep) {$this->_registerDep($data, $pkg, $dep, 'optional',$group['attribs']['name']);}}if (isset($group['subpackage'])) {if (!isset($group['subpackage'][0])) {$group['subpackage'] = array($group['subpackage']);}foreach ($group['subpackage'] as $dep) {$this->_registerDep($data, $pkg, $dep, 'optional',$group['attribs']['name']);}}}}if ($data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())] == array()) {unset($data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())]);if (!count($data['dependencies'][strtolower($pkg->getChannel())])) {unset($data['dependencies'][strtolower($pkg->getChannel())]);}}}/*** @param array the database* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2* @param array the specific dependency* @param required|optional whether this is a required or an optional dep* @param string|false dependency group this dependency is from, or false for ordinary dep*/function _registerDep(&$data, &$pkg, $dep, $type, $group = false){$info = array('dep' => $dep,'type' => $type,'group' => $group);if (isset($dep['channel'])) {$depchannel = $dep['channel'];} else {$depchannel = '__uri';}if (!isset($data['dependencies'])) {$data['dependencies'] = array();}if (!isset($data['dependencies'][strtolower($pkg->getChannel())])) {$data['dependencies'][strtolower($pkg->getChannel())] = array();}if (!isset($data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())])) {$data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())] = array();}$data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())][]= $info;if (isset($data['packages'][strtolower($depchannel)][strtolower($dep['name'])])) {$found = false;foreach ($data['packages'][strtolower($depchannel)][strtolower($dep['name'])]as $i => $p) {if ($p['channel'] == strtolower($pkg->getChannel()) &&$p['package'] == strtolower($pkg->getPackage())) {$found = true;break;}}if (!$found) {$data['packages'][strtolower($depchannel)][strtolower($dep['name'])][]= array('channel' => strtolower($pkg->getChannel()),'package' => strtolower($pkg->getPackage()));}} else {if (!isset($data['packages'])) {$data['packages'] = array();}if (!isset($data['packages'][strtolower($depchannel)])) {$data['packages'][strtolower($depchannel)] = array();}if (!isset($data['packages'][strtolower($depchannel)][strtolower($dep['name'])])) {$data['packages'][strtolower($depchannel)][strtolower($dep['name'])] = array();}$data['packages'][strtolower($depchannel)][strtolower($dep['name'])][]= array('channel' => strtolower($pkg->getChannel()),'package' => strtolower($pkg->getPackage()));}}}?>