diff --git a/src/Composer/Repository/Pear/BaseChannelReader.php b/src/Composer/Repository/Pear/BaseChannelReader.php new file mode 100644 index 000000000..fa6944ed5 --- /dev/null +++ b/src/Composer/Repository/Pear/BaseChannelReader.php @@ -0,0 +1,81 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Util\RemoteFilesystem; + +/** + * Base PEAR Channel reader. + * + * Provides xml namespaces and red + * + * @author Alexey Prilipko + */ +abstract class BaseChannelReader +{ + /** + * PEAR REST Interface namespaces + */ + const channelNS = 'http://pear.php.net/channel-1.0'; + const allCategoriesNS = 'http://pear.php.net/dtd/rest.allcategories'; + const categoryPackagesInfoNS = 'http://pear.php.net/dtd/rest.categorypackageinfo'; + const allPackagesNS = 'http://pear.php.net/dtd/rest.allpackages'; + const allReleasesNS = 'http://pear.php.net/dtd/rest.allreleases'; + const packageInfoNS = 'http://pear.php.net/dtd/rest.package'; + + /** @var RemoteFilesystem */ + private $rfs; + + protected function __construct($rfs) + { + $this->rfs = $rfs; + } + + /** + * Read content from remote filesystem. + * + * @param $origin string server + * @param $path string relative path to content + * @return \SimpleXMLElement + */ + protected function requestContent($origin, $path) + { + $url = rtrim($origin, '/') . '/' . ltrim($path, '/'); + $content = $this->rfs->getContents($origin, $url, false); + if (!$content) { + throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.'); + } + + return $content; + } + + /** + * Read xml content from remote filesystem + * + * @param $origin string server + * @param $path string relative path to content + * @return \SimpleXMLElement + */ + protected function requestXml($origin, $path) + { + // http://components.ez.no/p/packages.xml is malformed. to read it we must ignore parsing errors. + $xml = simplexml_load_string($this->requestContent($origin, $path), "SimpleXMLElement", LIBXML_NOERROR); + + if (false == $xml) { + $url = rtrim($origin, '/') . '/' . ltrim($path, '/'); + throw new \UnexpectedValueException('The PEAR channel at '.$origin.' is broken.'); + } + + return $xml; + } +} diff --git a/src/Composer/Repository/Pear/ChannelInfo.php b/src/Composer/Repository/Pear/ChannelInfo.php new file mode 100644 index 000000000..69e33b887 --- /dev/null +++ b/src/Composer/Repository/Pear/ChannelInfo.php @@ -0,0 +1,67 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * PEAR channel info + * + * @author Alexey Prilipko + */ +class ChannelInfo +{ + private $name; + private $alias; + private $packages; + + /** + * @param string $name + * @param string $alias + * @param PackageInfo[] $packages + */ + public function __construct($name, $alias, array $packages) + { + $this->name = $name; + $this->alias = $alias; + $this->packages = $packages; + } + + /** + * Name of the channel + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Alias of the channel + * + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * List of channel packages + * + * @return PackageInfo[] + */ + public function getPackages() + { + return $this->packages; + } +} diff --git a/src/Composer/Repository/Pear/ChannelReader.php b/src/Composer/Repository/Pear/ChannelReader.php new file mode 100644 index 000000000..54bdc39fa --- /dev/null +++ b/src/Composer/Repository/Pear/ChannelReader.php @@ -0,0 +1,200 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Util\RemoteFilesystem; +use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Package\Link; + +/** + * PEAR Channel package reader. + * + * Reads channel packages info from and builds MemoryPackage's + * + * @author Alexey Prilipko + */ +class ChannelReader extends BaseChannelReader +{ + /** @var array of ('xpath test' => 'rest implementation') */ + private $readerMap; + + public function __construct(RemoteFilesystem $rfs) + { + parent::__construct($rfs); + + $rest10reader = new ChannelRest10Reader($rfs); + $rest11reader = new ChannelRest11Reader($rfs); + + $this->readerMap = array( + 'REST1.3' => $rest11reader, + 'REST1.2' => $rest11reader, + 'REST1.1' => $rest11reader, + 'REST1.0' => $rest10reader, + ); + } + + /** + * Reads channel supported REST interfaces and selects one of them + * + * @param $channelXml \SimpleXMLElement + * @param $supportedVersions string[] supported PEAR REST protocols + * @return array|bool hash with selected version and baseUrl + */ + private function selectRestVersion($channelXml, $supportedVersions) + { + $channelXml->registerXPathNamespace('ns', self::channelNS); + + foreach ($supportedVersions as $version) { + $xpathTest = "ns:servers/ns:primary/ns:rest/ns:baseurl[@type='{$version}']"; + $testResult = $channelXml->xpath($xpathTest); + if (count($testResult) > 0) { + return array('version' => $version, 'baseUrl' => (string) $testResult[0]); + } + } + + return false; + } + + /** + * Reads PEAR channel through REST interface and builds list of packages + * + * @param $url string PEAR Channel url + * @return ChannelInfo + */ + public function read($url) + { + $xml = $this->requestXml($url, "/channel.xml"); + + $channelName = (string) $xml->name; + $channelSummary = (string) $xml->summary; + $channelAlias = (string) $xml->suggestedalias; + + $supportedVersions = array_keys($this->readerMap); + $selectedRestVersion = $this->selectRestVersion($xml, $supportedVersions); + if (false === $selectedRestVersion) { + throw new \UnexpectedValueException(sprintf('PEAR repository $s does not supports any of %s protocols.', $url, implode(', ', $supportedVersions))); + } + + $reader = $this->readerMap[$selectedRestVersion['version']]; + $packageDefinitions = $reader->read($selectedRestVersion['baseUrl']); + + return new ChannelInfo($channelName, $channelAlias, $packageDefinitions); + } + + /** + * Builds MemoryPackages from PEAR package definition data. + * + * @param $channelName string channel name + * @param $channelAlias string channel alias + * @param $packageDefinitions PackageInfo[] package definition + * @return array + */ + private function buildComposerPackages($channelName, $channelAlias, $packageDefinitions) + { + $versionParser = new \Composer\Package\Version\VersionParser(); + $result = array(); + foreach ($packageDefinitions as $packageDefinition) { + foreach ($packageDefinition->getReleases() as $version => $releaseInfo) { + $normalizedVersion = $this->parseVersion($version); + if (false === $normalizedVersion) { + continue; // skip packages with unparsable versions + } + + $composerPackageName = $this->buildComposerPackageName($packageDefinition->getChannelName(), $packageDefinition->getPackageName()); + + // distribution url must be read from /r/{packageName}/{version}.xml::/r/g:text() + $distUrl = "http://{$packageDefinition->getChannelName()}/get/{$packageDefinition->getPackageName()}-{$version}.tgz"; + + $requires = array(); + $suggests = array(); + $conflicts = array(); + $replaces = array(); + + // alias package only when its channel matches repository channel, + // cause we've know only repository channel alias + if ($channelName == $packageDefinition->getChannelName()) { + $composerPackageAlias = $this->buildComposerPackageName($channelAlias, $packageDefinition->getPackageName()); + $aliasConstraint = new VersionConstraint('==', $normalizedVersion); + $aliasLink = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); + $replaces[] = $aliasLink; + } + + $dependencyInfo = $releaseInfo->getDependencyInfo(); + foreach ($dependencyInfo->getRequires() as $dependencyConstraint) { + $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); + $constraint = $versionParser->parseConstraints($dependencyConstraint->getConstraint()); + $link = new Link($composerPackageName, $dependencyPackageName, $constraint, $dependencyConstraint->getType(), $dependencyConstraint->getConstraint()); + switch ($dependencyConstraint->getType()) { + case 'required': + $requires[] = $link; + break; + case 'conflicts': + $conflicts[] = $link; + break; + case 'replaces': + $replaces[] = $link; + break; + } + } + + foreach ($dependencyInfo->getOptionals() as $groupName => $dependencyConstraints) { + foreach ($dependencyConstraints as $dependencyConstraint) { + $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); + $suggests[$groupName.'-'.$dependencyPackageName] = $dependencyConstraint->getConstraint(); + } + } + + $package = new \Composer\Package\MemoryPackage($composerPackageName, $normalizedVersion, $version); + $package->setType('library'); + $package->setDescription($packageDefinition->getDescription()); + $package->setDistType('pear'); + $package->setDistUrl($distUrl); + $package->setAutoload(array('classmap' => array(''))); + $package->setIncludePaths(array('/')); + $package->setRequires($requires); + $package->setConflicts($conflicts); + $package->setSuggests($suggests); + $package->setReplaces($replaces); + $result[] = $package; + } + } + + return $result; + } + + private function buildComposerPackageName($pearChannelName, $pearPackageName) + { + if ($pearChannelName == 'php') { + return "php"; + } + if ($pearChannelName == 'ext') { + return "ext-{$pearPackageName}"; + } + + return "pear-{$pearChannelName}/{$pearPackageName}"; + } + + protected 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; + } else { + return false; + } + } +} diff --git a/src/Composer/Repository/Pear/ChannelRest10Reader.php b/src/Composer/Repository/Pear/ChannelRest10Reader.php new file mode 100644 index 000000000..fb5b91957 --- /dev/null +++ b/src/Composer/Repository/Pear/ChannelRest10Reader.php @@ -0,0 +1,164 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Downloader\TransportException; + +/** + * Read PEAR packages using REST 1.0 interface + * + * At version 1.0 package descriptions read from: + * {baseUrl}/p/packages.xml + * {baseUrl}/p/{package}/info.xml + * {baseUrl}/p/{package}/allreleases.xml + * {baseUrl}/p/{package}/deps.{version}.txt + * + * @author Alexey Prilipko + */ +class ChannelRest10Reader extends BaseChannelReader +{ + private $dependencyReader; + + public function __construct($rfs) + { + parent::__construct($rfs); + + $this->dependencyReader = new PackageDependencyParser(); + } + + /** + * Reads package descriptions using PEAR Rest 1.0 interface + * + * @param $baseUrl string base Url interface + * + * @return PackageInfo[] + */ + public function read($baseUrl) + { + return $this->readPackages($baseUrl); + } + + /** + * Read list of packages from + * {baseUrl}/p/packages.xml + * + * @param $baseUrl string + * @return PackageInfo[] + */ + private function readPackages($baseUrl) + { + $result = array(); + + $xmlPath = '/p/packages.xml'; + $xml = $this->requestXml($baseUrl, $xmlPath); + $xml->registerXPathNamespace('ns', self::allPackagesNS); + foreach ($xml->xpath('ns:p') as $node) { + $packageName = (string) $node; + $packageInfo = $this->readPackage($baseUrl, $packageName); + $result[] = $packageInfo; + } + + return $result; + } + + /** + * Read package info from + * {baseUrl}/p/{package}/info.xml + * + * @param $baseUrl string + * @param $packageName string + * @return PackageInfo + */ + private function readPackage($baseUrl, $packageName) + { + $xmlPath = '/p/' . strtolower($packageName) . '/info.xml'; + $xml = $this->requestXml($baseUrl, $xmlPath); + $xml->registerXPathNamespace('ns', self::packageInfoNS); + + $channelName = (string) $xml->c; + $packageName = (string) $xml->n; + $license = (string) $xml->l; + $shortDescription = (string) $xml->s; + $description = (string) $xml->d; + + return new PackageInfo( + $channelName, + $packageName, + $license, + $shortDescription, + $description, + $this->readPackageReleases($baseUrl, $packageName) + ); + } + + /** + * Read package releases from + * {baseUrl}/p/{package}/allreleases.xml + * + * @param $baseUrl string + * @param $packageName string + * @return ReleaseInfo[] hash array with keys as version numbers + */ + private function readPackageReleases($baseUrl, $packageName) + { + $result = array(); + + try { + $xmlPath = '/r/' . strtolower($packageName) . '/allreleases.xml'; + $xml = $this->requestXml($baseUrl, $xmlPath); + $xml->registerXPathNamespace('ns', self::allReleasesNS); + foreach ($xml->xpath('ns:r') as $node) { + $releaseVersion = (string) $node->v; + $releaseStability = (string) $node->s; + + try { + $result[$releaseVersion] = new ReleaseInfo( + $releaseStability, + $this->readPackageReleaseDependencies($baseUrl, $packageName, $releaseVersion) + ); + } catch (TransportException $exception) { + if ($exception->getCode() != 404) { + throw $exception; + } + } + } + } catch (TransportException $exception) { + if ($exception->getCode() != 404) { + throw $exception; + } + } + + return $result; + } + + /** + * Read package dependencies from + * {baseUrl}/p/{package}/deps.{version}.txt + * + * @param $baseUrl string + * @param $packageName string + * @param $version string + * @return DependencyInfo[] + */ + private function readPackageReleaseDependencies($baseUrl, $packageName, $version) + { + $dependencyReader = new PackageDependencyParser(); + + $depthPath = '/r/' . strtolower($packageName) . '/deps.' . $version . '.txt'; + $content = $this->requestContent($baseUrl, $depthPath); + $dependencyArray = unserialize($content); + $result = $dependencyReader->buildDependencyInfo($dependencyArray); + + return $result; + } +} diff --git a/src/Composer/Repository/Pear/ChannelRest11Reader.php b/src/Composer/Repository/Pear/ChannelRest11Reader.php new file mode 100644 index 000000000..90f988b59 --- /dev/null +++ b/src/Composer/Repository/Pear/ChannelRest11Reader.php @@ -0,0 +1,136 @@ + + * Jordi Boggiano + * + * 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.1 interface + * + * At version 1.1 package descriptions read from: + * {baseUrl}/c/categories.xml + * {baseUrl}/c/{category}/packagesinfo.xml + * + * @author Alexey Prilipko + */ +class ChannelRest11Reader extends BaseChannelReader +{ + private $dependencyReader; + + public function __construct($rfs) + { + parent::__construct($rfs); + + $this->dependencyReader = new PackageDependencyParser(); + } + + /** + * Reads package descriptions using PEAR Rest 1.1 interface + * + * @param $baseUrl string base Url interface + * + * @return PackageInfo[] + */ + public function read($baseUrl) + { + return $this->readChannelPackages($baseUrl); + } + + /** + * Read list of channel categories from + * {baseUrl}/c/categories.xml + * + * @param $baseUrl string + * @return PackageInfo[] + */ + private function readChannelPackages($baseUrl) + { + $result = array(); + + $xml = $this->requestXml($baseUrl, "/c/categories.xml"); + $xml->registerXPathNamespace('ns', self::allCategoriesNS); + foreach ($xml->xpath('ns:c') as $node) { + $categoryName = (string) $node; + $categoryPackages = $this->readCategoryPackages($baseUrl, $categoryName); + $result = array_merge($result, $categoryPackages); + } + + return $result; + } + + /** + * Read packages from + * {baseUrl}/c/{category}/packagesinfo.xml + * + * @param $baseUrl string + * @param $categoryName string + * @return PackageInfo[] + */ + private function readCategoryPackages($baseUrl, $categoryName) + { + $result = array(); + + $categoryPath = '/c/'.urlencode($categoryName).'/packagesinfo.xml'; + $xml = $this->requestXml($baseUrl, $categoryPath); + $xml->registerXPathNamespace('ns', self::categoryPackagesInfoNS); + foreach ($xml->xpath('ns:pi') as $node) { + $packageInfo = $this->parsePackage($node); + $result[] = $packageInfo; + } + + return $result; + } + + /** + * Parses package node. + * + * @param $packageInfo \SimpleXMLElement xml element describing package + * @return PackageInfo + */ + private function parsePackage($packageInfo) + { + $packageInfo->registerXPathNamespace('ns', self::categoryPackagesInfoNS); + $channelName = (string) $packageInfo->p->c; + $packageName = (string) $packageInfo->p->n; + $license = (string) $packageInfo->p->l; + $shortDescription = (string) $packageInfo->p->s; + $description = (string) $packageInfo->p->d; + + $dependencies = array(); + foreach ($packageInfo->xpath('ns:deps') as $node) { + $dependencyVersion = (string) $node->v; + $dependencyArray = unserialize((string) $node->d); + + $dependencyInfo = $this->dependencyReader->buildDependencyInfo($dependencyArray); + + $dependencies[$dependencyVersion] = $dependencyInfo; + } + + $releases = array(); + foreach ($packageInfo->xpath('ns:a/ns:r') as $node) { + $releaseVersion = (string) $node->v; + $releaseStability = (string) $node->s; + $releases[$releaseVersion] = new ReleaseInfo( + $releaseStability, + isset($dependencies[$releaseVersion]) ? $dependencies[$releaseVersion] : new DependencyInfo(array(), array()) + ); + } + + return new PackageInfo( + $channelName, + $packageName, + $license, + $shortDescription, + $description, + $releases + ); + } +} diff --git a/src/Composer/Repository/Pear/DependencyConstraint.php b/src/Composer/Repository/Pear/DependencyConstraint.php new file mode 100644 index 000000000..13a790026 --- /dev/null +++ b/src/Composer/Repository/Pear/DependencyConstraint.php @@ -0,0 +1,60 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * PEAR package release dependency info + * + * @author Alexey Prilipko + */ +class DependencyConstraint +{ + private $type; + private $constraint; + private $channelName; + private $packageName; + + /** + * @param string $type + * @param string $constraint + * @param string $channelName + * @param string $packageName + */ + public function __construct($type, $constraint, $channelName, $packageName) + { + $this->type = $type; + $this->constraint = $constraint; + $this->channelName = $channelName; + $this->packageName = $packageName; + } + + public function getChannelName() + { + return $this->channelName; + } + + public function getConstraint() + { + return $this->constraint; + } + + public function getPackageName() + { + return $this->packageName; + } + + public function getType() + { + return $this->type; + } +} diff --git a/src/Composer/Repository/Pear/DependencyInfo.php b/src/Composer/Repository/Pear/DependencyInfo.php new file mode 100644 index 000000000..c6b266e37 --- /dev/null +++ b/src/Composer/Repository/Pear/DependencyInfo.php @@ -0,0 +1,50 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * PEAR package release dependency info + * + * @author Alexey Prilipko + */ +class DependencyInfo +{ + private $requires; + private $optionals; + + /** + * @param DependencyConstraint[] $requires list of requires/conflicts/replaces + * @param array $optionals [groupName => DependencyConstraint[]] list of optional groups + */ + public function __construct($requires, $optionals) + { + $this->requires = $requires; + $this->optionals = $optionals; + } + + /** + * @return DependencyConstraint[] list of requires/conflicts/replaces + */ + public function getRequires() + { + return $this->requires; + } + + /** + * @return array [groupName => DependencyConstraint[]] list of optional groups + */ + public function getOptionals() + { + return $this->optionals; + } +} diff --git a/src/Composer/Repository/Pear/PackageDependencyParser.php b/src/Composer/Repository/Pear/PackageDependencyParser.php new file mode 100644 index 000000000..6902facac --- /dev/null +++ b/src/Composer/Repository/Pear/PackageDependencyParser.php @@ -0,0 +1,312 @@ + + * Jordi Boggiano + * + * 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 + */ +class PackageDependencyParser +{ + /** + * Builds dependency information from package.xml 1.0 format + * + * http://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 $subDepItem) { + $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; + } + + /** + * 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()); + } elseif (!$this->isHash($depArray)) { + return new DependencyInfo($this->buildDependency10Info($depArray), array()); + } else { + return $this->buildDependency20Info($depArray); + } + } + + /** + * Parses version constraint + * + * @param array $data array containing serveral '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 '*'; + } elseif (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 bool|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; + } else { + return false; + } + } + + /** + * 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); + } +} diff --git a/src/Composer/Repository/Pear/PackageInfo.php b/src/Composer/Repository/Pear/PackageInfo.php new file mode 100644 index 000000000..5fd5956d3 --- /dev/null +++ b/src/Composer/Repository/Pear/PackageInfo.php @@ -0,0 +1,94 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * PEAR Package info + * + * @author Alexey Prilipko + */ +class PackageInfo +{ + private $channelName; + private $packageName; + private $license; + private $shortDescription; + private $description; + private $releases; + + /** + * @param string $channelName + * @param string $packageName + * @param string $license + * @param string $shortDescription + * @param string $description + * @param ReleaseInfo[] $releases associative array maps release version to release info + */ + public function __construct($channelName, $packageName, $license, $shortDescription, $description, $releases) + { + $this->channelName = $channelName; + $this->packageName = $packageName; + $this->license = $license; + $this->shortDescription = $shortDescription; + $this->description = $description; + $this->releases = $releases; + } + + /** + * @return string the package channel name + */ + public function getChannelName() + { + return $this->channelName; + } + + /** + * @return string the package name + */ + public function getPackageName() + { + return $this->packageName; + } + + /** + * @return string the package description + */ + public function getDescription() + { + return $this->description; + } + + /** + * @return string the package short escription + */ + public function getShortDescription() + { + return $this->shortDescription; + } + + /** + * @return string the package licence + */ + public function getLicense() + { + return $this->license; + } + + /** + * @return ReleaseInfo[] + */ + public function getReleases() + { + return $this->releases; + } +} diff --git a/src/Composer/Repository/Pear/ReleaseInfo.php b/src/Composer/Repository/Pear/ReleaseInfo.php new file mode 100644 index 000000000..4ad984d80 --- /dev/null +++ b/src/Composer/Repository/Pear/ReleaseInfo.php @@ -0,0 +1,50 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +/** + * PEAR package release info + * + * @author Alexey Prilipko + */ +class ReleaseInfo +{ + private $stability; + private $dependencyInfo; + + /** + * @param string $stability + * @param DependencyInfo $dependencies + */ + public function __construct($stability, $dependencyInfo) + { + $this->stability = $stability; + $this->dependencyInfo = $dependencyInfo; + } + + /** + * @return DependencyInfo release dependencies + */ + public function getDependencyInfo() + { + return $this->dependencyInfo; + } + + /** + * @return string release stability + */ + public function getStability() + { + return $this->stability; + } +} diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index fb4b64736..22045b863 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -13,26 +13,33 @@ namespace Composer\Repository; use Composer\IO\IOInterface; -use Composer\Package\Loader\ArrayLoader; +use Composer\Package\MemoryPackage; +use Composer\Repository\Pear\ChannelInfo; +use Composer\Package\Link; +use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Util\RemoteFilesystem; -use Composer\Json\JsonFile; use Composer\Config; -use Composer\Downloader\TransportException; /** + * Builds list of package from PEAR channel. + * + * Packages read from channel are named as 'pear-{channelName}/{packageName}' + * and has aliased as 'pear-{channelAlias}/{packageName}' + * * @author Benjamin Eberlei * @author Jordi Boggiano */ class PearRepository extends ArrayRepository { - private static $channelNames = array(); - private $url; - private $baseUrl; - private $channel; private $io; private $rfs; + /** @var string vendor makes additional alias for each channel as {prefix}/{packagename}. It allows smoother + * package transition to composer-like repositories. + */ + private $vendorAlias; + public function __construct(array $repoConfig, IOInterface $io, Config $config, RemoteFilesystem $rfs = null) { if (!preg_match('{^https?://}', $repoConfig['url'])) { @@ -44,9 +51,9 @@ class PearRepository extends ArrayRepository } $this->url = rtrim($repoConfig['url'], '/'); - $this->channel = !empty($repoConfig['channel']) ? $repoConfig['channel'] : null; $this->io = $io; $this->rfs = $rfs ?: new RemoteFilesystem($this->io); + $this->vendorAlias = isset($repoConfig['vendor-alias']) ? $repoConfig['vendor-alias'] : null; } protected function initialize() @@ -54,341 +61,127 @@ class PearRepository extends ArrayRepository parent::initialize(); $this->io->write('Initializing PEAR repository '.$this->url); - $this->initializeChannel(); - $this->io->write('Packages names will be prefixed with: pear-'.$this->channel.'/'); - // try to load as a composer repo + $reader = new \Composer\Repository\Pear\ChannelReader($this->rfs); try { - $json = new JsonFile($this->url.'/packages.json', new RemoteFilesystem($this->io)); - $packages = $json->read(); - - if ($this->io->isVerbose()) { - $this->io->write('Repository is Composer-compatible, loading via packages.json instead of PEAR protocol'); - } - - $loader = new ArrayLoader(); - foreach ($packages as $data) { - foreach ($data['versions'] as $rev) { - if (strpos($rev['name'], 'pear-'.$this->channel) !== 0) { - $rev['name'] = 'pear-'.$this->channel.'/'.$rev['name']; - } - $this->addPackage($loader->load($rev)); - if ($this->io->isVerbose()) { - $this->io->write('Loaded '.$rev['name'].' '.$rev['version']); - } - } - } - - return; + $channelInfo = $reader->read($this->url); } catch (\Exception $e) { + $this->io->write('PEAR repository from '.$this->url.' could not be loaded. '.$e->getMessage().''); + return; } - - $this->fetchFromServer(); - } - - protected function initializeChannel() - { - $channelXML = $this->requestXml($this->url . "/channel.xml"); - if (!$this->channel) { - $this->channel = $channelXML->getElementsByTagName("suggestedalias")->item(0)->nodeValue - ?: $channelXML->getElementsByTagName("name")->item(0)->nodeValue; - } - if (!$this->baseUrl) { - $this->baseUrl = $channelXML->getElementsByTagName("baseurl")->item(0)->nodeValue - ? trim($channelXML->getElementsByTagName("baseurl")->item(0)->nodeValue, '/') - : $this->url . '/rest'; - } - - self::$channelNames[$channelXML->getElementsByTagName("name")->item(0)->nodeValue] = $this->channel; - } - - protected function fetchFromServer() - { - $categoryXML = $this->requestXml($this->baseUrl . "/c/categories.xml"); - $categories = $categoryXML->getElementsByTagName("c"); - - foreach ($categories as $category) { - $link = $this->baseUrl . '/c/' . str_replace(' ', '+', $category->nodeValue); - try { - $packagesLink = $link . "/packagesinfo.xml"; - $this->fetchPear2Packages($packagesLink); - } catch (TransportException $e) { - if (false === strpos($e->getMessage(), '404')) { - throw $e; - } - $categoryLink = $link . "/packages.xml"; - $this->fetchPearPackages($categoryLink); - } - - } - } - - /** - * @param string $categoryLink - * @throws TransportException - * @throws \InvalidArgumentException - */ - private function fetchPearPackages($categoryLink) - { - $packagesXML = $this->requestXml($categoryLink); - $packages = $packagesXML->getElementsByTagName('p'); - $loader = new ArrayLoader(); + $packages = $this->buildComposerPackages($channelInfo); foreach ($packages as $package) { - $packageName = $package->nodeValue; - $fullName = 'pear-'.$this->channel.'/'.$packageName; - - $releaseLink = $this->baseUrl . "/r/" . $packageName; - $allReleasesLink = $releaseLink . "/allreleases2.xml"; - - try { - $releasesXML = $this->requestXml($allReleasesLink); - } catch (TransportException $e) { - if (strpos($e->getMessage(), '404')) { - continue; - } - throw $e; - } - - $releases = $releasesXML->getElementsByTagName('r'); - - foreach ($releases as $release) { - /* @var $release \DOMElement */ - $pearVersion = $release->getElementsByTagName('v')->item(0)->nodeValue; - - $packageData = array( - 'name' => $fullName, - 'type' => 'library', - 'dist' => array('type' => 'pear', 'url' => $this->url.'/get/'.$packageName.'-'.$pearVersion.".tgz"), - 'version' => $pearVersion, - 'autoload' => array( - 'classmap' => array(''), - ), - 'include-path' => array('/'), - ); - - try { - $deps = $this->rfs->getContents($this->url, $releaseLink . "/deps.".$pearVersion.".txt", false); - } catch (TransportException $e) { - if (strpos($e->getMessage(), '404')) { - continue; - } - throw $e; - } - - $packageData += $this->parseDependencies($deps); - - try { - $this->addPackage($loader->load($packageData)); - if ($this->io->isVerbose()) { - $this->io->write('Loaded '.$packageData['name'].' '.$packageData['version']); - } - } catch (\UnexpectedValueException $e) { - if ($this->io->isVerbose()) { - $this->io->write('Could not load '.$packageData['name'].' '.$packageData['version'].': '.$e->getMessage()); - } - continue; - } - } + $this->addPackage($package); } } /** - * @param array $data - * @return string + * Builds MemoryPackages from PEAR package definition data. + * + * @param ChannelInfo $channelInfo + * @return MemoryPackage */ - private function parseVersion(array $data) + private function buildComposerPackages(ChannelInfo $channelInfo) { - if (!isset($data['min']) && !isset($data['max'])) { - return '*'; - } - $versions = array(); - if (isset($data['min'])) { - $versions[] = '>=' . $data['min']; - } - if (isset($data['max'])) { - $versions[] = '<=' . $data['max']; + $versionParser = new \Composer\Package\Version\VersionParser(); + $result = array(); + foreach ($channelInfo->getPackages() as $packageDefinition) { + foreach ($packageDefinition->getReleases() as $version => $releaseInfo) { + $normalizedVersion = $this->parseVersion($version); + if (false === $normalizedVersion) { + continue; // skip packages with unparsable versions + } + + $composerPackageName = $this->buildComposerPackageName($packageDefinition->getChannelName(), $packageDefinition->getPackageName()); + + // distribution url must be read from /r/{packageName}/{version}.xml::/r/g:text() + // but this location is 'de-facto' standard + $distUrl = "http://{$packageDefinition->getChannelName()}/get/{$packageDefinition->getPackageName()}-{$version}.tgz"; + + $requires = array(); + $suggests = array(); + $conflicts = array(); + $replaces = array(); + + // alias package only when its channel matches repository channel, + // cause we've know only repository channel alias + if ($channelInfo->getName() == $packageDefinition->getChannelName()) { + $composerPackageAlias = $this->buildComposerPackageName($channelInfo->getAlias(), $packageDefinition->getPackageName()); + $aliasConstraint = new VersionConstraint('==', $normalizedVersion); + $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); + } + + // alias package with user-specified prefix. it makes private pear channels looks like composer's. + if (!empty($this->vendorAlias)) { + $composerPackageAlias = "{$this->vendorAlias}/{$packageDefinition->getPackageName()}"; + $aliasConstraint = new VersionConstraint('==', $normalizedVersion); + $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint); + } + + foreach ($releaseInfo->getDependencyInfo()->getRequires() as $dependencyConstraint) { + $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); + $constraint = $versionParser->parseConstraints($dependencyConstraint->getConstraint()); + $link = new Link($composerPackageName, $dependencyPackageName, $constraint, $dependencyConstraint->getType(), $dependencyConstraint->getConstraint()); + switch ($dependencyConstraint->getType()) { + case 'required': + $requires[] = $link; + break; + case 'conflicts': + $conflicts[] = $link; + break; + case 'replaces': + $replaces[] = $link; + break; + } + } + + foreach ($releaseInfo->getDependencyInfo()->getOptionals() as $group => $dependencyConstraints) { + foreach ($dependencyConstraints as $dependencyConstraint) { + $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName()); + $suggests[$group.'-'.$dependencyPackageName] = $dependencyConstraint->getConstraint(); + } + } + + $package = new MemoryPackage($composerPackageName, $normalizedVersion, $version); + $package->setType('library'); + $package->setDescription($packageDefinition->getDescription()); + $package->setDistType('pear'); + $package->setDistUrl($distUrl); + $package->setAutoload(array('classmap' => array(''))); + $package->setIncludePaths(array('/')); + $package->setRequires($requires); + $package->setConflicts($conflicts); + $package->setSuggests($suggests); + $package->setReplaces($replaces); + $result[] = $package; + } } - return implode(',', $versions); + return $result; } - /** - * @todo Improve dependencies resolution of pear packages. - * @param array $depsOptions - * @return array - */ - private function parseDependenciesOptions(array $depsOptions) + private function buildComposerPackageName($pearChannelName, $pearPackageName) { - $data = array(); - foreach ($depsOptions as $name => $options) { - // make sure single deps are wrapped in an array - if (isset($options['name'])) { - $options = array($options); - } - if ('php' == $name) { - $data[$name] = $this->parseVersion($options); - } elseif ('package' == $name) { - foreach ($options as $key => $value) { - if (isset($value['providesextension'])) { - // skip PECL dependencies - continue; - } - if (isset($value['uri'])) { - // skip uri-based dependencies - continue; - } - - if (is_array($value)) { - $dataKey = $value['name']; - if (false === strpos($dataKey, '/')) { - $dataKey = $this->getChannelShorthand($value['channel']).'/'.$dataKey; - } - $data['pear-'.$dataKey] = $this->parseVersion($value); - } - } - } elseif ('extension' == $name) { - foreach ($options as $key => $value) { - $dataKey = 'ext-' . $value['name']; - $data[$dataKey] = $this->parseVersion($value); - } - } - } - - return $data; - } - - /** - * @param string $deps - * @return array - * @throws \InvalidArgumentException - */ - private function parseDependencies($deps) - { - if (preg_match('((O:([0-9])+:"([^"]+)"))', $deps, $matches)) { - if (strlen($matches[3]) == $matches[2]) { - throw new \InvalidArgumentException("Invalid dependency data, it contains serialized objects."); - } - } - $deps = (array) @unserialize($deps); - unset($deps['required']['pearinstaller']); - - $depsData = array(); - if (!empty($deps['required'])) { - $depsData['require'] = $this->parseDependenciesOptions($deps['required']); - } - - if (!empty($deps['optional'])) { - $depsData['suggest'] = $this->parseDependenciesOptions($deps['optional']); - } - - return $depsData; - } - - /** - * @param string $packagesLink - * @return void - * @throws \InvalidArgumentException - */ - private function fetchPear2Packages($packagesLink) - { - $loader = new ArrayLoader(); - $packagesXml = $this->requestXml($packagesLink); - - $informations = $packagesXml->getElementsByTagName('pi'); - foreach ($informations as $information) { - $package = $information->getElementsByTagName('p')->item(0); - - $packageName = $package->getElementsByTagName('n')->item(0)->nodeValue; - $fullName = 'pear-'.$this->channel.'/'.$packageName; - $packageData = array( - 'name' => $fullName, - 'type' => 'library', - 'autoload' => array( - 'classmap' => array(''), - ), - 'include-path' => array('/'), - ); - $packageKeys = array('l' => 'license', 'd' => 'description'); - foreach ($packageKeys as $pear => $composer) { - if ($package->getElementsByTagName($pear)->length > 0 - && ($pear = $package->getElementsByTagName($pear)->item(0)->nodeValue)) { - $packageData[$composer] = $pear; - } - } - - $depsData = array(); - foreach ($information->getElementsByTagName('deps') as $depElement) { - $depsVersion = $depElement->getElementsByTagName('v')->item(0)->nodeValue; - $depsData[$depsVersion] = $this->parseDependencies( - $depElement->getElementsByTagName('d')->item(0)->nodeValue - ); - } - - $releases = $information->getElementsByTagName('a')->item(0); - if (!$releases) { - continue; - } - - $releases = $releases->getElementsByTagName('r'); - $packageUrl = $this->url . '/get/' . $packageName; - foreach ($releases as $release) { - $version = $release->getElementsByTagName('v')->item(0)->nodeValue; - $releaseData = array( - 'dist' => array( - 'type' => 'pear', - 'url' => $packageUrl . '-' . $version . '.tgz' - ), - 'version' => $version - ); - if (isset($depsData[$version])) { - $releaseData += $depsData[$version]; - } - - $package = $packageData + $releaseData; - try { - $this->addPackage($loader->load($package)); - if ($this->io->isVerbose()) { - $this->io->write('Loaded '.$package['name'].' '.$package['version']); - } - } catch (\UnexpectedValueException $e) { - if ($this->io->isVerbose()) { - $this->io->write('Could not load '.$package['name'].' '.$package['version'].': '.$e->getMessage()); - } - continue; - } - } + if ($pearChannelName == 'php') { + return "php"; + } elseif ($pearChannelName == 'ext') { + return "ext-{$pearPackageName}"; + } else { + return "pear-{$pearChannelName}/{$pearPackageName}"; } } - /** - * @param string $url - * @return \DOMDocument - */ - private function requestXml($url) + protected function parseVersion($version) { - $content = $this->rfs->getContents($this->url, $url, false); - if (!$content) { - throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.'); + 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; + } else { + return false; } - $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->loadXML($content); - - return $dom; - } - - private function getChannelShorthand($url) - { - if (!isset(self::$channelNames[$url])) { - try { - $channelXML = $this->requestXml('http://'.$url."/channel.xml"); - $shorthand = $channelXML->getElementsByTagName("suggestedalias")->item(0)->nodeValue - ?: $channelXML->getElementsByTagName("name")->item(0)->nodeValue; - self::$channelNames[$url] = $shorthand; - } catch (\Exception $e) { - self::$channelNames[$url] = substr($url, 0, strpos($url, '.')); - } - } - - return self::$channelNames[$url]; } } diff --git a/tests/Composer/Test/Mock/RemoteFilesystemMock.php b/tests/Composer/Test/Mock/RemoteFilesystemMock.php index 118232359..444c557e2 100644 --- a/tests/Composer/Test/Mock/RemoteFilesystemMock.php +++ b/tests/Composer/Test/Mock/RemoteFilesystemMock.php @@ -13,6 +13,7 @@ namespace Composer\Test\Mock; use Composer\Util\RemoteFilesystem; +use Composer\Downloader\TransportException; /** * Remote filesystem mock @@ -29,10 +30,11 @@ class RemoteFilesystemMock extends RemoteFilesystem public function getContents($originUrl, $fileUrl, $progress = true) { - if(!empty($this->contentMap[$fileUrl])) + if (!empty($this->contentMap[$fileUrl])) { return $this->contentMap[$fileUrl]; + } - throw new \Composer\Downloader\TransportException('The "'.$fileUrl.'" file could not be downloaded (NOT FOUND)', 404); + throw new TransportException('The "'.$fileUrl.'" file could not be downloaded (NOT FOUND)', 404); } } diff --git a/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php new file mode 100644 index 000000000..a5f77e02d --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/ChannelReaderTest.php @@ -0,0 +1,144 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Test\TestCase; +use Composer\Package\LinkConstraint\VersionConstraint; +use Composer\Package\Link; +use Composer\Package\MemoryPackage; +use Composer\Test\Mock\RemoteFilesystemMock; + +class ChannelReaderTest extends TestCase +{ + public function testShouldBuildPackagesFromPearSchema() + { + $rfs = new RemoteFilesystemMock(array( + 'http://pear.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), + 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), + 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), + )); + + $reader = new \Composer\Repository\Pear\ChannelReader($rfs); + + $channelInfo = $reader->read('http://pear.net/'); + $packages = $channelInfo->getPackages(); + + $this->assertCount(3, $packages); + $this->assertEquals('HTTP_Client', $packages[0]->getPackageName()); + $this->assertEquals('HTTP_Request', $packages[1]->getPackageName()); + $this->assertEquals('MDB2', $packages[2]->getPackageName()); + + $mdb2releases = $packages[2]->getReleases(); + $this->assertEquals(9, count($mdb2releases['2.4.0']->getDependencyInfo()->getOptionals())); + } + + public function testShouldSelectCorrectReader() + { + $rfs = new RemoteFilesystemMock(array( + 'http://pear.1.0.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.0.xml'), + 'http://test.loc/rest10/p/packages.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/packages.xml'), + 'http://test.loc/rest10/p/http_client/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_info.xml'), + 'http://test.loc/rest10/p/http_request/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_info.xml'), + 'http://pear.1.1.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), + 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), + 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), + )); + + $reader = new \Composer\Repository\Pear\ChannelReader($rfs); + + $reader->read('http://pear.1.0.net/'); + $reader->read('http://pear.1.1.net/'); + } + + public function testShouldCreatePackages() + { + $reader = $this->getMockBuilder('\Composer\Repository\Pear\ChannelReader') + ->disableOriginalConstructor() + ->getMock(); + + $ref = new \ReflectionMethod($reader, 'buildComposerPackages'); + $ref->setAccessible(true); + + $packageInfo = new PackageInfo( + 'test.loc', + 'sample', + 'license', + 'shortDescription', + 'description', + array( + '1.0.0.1' => new ReleaseInfo( + 'stable', + new DependencyInfo( + array( + new DependencyConstraint( + 'required', + '> 5.2.0.0', + 'php', + '' + ), + new DependencyConstraint( + 'conflicts', + '== 2.5.6.0', + 'pear.php.net', + 'broken' + ), + ), + array( + '*' => array( + new DependencyConstraint( + 'optional', + '*', + 'ext', + 'xml' + ), + ) + ) + ) + ) + ) + ); + + $packages = $ref->invoke($reader, 'test.loc', 'test', array($packageInfo)); + + $expectedPackage = new MemoryPackage('pear-test.loc/sample', '1.0.0.1' , '1.0.0.1'); + $expectedPackage->setType('library'); + $expectedPackage->setDistType('pear'); + $expectedPackage->setDescription('description'); + $expectedPackage->setDistUrl("http://test.loc/get/sample-1.0.0.1.tgz"); + $expectedPackage->setAutoload(array('classmap' => array(''))); + $expectedPackage->setIncludePaths(array('/')); + $expectedPackage->setRequires(array( + new Link('pear-test.loc/sample', 'php', $this->createConstraint('>', '5.2.0.0'), 'required', '> 5.2.0.0'), + )); + $expectedPackage->setConflicts(array( + new Link('pear-test.loc/sample', 'pear-pear.php.net/broken', $this->createConstraint('==', '2.5.6.0'), 'conflicts', '== 2.5.6.0'), + )); + $expectedPackage->setSuggests(array( + '*-ext-xml' => '*', + )); + $expectedPackage->setReplaces(array( + new Link('pear-test.loc/sample', 'pear-test/sample', new VersionConstraint('==', '1.0.0.1'), 'replaces', '== 1.0.0.1'), + )); + + $this->assertCount(1, $packages); + $this->assertEquals($expectedPackage, $packages[0], 0, 1); + } + + private function createConstraint($operator, $version) + { + $constraint = new VersionConstraint($operator, $version); + $constraint->setPrettyString($operator.' '.$version); + + return $constraint; + } +} diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php new file mode 100644 index 000000000..ac4f377be --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/ChannelRest10ReaderTest.php @@ -0,0 +1,41 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Test\TestCase; +use Composer\Test\Mock\RemoteFilesystemMock; + +class ChannelRest10ReaderTest extends TestCase +{ + public function testShouldBuildPackagesFromPearSchema() + { + $rfs = new RemoteFilesystemMock(array( + 'http://test.loc/rest10/p/packages.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/packages.xml'), + 'http://test.loc/rest10/p/http_client/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_info.xml'), + 'http://test.loc/rest10/r/http_client/allreleases.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_allreleases.xml'), + 'http://test.loc/rest10/r/http_client/deps.1.2.1.txt' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_client_deps.1.2.1.txt'), + 'http://test.loc/rest10/p/http_request/info.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_info.xml'), + 'http://test.loc/rest10/r/http_request/allreleases.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_allreleases.xml'), + 'http://test.loc/rest10/r/http_request/deps.1.4.0.txt' => file_get_contents(__DIR__ . '/Fixtures/Rest1.0/http_request_deps.1.4.0.txt'), + )); + + $reader = new \Composer\Repository\Pear\ChannelRest10Reader($rfs); + + /** @var $packages \Composer\Package\PackageInterface[] */ + $packages = $reader->read('http://test.loc/rest10'); + + $this->assertCount(2, $packages); + $this->assertEquals('HTTP_Client', $packages[0]->getPackageName()); + $this->assertEquals('HTTP_Request', $packages[1]->getPackageName()); + } +} diff --git a/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php new file mode 100644 index 000000000..58105a5eb --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/ChannelRest11ReaderTest.php @@ -0,0 +1,37 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Test\TestCase; +use Composer\Test\Mock\RemoteFilesystemMock; + +class ChannelRest11ReaderTest extends TestCase +{ + public function testShouldBuildPackagesFromPearSchema() + { + $rfs = new RemoteFilesystemMock(array( + 'http://pear.1.1.net/channel.xml' => file_get_contents(__DIR__ . '/Fixtures/channel.1.1.xml'), + 'http://test.loc/rest11/c/categories.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/categories.xml'), + 'http://test.loc/rest11/c/Default/packagesinfo.xml' => file_get_contents(__DIR__ . '/Fixtures/Rest1.1/packagesinfo.xml'), + )); + + $reader = new \Composer\Repository\Pear\ChannelRest11Reader($rfs); + + /** @var $packages \Composer\Package\PackageInterface[] */ + $packages = $reader->read('http://test.loc/rest11'); + + $this->assertCount(3, $packages); + $this->assertEquals('HTTP_Client', $packages[0]->getPackageName()); + $this->assertEquals('HTTP_Request', $packages[1]->getPackageName()); + } +} diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/DependencyParserTestData.json b/tests/Composer/Test/Repository/Pear/Fixtures/DependencyParserTestData.json new file mode 100644 index 000000000..e87ba40de --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/DependencyParserTestData.json @@ -0,0 +1,167 @@ +[ + { + "expected": [ + { + "type" : "required", + "constraint" : "*", + "channel" : "pear.php.net", + "name" : "Foo" + } + ], + "1.0": [ + { "type": "pkg", "rel": "has", "name": "Foo" } + ], + "2.0": { + "required": { + "package": { + "name": "Foo", + "channel": "pear.php.net" + } + } + } + }, + { + "expected": [ + { + "type" : "required", + "constraint" : ">1.0.0.0", + "channel" : "pear.php.net", + "name" : "Foo" + } + ], + "1.0": [ + { "type": "pkg", "rel": "gt", "version": "1.0.0", "name": "Foo" } + ], + "2.0": { + "required": { + "package": { + "name": "Foo", + "channel": "pear.php.net", + "min": "1.0.0", + "exclude": "1.0.0" + } + } + } + }, + { + "expected": [ + { + "type" : "conflicts", + "constraint" : "*", + "channel" : "pear.php.net", + "name" : "Foo" + } + ], + "1.0": [ + { "type": "pkg", "rel": "not", "name": "Foo" } + ], + "2.0": { + "required": { + "package": { + "name": "Foo", + "channel": "pear.php.net", + "conflicts": true + } + } + } + }, + { + "expected": [ + { + "type" : "required", + "constraint" : ">=1.0.0.0", + "channel" : "pear.php.net", + "name" : "Foo" + }, + { + "type" : "required", + "constraint" : "<2.0.0.0", + "channel" : "pear.php.net", + "name" : "Foo" + } + ], + "1.0": [ + { "type": "pkg", "rel": "ge", "version": "1.0.0", "name": "Foo" }, + { "type": "pkg", "rel": "lt", "version": "2.0.0", "name": "Foo" } + ], + "2.0": { + "required": { + "package": [ + { + "name": "Foo", + "channel": "pear.php.net", + "min": "1.0.0" + }, + { + "name": "Foo", + "channel": "pear.php.net", + "max": "2.0.0", + "exclude": "2.0.0" + } + ] + } + } + }, + { + "expected": [ + { + "type" : "required", + "constraint" : ">=5.3.0.0", + "channel" : "php", + "name" : "" + } + ], + "1.0": [ + { "type": "php", "rel": "ge", "version": "5.3"} + ], + "2.0": { + "required": { + "php": { + "min": "5.3" + } + } + } + }, + { + "expected": [ + { + "type" : "required", + "constraint" : "*", + "channel" : "ext", + "name" : "xmllib" + } + ], + "1.0": [ + { "type": "ext", "rel": "has", "name": "xmllib"} + ], + "2.0": { + "required": { + "extension": [ + { + "name": "xmllib" + } + ] + } + } + }, + { + "expected": [ + { + "type" : "optional", + "constraint" : "*", + "channel" : "ext", + "name" : "xmllib" + } + ], + "1.0": false, + "2.0": { + "optional": { + "extension": [ + { + "name": "xmllib" + } + ] + } + } + } +] \ No newline at end of file diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_allreleases.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_allreleases.xml new file mode 100644 index 000000000..1ce9d2f85 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_allreleases.xml @@ -0,0 +1,9 @@ + + +

HTTP_Client

+ pear.net + + 1.2.1 + stable + +
diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_deps.1.2.1.txt b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_deps.1.2.1.txt new file mode 100644 index 000000000..db0effb7d --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_deps.1.2.1.txt @@ -0,0 +1 @@ +a:1:{s:8:"required";a:3:{s:3:"php";a:1:{s:3:"min";s:5:"4.3.0";}s:13:"pearinstaller";a:1:{s:3:"min";s:5:"1.4.3";}s:7:"package";a:3:{s:4:"name";s:12:"HTTP_Request";s:7:"channel";s:8:"pear.net";s:3:"min";s:5:"1.4.0";}}} \ No newline at end of file diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_info.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_info.xml new file mode 100644 index 000000000..2197591b0 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_client_info.xml @@ -0,0 +1,14 @@ + +

+ HTTP_Client + pear.net + Default + BSD + + Easy way to perform multiple HTTP requests and process their results + + + The HTTP_Client class wraps around HTTP_Request and provides a higher level interface for performing multiple HTTP requests. Features: * Manages cookies and referrers between requests * Handles HTTP redirection * Has methods to set default headers and request parameters * Implements the Subject-Observer design pattern: the base class sends events to listeners that do the response processing. + + +

diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_allreleases.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_allreleases.xml new file mode 100644 index 000000000..b6516b743 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_allreleases.xml @@ -0,0 +1,9 @@ + + +

HTTP_Request

+ pear.net + + 1.4.0 + stable + +
diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_deps.1.4.0.txt b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_deps.1.4.0.txt new file mode 100644 index 000000000..da7412d09 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_deps.1.4.0.txt @@ -0,0 +1 @@ +a:1:{s:8:"required";a:3:{s:3:"php";a:1:{s:3:"min";s:5:"4.0.0";}s:13:"pearinstaller";a:1:{s:3:"min";s:7:"1.4.0b1";}s:7:"package";a:2:{i:0;a:3:{s:4:"name";s:7:"Net_URL";s:7:"channel";s:12:"pear.dev.loc";s:3:"min";s:6:"1.0.12";}i:1;a:3:{s:4:"name";s:10:"Net_Socket";s:7:"channel";s:8:"pear.net";s:3:"min";s:5:"1.0.2";}}}} \ No newline at end of file diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_info.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_info.xml new file mode 100644 index 000000000..8af662792 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/http_request_info.xml @@ -0,0 +1,12 @@ + +

+ HTTP_Request + pear.net + Default + BSD + Provides an easy way to perform HTTP requests + + Supports GET/POST/HEAD/TRACE/PUT/DELETE, Basic authentication, Proxy, Proxy Authentication, SSL, file uploads etc. + + +

diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/packages.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/packages.xml new file mode 100644 index 000000000..c38497ecd --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.0/packages.xml @@ -0,0 +1,6 @@ + + + pear.net +

HTTP_Client

+

HTTP_Request

+
diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/categories.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/categories.xml new file mode 100644 index 000000000..934c12655 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/categories.xml @@ -0,0 +1,5 @@ + + + pear.net + Default + diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/packagesinfo.xml b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/packagesinfo.xml new file mode 100644 index 000000000..889ba9ace --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/Rest1.1/packagesinfo.xml @@ -0,0 +1,97 @@ + + + +

+ HTTP_Client + pear.net + Default + BSD + + Easy way to perform multiple HTTP requests and process their results + + + The HTTP_Client class wraps around HTTP_Request and provides a higher level interface for performing multiple HTTP requests. Features: * Manages cookies and referrers between requests * Handles HTTP redirection * Has methods to set default headers and request parameters * Implements the Subject-Observer design pattern: the base class sends events to listeners that do the response processing. + + +

+ + + 1.2.1 + stable + + + + 1.2.1 + a:1:{s:8:"required";a:3:{s:3:"php";a:1:{s:3:"min";s:5:"4.3.0";}s:13:"pearinstaller";a:1:{s:3:"min";s:5:"1.4.3";}s:7:"package";a:3:{s:4:"name";s:12:"HTTP_Request";s:7:"channel";s:8:"pear.net";s:3:"min";s:5:"1.4.0";}}} + +
+ +

+ HTTP_Request + pear.net + Default + BSD + Provides an easy way to perform HTTP requests + + Supports GET/POST/HEAD/TRACE/PUT/DELETE, Basic authentication, Proxy, Proxy Authentication, SSL, file uploads etc. + + +

+ + + 1.4.0 + stable + + + + 1.4.0 + a:1:{s:8:"required";a:3:{s:3:"php";a:1:{s:3:"min";s:5:"4.0.0";}s:13:"pearinstaller";a:1:{s:3:"min";s:7:"1.4.0b1";}s:7:"package";a:2:{i:0;a:3:{s:4:"name";s:7:"Net_URL";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:6:"1.0.12";}i:1;a:3:{s:4:"name";s:10:"Net_Socket";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.0.2";}}}} + +
+ +

MDB2 + pear.net + Database + BSD License + database abstraction layer + PEAR MDB2 is a merge of the PEAR DB and Metabase php database abstraction layers. + + It provides a common API for all supported RDBMS. The main difference to most + other DB abstraction packages is that MDB2 goes much further to ensure + portability. MDB2 provides most of its many features optionally that + can be used to construct portable SQL statements: + * Object-Oriented API + * A DSN (data source name) or array format for specifying database servers + * Datatype abstraction and on demand datatype conversion + * Various optional fetch modes to fix portability issues + * Portable error codes + * Sequential and non sequential row fetching as well as bulk fetching + * Ability to make buffered and unbuffered queries + * Ordered array and associative array for the fetched rows + * Prepare/execute (bind) named and unnamed placeholder emulation + * Sequence/autoincrement emulation + * Replace emulation + * Limited sub select emulation + * Row limit emulation + * Transactions/savepoint support + * Large Object support + * Index/Unique Key/Primary Key support + * Pattern matching abstraction + * Module framework to load advanced functionality on demand + * Ability to read the information schema + * RDBMS management methods (creating, dropping, altering) + * Reverse engineering schemas from an existing database + * SQL function call abstraction + * Full integration into the PEAR Framework + * PHPDoc API documentation + +

+ + 2.4.0stable + + + 2.4.0 + a:2:{s:8:"required";a:3:{s:3:"php";a:1:{s:3:"min";s:5:"4.3.2";}s:13:"pearinstaller";a:1:{s:3:"min";s:7:"1.4.0b1";}s:7:"package";a:3:{s:4:"name";s:4:"PEAR";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.3.6";}}s:5:"group";a:9:{i:0;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:29:"Frontbase SQL driver for MDB2";s:4:"name";s:5:"fbsql";}s:10:"subpackage";a:3:{s:4:"name";s:17:"MDB2_Driver_fbsql";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"0.3.0";}}i:1;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:34:"Interbase/Firebird driver for MDB2";s:4:"name";s:5:"ibase";}s:10:"subpackage";a:3:{s:4:"name";s:17:"MDB2_Driver_ibase";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}i:2;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:21:"MySQL driver for MDB2";s:4:"name";s:5:"mysql";}s:10:"subpackage";a:3:{s:4:"name";s:17:"MDB2_Driver_mysql";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}i:3;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:22:"MySQLi driver for MDB2";s:4:"name";s:6:"mysqli";}s:10:"subpackage";a:3:{s:4:"name";s:18:"MDB2_Driver_mysqli";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}i:4;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:29:"MS SQL Server driver for MDB2";s:4:"name";s:5:"mssql";}s:10:"subpackage";a:3:{s:4:"name";s:17:"MDB2_Driver_mssql";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.2.0";}}i:5;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:22:"Oracle driver for MDB2";s:4:"name";s:4:"oci8";}s:10:"subpackage";a:3:{s:4:"name";s:16:"MDB2_Driver_oci8";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}i:6;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:26:"PostgreSQL driver for MDB2";s:4:"name";s:5:"pgsql";}s:10:"subpackage";a:3:{s:4:"name";s:17:"MDB2_Driver_pgsql";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}i:7;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:24:"Querysim driver for MDB2";s:4:"name";s:8:"querysim";}s:10:"subpackage";a:3:{s:4:"name";s:20:"MDB2_Driver_querysim";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"0.6.0";}}i:8;a:2:{s:7:"attribs";a:2:{s:4:"hint";s:23:"SQLite2 driver for MDB2";s:4:"name";s:6:"sqlite";}s:10:"subpackage";a:3:{s:4:"name";s:18:"MDB2_Driver_sqlite";s:7:"channel";s:12:"pear.php.net";s:3:"min";s:5:"1.4.0";}}}} + +
+
diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.0.xml b/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.0.xml new file mode 100644 index 000000000..dc3c9e8bb --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.0.xml @@ -0,0 +1,12 @@ + + pear.net + Test PEAR channel + test_alias + + + + http://test.loc/rest10/ + + + + diff --git a/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.1.xml b/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.1.xml new file mode 100644 index 000000000..bf1e55d68 --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/Fixtures/channel.1.1.xml @@ -0,0 +1,12 @@ + + pear.net + Test PEAR channel + test_alias + + + + http://test.loc/rest11/ + + + + diff --git a/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php b/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php new file mode 100644 index 000000000..959fe2a9e --- /dev/null +++ b/tests/Composer/Test/Repository/Pear/PackageDependencyParserTest.php @@ -0,0 +1,58 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Pear; + +use Composer\Test\TestCase; + +class PackageDependencyParserTest extends TestCase +{ + /** + * @dataProvider dataProvider10 + * @param $expected + * @param $data + */ + public function testShouldParseDependencies($expected, $data10, $data20) + { + $expectedDependencies = array(); + foreach ($expected as $expectedItem) { + $expectedDependencies[] = new DependencyConstraint( + $expectedItem['type'], + $expectedItem['constraint'], + $expectedItem['channel'], + $expectedItem['name'] + ); + } + + $parser = new PackageDependencyParser(); + + if (false !== $data10) { + $result = $parser->buildDependencyInfo($data10); + $this->assertEquals($expectedDependencies, $result->getRequires() + $result->getOptionals(), "Failed for package.xml 1.0 format"); + } + + if (false !== $data20) { + $result = $parser->buildDependencyInfo($data20); + $this->assertEquals($expectedDependencies, $result->getRequires() + $result->getOptionals(), "Failed for package.xml 2.0 format"); + } + } + + public function dataProvider10() + { + $data = json_decode(file_get_contents(__DIR__.'/Fixtures/DependencyParserTestData.json'), true); + if (0 !== json_last_error()) { + throw new \PHPUnit_Framework_Exception('Invalid json file.'); + } + + return $data; + } +} diff --git a/tests/Composer/Test/Repository/PearRepositoryTest.php b/tests/Composer/Test/Repository/PearRepositoryTest.php index 7021c8615..c14682553 100644 --- a/tests/Composer/Test/Repository/PearRepositoryTest.php +++ b/tests/Composer/Test/Repository/PearRepositoryTest.php @@ -29,11 +29,11 @@ class PearRepositoryTest extends TestCase */ private $remoteFilesystem; - public function testComposerNonCompatibleRepositoryShouldSetIncludePath() + public function testComposerShouldSetIncludePath() { $url = 'pear.phpmd.org'; $expectedPackages = array( - array('name' => 'pear-phpmd/PHP_PMD', 'version' => '1.3.3'), + array('name' => 'pear-pear.phpmd.org/PHP_PMD', 'version' => '1.3.3'), ); $repoConfig = array( @@ -78,53 +78,47 @@ class PearRepositoryTest extends TestCase public function repositoryDataProvider() { return array( - array( + array( 'pear.phpunit.de', array( - array('name' => 'pear-phpunit/PHPUnit_MockObject', 'version' => '1.1.1'), - array('name' => 'pear-phpunit/PHPUnit', 'version' => '3.6.10'), + array('name' => 'pear-pear.phpunit.de/PHPUnit_MockObject', 'version' => '1.1.1'), + array('name' => 'pear-pear.phpunit.de/PHPUnit', 'version' => '3.6.10'), ) ), array( 'pear.php.net', array( - array('name' => 'pear-pear/PEAR', 'version' => '1.9.4'), + array('name' => 'pear-pear.php.net/PEAR', 'version' => '1.9.4'), ) ), array( 'pear.pdepend.org', array( - array('name' => 'pear-pdepend/PHP_Depend', 'version' => '1.0.5'), + array('name' => 'pear-pear.pdepend.org/PHP_Depend', 'version' => '1.0.5'), ) ), array( 'pear.phpmd.org', array( - array('name' => 'pear-phpmd/PHP_PMD', 'version' => '1.3.3'), + array('name' => 'pear-pear.phpmd.org/PHP_PMD', 'version' => '1.3.3'), ) ), array( 'pear.doctrine-project.org', array( - array('name' => 'pear-doctrine/DoctrineORM', 'version' => '2.2.2'), + array('name' => 'pear-pear.doctrine-project.org/DoctrineORM', 'version' => '2.2.2'), ) ), array( 'pear.symfony-project.com', array( - array('name' => 'pear-symfony/YAML', 'version' => '1.0.6'), + array('name' => 'pear-pear.symfony-project.com/YAML', 'version' => '1.0.6'), ) ), array( 'pear.pirum-project.org', array( - array('name' => 'pear-pirum/Pirum', 'version' => '1.1.4'), - ) - ), - array( - 'packages.zendframework.com', - array( - array('name' => 'pear-zf2/Zend_Code', 'version' => '2.0.0.0-beta3'), + array('name' => 'pear-pear.pirum-project.org/Pirum', 'version' => '1.1.4'), ) ), );