319 lines
11 KiB
PHP
319 lines
11 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of Composer.
|
|
*
|
|
* (c) Nils Adermann <naderman@naderman.de>
|
|
* Jordi Boggiano <j.boggiano@seld.be>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Composer\Repository\Pear;
|
|
|
|
/**
|
|
* Read PEAR packages using REST 1.0 interface
|
|
*
|
|
* @author Alexey Prilipko <palex@farpost.com>
|
|
*/
|
|
class PackageDependencyParser
|
|
{
|
|
/**
|
|
* Builds dependency information. It detects used package.xml format.
|
|
*
|
|
* @param $depArray array
|
|
* @return DependencyInfo
|
|
*/
|
|
public function buildDependencyInfo($depArray)
|
|
{
|
|
if (!is_array($depArray)) {
|
|
return new DependencyInfo(array(), array());
|
|
}
|
|
if (!$this->isHash($depArray)) {
|
|
return new DependencyInfo($this->buildDependency10Info($depArray), array());
|
|
}
|
|
|
|
return $this->buildDependency20Info($depArray);
|
|
}
|
|
|
|
/**
|
|
* Builds dependency information from package.xml 1.0 format
|
|
*
|
|
* https://pear.php.net/manual/en/guide.developers.package2.dependencies.php
|
|
*
|
|
* package.xml 1.0 format consists of array of
|
|
* { type="php|os|sapi|ext|pkg" rel="has|not|eq|ge|gt|le|lt" optional="yes"
|
|
* channel="channelName" name="extName|packageName" }
|
|
*
|
|
* @param $depArray array Dependency data in package.xml 1.0 format
|
|
* @return DependencyConstraint[]
|
|
*/
|
|
private function buildDependency10Info($depArray)
|
|
{
|
|
static $dep10toOperatorMap = array('has' => '==', 'eq' => '==', 'ge' => '>=', 'gt' => '>', 'le' => '<=', 'lt' => '<', 'not' => '!=');
|
|
|
|
$result = array();
|
|
|
|
foreach ($depArray as $depItem) {
|
|
if (empty($depItem['rel']) || !array_key_exists($depItem['rel'], $dep10toOperatorMap)) {
|
|
// 'unknown rel type:' . $depItem['rel'];
|
|
continue;
|
|
}
|
|
|
|
$depType = !empty($depItem['optional']) && 'yes' == $depItem['optional']
|
|
? 'optional'
|
|
: 'required';
|
|
$depType = 'not' == $depItem['rel']
|
|
? 'conflicts'
|
|
: $depType;
|
|
|
|
$depVersion = !empty($depItem['version']) ? $this->parseVersion($depItem['version']) : '*';
|
|
|
|
// has & not are special operators that does not requires version
|
|
$depVersionConstraint = ('has' == $depItem['rel'] || 'not' == $depItem['rel']) && '*' == $depVersion
|
|
? '*'
|
|
: $dep10toOperatorMap[$depItem['rel']] . $depVersion;
|
|
|
|
switch ($depItem['type']) {
|
|
case 'php':
|
|
$depChannelName = 'php';
|
|
$depPackageName = '';
|
|
break;
|
|
case 'pkg':
|
|
$depChannelName = !empty($depItem['channel']) ? $depItem['channel'] : 'pear.php.net';
|
|
$depPackageName = $depItem['name'];
|
|
break;
|
|
case 'ext':
|
|
$depChannelName = 'ext';
|
|
$depPackageName = $depItem['name'];
|
|
break;
|
|
case 'os':
|
|
case 'sapi':
|
|
$depChannelName = '';
|
|
$depPackageName = '';
|
|
break;
|
|
default:
|
|
$depChannelName = '';
|
|
$depPackageName = '';
|
|
break;
|
|
}
|
|
|
|
if ('' != $depChannelName) {
|
|
$result[] = new DependencyConstraint(
|
|
$depType,
|
|
$depVersionConstraint,
|
|
$depChannelName,
|
|
$depPackageName
|
|
);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Builds dependency information from package.xml 2.0 format
|
|
*
|
|
* @param $depArray array Dependency data in package.xml 1.0 format
|
|
* @return DependencyInfo
|
|
*/
|
|
private function buildDependency20Info($depArray)
|
|
{
|
|
$result = array();
|
|
$optionals = array();
|
|
$defaultOptionals = array();
|
|
foreach ($depArray as $depType => $depTypeGroup) {
|
|
if (!is_array($depTypeGroup)) {
|
|
continue;
|
|
}
|
|
if ('required' == $depType || 'optional' == $depType) {
|
|
foreach ($depTypeGroup as $depItemType => $depItem) {
|
|
switch ($depItemType) {
|
|
case 'php':
|
|
$result[] = new DependencyConstraint(
|
|
$depType,
|
|
$this->parse20VersionConstraint($depItem),
|
|
'php',
|
|
''
|
|
);
|
|
break;
|
|
case 'package':
|
|
$deps = $this->buildDepPackageConstraints($depItem, $depType);
|
|
$result = array_merge($result, $deps);
|
|
break;
|
|
case 'extension':
|
|
$deps = $this->buildDepExtensionConstraints($depItem, $depType);
|
|
$result = array_merge($result, $deps);
|
|
break;
|
|
case 'subpackage':
|
|
$deps = $this->buildDepPackageConstraints($depItem, 'replaces');
|
|
$defaultOptionals += $deps;
|
|
break;
|
|
case 'os':
|
|
case 'pearinstaller':
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} elseif ('group' == $depType) {
|
|
if ($this->isHash($depTypeGroup)) {
|
|
$depTypeGroup = array($depTypeGroup);
|
|
}
|
|
|
|
foreach ($depTypeGroup as $depItem) {
|
|
$groupName = $depItem['attribs']['name'];
|
|
if (!isset($optionals[$groupName])) {
|
|
$optionals[$groupName] = array();
|
|
}
|
|
|
|
if (isset($depItem['subpackage'])) {
|
|
$optionals[$groupName] += $this->buildDepPackageConstraints($depItem['subpackage'], 'replaces');
|
|
} else {
|
|
$result += $this->buildDepPackageConstraints($depItem['package'], 'optional');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count($defaultOptionals) > 0) {
|
|
$optionals['*'] = $defaultOptionals;
|
|
}
|
|
|
|
return new DependencyInfo($result, $optionals);
|
|
}
|
|
|
|
/**
|
|
* Builds dependency constraint of 'extension' type
|
|
*
|
|
* @param $depItem array dependency constraint or array of dependency constraints
|
|
* @param $depType string target type of building constraint.
|
|
* @return DependencyConstraint[]
|
|
*/
|
|
private function buildDepExtensionConstraints($depItem, $depType)
|
|
{
|
|
if ($this->isHash($depItem)) {
|
|
$depItem = array($depItem);
|
|
}
|
|
|
|
$result = array();
|
|
foreach ($depItem as $subDepItem) {
|
|
$depChannelName = 'ext';
|
|
$depPackageName = $subDepItem['name'];
|
|
$depVersionConstraint = $this->parse20VersionConstraint($subDepItem);
|
|
|
|
$result[] = new DependencyConstraint(
|
|
$depType,
|
|
$depVersionConstraint,
|
|
$depChannelName,
|
|
$depPackageName
|
|
);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Builds dependency constraint of 'package' type
|
|
*
|
|
* @param $depItem array dependency constraint or array of dependency constraints
|
|
* @param $depType string target type of building constraint.
|
|
* @return DependencyConstraint[]
|
|
*/
|
|
private function buildDepPackageConstraints($depItem, $depType)
|
|
{
|
|
if ($this->isHash($depItem)) {
|
|
$depItem = array($depItem);
|
|
}
|
|
|
|
$result = array();
|
|
foreach ($depItem as $key => $subDepItem) {
|
|
if (!array_key_exists('channel', $subDepItem)) {
|
|
$depItem[$key]['channel'] = $depItem[$key]['uri'];
|
|
$subDepItem['channel'] = $subDepItem['uri'];
|
|
}
|
|
$depChannelName = $subDepItem['channel'];
|
|
$depPackageName = $subDepItem['name'];
|
|
$depVersionConstraint = $this->parse20VersionConstraint($subDepItem);
|
|
if (isset($subDepItem['conflicts'])) {
|
|
$depType = 'conflicts';
|
|
}
|
|
|
|
$result[] = new DependencyConstraint(
|
|
$depType,
|
|
$depVersionConstraint,
|
|
$depChannelName,
|
|
$depPackageName
|
|
);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Parses version constraint
|
|
*
|
|
* @param array $data array containing several 'min', 'max', 'has', 'exclude' and other keys.
|
|
* @return string
|
|
*/
|
|
private function parse20VersionConstraint(array $data)
|
|
{
|
|
static $dep20toOperatorMap = array('has' => '==', 'min' => '>=', 'max' => '<=', 'exclude' => '!=');
|
|
|
|
$versions = array();
|
|
$values = array_intersect_key($data, $dep20toOperatorMap);
|
|
if (0 == count($values)) {
|
|
return '*';
|
|
}
|
|
if (isset($values['min']) && isset($values['exclude']) && $data['min'] == $data['exclude']) {
|
|
$versions[] = '>' . $this->parseVersion($values['min']);
|
|
} elseif (isset($values['max']) && isset($values['exclude']) && $data['max'] == $data['exclude']) {
|
|
$versions[] = '<' . $this->parseVersion($values['max']);
|
|
} else {
|
|
foreach ($values as $op => $version) {
|
|
if ('exclude' == $op && is_array($version)) {
|
|
foreach ($version as $versionPart) {
|
|
$versions[] = $dep20toOperatorMap[$op] . $this->parseVersion($versionPart);
|
|
}
|
|
} else {
|
|
$versions[] = $dep20toOperatorMap[$op] . $this->parseVersion($version);
|
|
}
|
|
}
|
|
}
|
|
|
|
return implode(',', $versions);
|
|
}
|
|
|
|
/**
|
|
* Softened version parser
|
|
*
|
|
* @param $version
|
|
* @return null|string
|
|
*/
|
|
private function parseVersion($version)
|
|
{
|
|
if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?}i', $version, $matches)) {
|
|
$version = $matches[1]
|
|
.(!empty($matches[2]) ? $matches[2] : '.0')
|
|
.(!empty($matches[3]) ? $matches[3] : '.0')
|
|
.(!empty($matches[4]) ? $matches[4] : '.0');
|
|
|
|
return $version;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Test if array is associative or hash type
|
|
*
|
|
* @param array $array
|
|
* @return bool
|
|
*/
|
|
private function isHash(array $array)
|
|
{
|
|
return !array_key_exists(1, $array) && !array_key_exists(0, $array);
|
|
}
|
|
}
|