Browse Source

Merge pull request #1015 from Seldaek/memory

Reduce memory usage by only loading packages that are actually needed, fixes #456
Nils Adermann 12 years ago
parent
commit
01593e0628
41 changed files with 1260 additions and 702 deletions
  1. 28 3
      src/Composer/Command/CreateProjectCommand.php
  2. 18 49
      src/Composer/Command/DependsCommand.php
  3. 5 6
      src/Composer/Command/InitCommand.php
  4. 45 47
      src/Composer/Command/SearchCommand.php
  5. 62 43
      src/Composer/Command/ShowCommand.php
  6. 5 5
      src/Composer/Composer.php
  7. 203 30
      src/Composer/DependencyResolver/Pool.php
  8. 51 24
      src/Composer/Installer.php
  9. 1 1
      src/Composer/Package/AliasPackage.php
  10. 0 37
      src/Composer/Package/BasePackage.php
  11. 174 0
      src/Composer/Package/CompletePackage.php
  12. 81 0
      src/Composer/Package/CompletePackageInterface.php
  13. 27 12
      src/Composer/Package/Dumper/ArrayDumper.php
  14. 82 73
      src/Composer/Package/Loader/ArrayLoader.php
  15. 2 1
      src/Composer/Package/Loader/LoaderInterface.php
  16. 2 2
      src/Composer/Package/Loader/RootPackageLoader.php
  17. 2 2
      src/Composer/Package/Loader/ValidatingArrayLoader.php
  18. 12 1
      src/Composer/Package/Locker.php
  19. 9 216
      src/Composer/Package/Package.php
  20. 9 78
      src/Composer/Package/PackageInterface.php
  21. 81 0
      src/Composer/Package/RootPackage.php
  22. 46 0
      src/Composer/Package/RootPackageInterface.php
  23. 23 0
      src/Composer/Package/Version/VersionParser.php
  24. 14 0
      src/Composer/Repository/ArrayRepository.php
  25. 115 8
      src/Composer/Repository/ComposerRepository.php
  26. 14 0
      src/Composer/Repository/CompositeRepository.php
  27. 1 1
      src/Composer/Repository/Pear/ChannelReader.php
  28. 4 4
      src/Composer/Repository/PearRepository.php
  29. 4 4
      src/Composer/Repository/PlatformRepository.php
  30. 14 0
      src/Composer/Repository/RepositoryInterface.php
  31. 61 0
      src/Composer/Repository/StreamableRepositoryInterface.php
  32. 29 29
      tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  33. 1 1
      tests/Composer/Test/ComposerTest.php
  34. 14 4
      tests/Composer/Test/Fixtures/installer/update-alias-lock.test
  35. 1 1
      tests/Composer/Test/Installer/LibraryInstallerTest.php
  36. 1 1
      tests/Composer/Test/Installer/MetapackageInstallerTest.php
  37. 4 4
      tests/Composer/Test/InstallerTest.php
  38. 8 8
      tests/Composer/Test/Package/CompletePackageTest.php
  39. 2 2
      tests/Composer/Test/Package/Dumper/ArrayDumperTest.php
  40. 2 2
      tests/Composer/Test/Repository/Pear/ChannelReaderTest.php
  41. 3 3
      tests/Composer/Test/TestCase.php

+ 28 - 3
src/Composer/Command/CreateProjectCommand.php

@@ -87,7 +87,7 @@ EOT
         );
     }
 
-    public function installProject(IOInterface $io, $packageName, $directory = null, $version = null, $preferSource = false, $installDevPackages = false, $repositoryUrl = null, $disableCustomInstallers = false, $noScripts = false)
+    public function installProject(IOInterface $io, $packageName, $directory = null, $packageVersion = null, $preferSource = false, $installDevPackages = false, $repositoryUrl = null, $disableCustomInstallers = false, $noScripts = false)
     {
         $dm = $this->createDownloadManager($io);
         if ($preferSource) {
@@ -105,9 +105,29 @@ EOT
             throw new \InvalidArgumentException("Invalid repository url given. Has to be a .json file or an http url.");
         }
 
-        $candidates = $sourceRepo->findPackages($packageName, $version);
+        $candidates = array();
+        $name = strtolower($packageName);
+
+        if ($packageVersion === null) {
+            $sourceRepo->filterPackages(function ($package) use (&$candidates, $name) {
+                if ($package->getName() === $name) {
+                    $candidates[] = $package;
+                }
+            });
+        } else {
+            $parser = new VersionParser();
+            $version = $parser->normalize($packageVersion);
+            $sourceRepo->filterPackages(function ($package) use (&$candidates, $name, $version) {
+                if ($package->getName() === $name && $version === $package->getVersion()) {
+                    $candidates[] = $package;
+
+                    return false;
+                }
+            });
+        }
+
         if (!$candidates) {
-            throw new \InvalidArgumentException("Could not find package $packageName" . ($version ? " with version $version." : ''));
+            throw new \InvalidArgumentException("Could not find package $packageName" . ($packageVersion ? " with version $packageVersion." : ''));
         }
 
         if (null === $directory) {
@@ -122,6 +142,7 @@ EOT
                 $package = $candidate;
             }
         }
+        unset($candidates);
 
         $io->write('<info>Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')</info>', true);
 
@@ -144,6 +165,10 @@ EOT
 
         putenv('COMPOSER_ROOT_VERSION='.$package->getPrettyVersion());
 
+        // clean up memory
+        unset($dm, $config, $projectInstaller, $sourceRepo, $package);
+
+        // install dependencies of the created project
         $composer = Factory::create($io);
         $installer = Installer::create($io, $composer);
 

+ 18 - 49
src/Composer/Command/DependsCommand.php

@@ -51,69 +51,38 @@ EOT
     protected function execute(InputInterface $input, OutputInterface $output)
     {
         $composer = $this->getComposer();
-        $references = $this->getReferences($input, $output, $composer);
+        $repos = $composer->getRepositoryManager()->getRepositories();
 
-        if ($input->getOption('verbose')) {
-            $this->printReferences($input, $output, $references);
-        } else {
-            $this->printPackages($input, $output, $references);
-        }
-    }
+        $linkTypes = $this->linkTypes;
 
-    /**
-     * finds a list of packages which depend on another package
-     *
-     * @param  InputInterface            $input
-     * @param  OutputInterface           $output
-     * @param  Composer                  $composer
-     * @return array
-     * @throws \InvalidArgumentException
-     */
-    private function getReferences(InputInterface $input, OutputInterface $output, Composer $composer)
-    {
         $needle = $input->getArgument('package');
-
-        $references = array();
         $verbose = (bool) $input->getOption('verbose');
+        $types = array_map(function ($type) use ($linkTypes) {
+            $type = rtrim($type, 's');
+            if (!isset($linkTypes[$type])) {
+                throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', array_keys($linkTypes)));
+            }
 
-        $repos = $composer->getRepositoryManager()->getRepositories();
-        $types = $input->getOption('link-type');
+            return $type;
+        }, $input->getOption('link-type'));
+
+        foreach ($repos as $repo) {
+            $repo->filterPackages(function ($package) use ($needle, $types, $output, $verbose) {
+                static $outputPackages = array();
 
-        foreach ($repos as $repository) {
-            foreach ($repository->getPackages() as $package) {
                 foreach ($types as $type) {
-                    $type = rtrim($type, 's');
-                    if (!isset($this->linkTypes[$type])) {
-                        throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', array_keys($this->linkTypes)));
-                    }
                     foreach ($package->{'get'.$this->linkTypes[$type]}() as $link) {
                         if ($link->getTarget() === $needle) {
                             if ($verbose) {
-                                $references[] = array($type, $package, $link);
-                            } else {
-                                $references[$package->getName()] = $package->getPrettyName();
+                                $output->writeln($package->getPrettyName() . ' ' . $package->getPrettyVersion() . ' <info>' . $type . '</info> ' . $link->getPrettyConstraint());
+                            } elseif (!isset($outputPackages[$package->getName()])) {
+                                $output->writeln($package->getPrettyName());
+                                $outputPackages[$package->getName()] = true;
                             }
                         }
                     }
                 }
-            }
-        }
-
-        return $references;
-    }
-
-    private function printReferences(InputInterface $input, OutputInterface $output, array $references)
-    {
-        foreach ($references as $ref) {
-            $output->writeln($ref[1]->getPrettyName() . ' ' . $ref[1]->getPrettyVersion() . ' <info>' . $ref[0] . '</info> ' . $ref[2]->getPrettyConstraint());
-        }
-    }
-
-    private function printPackages(InputInterface $input, OutputInterface $output, array $packages)
-    {
-        ksort($packages);
-        foreach ($packages as $package) {
-            $output->writeln($package);
+            });
         }
     }
 }

+ 5 - 6
src/Composer/Command/InitCommand.php

@@ -271,13 +271,12 @@ EOT
         }
 
         $token = strtolower($name);
-        foreach ($this->repos->getPackages() as $package) {
-            if (false === ($pos = strpos($package->getName(), $token))) {
-                continue;
-            }
 
-            $packages[] = $package;
-        }
+        $this->repos->filterPackages(function ($package) use ($token, &$packages) {
+            if (false !== strpos($package->getName(), $token)) {
+                $packages[] = $package;
+            }
+        });
 
         return $packages;
     }

+ 45 - 47
src/Composer/Command/SearchCommand.php

@@ -17,7 +17,7 @@ use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Output\OutputInterface;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
-use Composer\Package\PackageInterface;
+use Composer\Package\CompletePackageInterface;
 use Composer\Package\AliasPackage;
 use Composer\Factory;
 
@@ -26,6 +26,11 @@ use Composer\Factory;
  */
 class SearchCommand extends Command
 {
+    protected $matches;
+    protected $lowMatches;
+    protected $tokens;
+    protected $output;
+
     protected function configure()
     {
         $this
@@ -58,70 +63,63 @@ EOT
             $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos));
         }
 
-        $tokens = $input->getArgument('tokens');
-        $packages = array();
+        $this->tokens = $input->getArgument('tokens');
+        $this->output = $output;
+        $repos->filterPackages(array($this, 'processPackage'), 'Composer\Package\CompletePackage');
 
-        $maxPackageLength = 0;
-        foreach ($repos->getPackages() as $package) {
-            if ($package instanceof AliasPackage || isset($packages[$package->getName()])) {
-                continue;
-            }
+        foreach ($this->lowMatches as $details) {
+            $output->writeln($details['name'] . '<comment>:</comment> '. $details['description']);
+        }
+    }
 
-            foreach ($tokens as $token) {
-                if (!$score = $this->matchPackage($package, $token)) {
-                    continue;
-                }
-
-                if (false !== ($pos = stripos($package->getName(), $token))) {
-                    $name = substr($package->getPrettyName(), 0, $pos)
-                        . '<highlight>' . substr($package->getPrettyName(), $pos, strlen($token)) . '</highlight>'
-                        . substr($package->getPrettyName(), $pos + strlen($token));
-                } else {
-                    $name = $package->getPrettyName();
-                }
-
-                $description = strtok($package->getDescription(), "\r\n");
-                if (false !== ($pos = stripos($description, $token))) {
-                    $description = substr($description, 0, $pos)
-                        . '<highlight>' . substr($description, $pos, strlen($token)) . '</highlight>'
-                        . substr($description, $pos + strlen($token));
-                }
-
-                $packages[$package->getName()] = array(
-                    'name' => $name,
-                    'description' => $description,
-                    'length' => $length = strlen($package->getPrettyName()),
-                    'score' => $score,
-                );
+    public function processPackage($package)
+    {
+        if ($package instanceof AliasPackage || isset($this->matches[$package->getName()])) {
+            return;
+        }
 
-                $maxPackageLength = max($maxPackageLength, $length);
+        foreach ($this->tokens as $token) {
+            if (!$score = $this->matchPackage($package, $token)) {
+                continue;
+            }
 
-                continue 2;
+            if (false !== ($pos = stripos($package->getName(), $token))) {
+                $name = substr($package->getPrettyName(), 0, $pos)
+                    . '<highlight>' . substr($package->getPrettyName(), $pos, strlen($token)) . '</highlight>'
+                    . substr($package->getPrettyName(), $pos + strlen($token));
+            } else {
+                $name = $package->getPrettyName();
             }
-        }
 
-        usort($packages, function ($a, $b) {
-            if ($a['score'] === $b['score']) {
-                return 0;
+            $description = strtok($package->getDescription(), "\r\n");
+            if (false !== ($pos = stripos($description, $token))) {
+                $description = substr($description, 0, $pos)
+                    . '<highlight>' . substr($description, $pos, strlen($token)) . '</highlight>'
+                    . substr($description, $pos + strlen($token));
             }
 
-            return $a['score'] > $b['score'] ? -1 : 1;
-        });
+            if ($score >= 3) {
+                $this->output->writeln($name . '<comment>:</comment> '. $description);
+                $this->matches[$package->getName()] = true;
+            } else {
+                $this->lowMatches[$package->getName()] = array(
+                    'name' => $name,
+                    'description' => $description,
+                );
+            }
 
-        foreach ($packages as $details) {
-            $extraSpaces = $maxPackageLength - $details['length'];
-            $output->writeln($details['name'] . str_repeat(' ', $extraSpaces) .' <comment>:</comment> '. $details['description']);
+            return;
         }
     }
 
     /**
      * tries to find a token within the name/keywords/description
      *
-     * @param  PackageInterface $package
+     * @param  CompletePackageInterface $package
      * @param  string           $token
      * @return boolean
      */
-    private function matchPackage(PackageInterface $package, $token)
+    private function matchPackage(CompletePackageInterface $package, $token)
     {
         $score = 0;
 

+ 62 - 43
src/Composer/Command/ShowCommand.php

@@ -14,7 +14,8 @@ namespace Composer\Command;
 
 use Composer\Composer;
 use Composer\Factory;
-use Composer\Package\PackageInterface;
+use Composer\Package\CompletePackageInterface;
+use Composer\Package\Version\VersionParser;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputOption;
@@ -38,9 +39,9 @@ class ShowCommand extends Command
             ->setDefinition(array(
                 new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect'),
                 new InputArgument('version', InputArgument::OPTIONAL, 'Version to inspect'),
-                new InputOption('installed', null, InputOption::VALUE_NONE, 'List installed packages only'),
-                new InputOption('platform', null, InputOption::VALUE_NONE, 'List platform packages only'),
-                new InputOption('self', null, InputOption::VALUE_NONE, 'Show the root package information'),
+                new InputOption('installed', 'i', InputOption::VALUE_NONE, 'List installed packages only'),
+                new InputOption('platform', 'p', InputOption::VALUE_NONE, 'List platform packages only'),
+                new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'),
             ))
             ->setHelp(<<<EOT
 The show command displays detailed information about a package, or
@@ -77,13 +78,14 @@ EOT
         // show single package or single version
         if ($input->getArgument('package') || !empty($package)) {
             if (empty($package)) {
-                $package = $this->getPackage($input, $output, $installedRepo, $repos);
-            }
-            if (!$package) {
-                throw new \InvalidArgumentException('Package '.$input->getArgument('package').' not found');
+                list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version'));
+
+                if (!$package) {
+                    throw new \InvalidArgumentException('Package '.$input->getArgument('package').' not found');
+                }
             }
 
-            $this->printMeta($input, $output, $package, $installedRepo, $repos);
+            $this->printMeta($input, $output, $package, $versions, $installedRepo, $repos);
             $this->printLinks($input, $output, $package, 'requires');
             $this->printLinks($input, $output, $package, 'devRequires', 'requires (dev)');
             if ($package->getSuggests()) {
@@ -101,7 +103,7 @@ EOT
 
         // list packages
         $packages = array();
-        foreach ($repos->getPackages() as $package) {
+        $repos->filterPackages(function ($package) use (&$packages, $platformRepo, $installedRepo) {
             if ($platformRepo->hasPackage($package)) {
                 $type = '<info>platform</info>:';
             } elseif ($installedRepo->hasPackage($package)) {
@@ -109,13 +111,12 @@ EOT
             } else {
                 $type = '<comment>available</comment>:';
             }
-            if (isset($packages[$type][$package->getName()])
-                && version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '>=')
+            if (!isset($packages[$type][$package->getName()])
+                || version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<')
             ) {
-                continue;
+                $packages[$type][$package->getName()] = $package;
             }
-            $packages[$type][$package->getName()] = $package;
-        }
+        }, 'Composer\Package\CompletePackage');
 
         foreach (array('<info>platform</info>:' => true, '<comment>available</comment>:' => false, '<info>installed</info>:' => true) as $type => $showVersion) {
             if (isset($packages[$type])) {
@@ -132,44 +133,69 @@ EOT
     /**
      * finds a package by name and version if provided
      *
-     * @param  InputInterface            $input
-     * @return PackageInterface
+     * @return array array(CompletePackageInterface, array of versions)
      * @throws \InvalidArgumentException
      */
-    protected function getPackage(InputInterface $input, OutputInterface $output, RepositoryInterface $installedRepo, RepositoryInterface $repos)
+    protected function getPackage(RepositoryInterface $installedRepo, RepositoryInterface $repos, $name, $version = null)
     {
-        // we have a name and a version so we can use ::findPackage
-        if ($input->getArgument('version')) {
-            return $repos->findPackage($input->getArgument('package'), $input->getArgument('version'));
+        $name = strtolower($name);
+        if ($version) {
+            $parser = new VersionParser();
+            $version = $parser->normalize($version);
         }
 
-        // check if we have a local installation so we can grab the right package/version
-        foreach ($installedRepo->getPackages() as $package) {
-            if ($package->getName() === $input->getArgument('package')) {
-                return $package;
+        $match = null;
+        $matches = array();
+        $repos->filterPackages(function ($package) use ($name, $version, &$matches) {
+            if ($package->getName() === $name) {
+                $matches[] = $package;
             }
-        }
+        }, 'Composer\Package\CompletePackage');
 
-        // we only have a name, so search for the highest version of the given package
-        $highestVersion = null;
-        foreach ($repos->findPackages($input->getArgument('package')) as $package) {
-            if (null === $highestVersion || version_compare($package->getVersion(), $highestVersion->getVersion(), '>=')) {
-                $highestVersion = $package;
+        if (null === $version) {
+            // search for a locally installed version
+            foreach ($matches as $package) {
+                if ($installedRepo->hasPackage($package)) {
+                    $match = $package;
+                    break;
+                }
+            }
+
+            if (!$match) {
+                // fallback to the highest version
+                foreach ($matches as $package) {
+                    if (null === $match || version_compare($package->getVersion(), $match->getVersion(), '>=')) {
+                        $match = $package;
+                    }
+                }
             }
+        } else {
+            // select the specified version
+            foreach ($matches as $package) {
+                if ($package->getVersion() === $version) {
+                    $match = $package;
+                }
+            }
+        }
+
+        // build versions array
+        $versions = array();
+        foreach ($matches as $package) {
+            $versions[$package->getPrettyVersion()] = $package->getVersion();
         }
 
-        return $highestVersion;
+        return array($match, $versions);
     }
 
     /**
      * prints package meta data
      */
-    protected function printMeta(InputInterface $input, OutputInterface $output, PackageInterface $package, RepositoryInterface $installedRepo, RepositoryInterface $repos)
+    protected function printMeta(InputInterface $input, OutputInterface $output, CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, RepositoryInterface $repos)
     {
         $output->writeln('<info>name</info>     : ' . $package->getPrettyName());
         $output->writeln('<info>descrip.</info> : ' . $package->getDescription());
         $output->writeln('<info>keywords</info> : ' . join(', ', $package->getKeywords() ?: array()));
-        $this->printVersions($input, $output, $package, $installedRepo, $repos);
+        $this->printVersions($input, $output, $package, $versions, $installedRepo, $repos);
         $output->writeln('<info>type</info>     : ' . $package->getType());
         $output->writeln('<info>license</info>  : ' . implode(', ', $package->getLicense()));
         $output->writeln('<info>source</info>   : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
@@ -206,7 +232,7 @@ EOT
     /**
      * prints all available versions of this package and highlights the installed one if any
      */
-    protected function printVersions(InputInterface $input, OutputInterface $output, PackageInterface $package, RepositoryInterface $installedRepo, RepositoryInterface $repos)
+    protected function printVersions(InputInterface $input, OutputInterface $output, CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, RepositoryInterface $repos)
     {
         if ($input->getArgument('version')) {
             $output->writeln('<info>version</info>  : ' . $package->getPrettyVersion());
@@ -214,14 +240,7 @@ EOT
             return;
         }
 
-        $versions = array();
-
-        foreach ($repos->findPackages($package->getName()) as $version) {
-            $versions[$version->getPrettyVersion()] = $version->getVersion();
-        }
-
         uasort($versions, 'version_compare');
-
         $versions = implode(', ', array_keys(array_reverse($versions)));
 
         // highlight installed version
@@ -237,7 +256,7 @@ EOT
      *
      * @param string $linkType
      */
-    protected function printLinks(InputInterface $input, OutputInterface $output, PackageInterface $package, $linkType, $title = null)
+    protected function printLinks(InputInterface $input, OutputInterface $output, CompletePackageInterface $package, $linkType, $title = null)
     {
         $title = $title ?: $linkType;
         if ($links = $package->{'get'.ucfirst($linkType)}()) {

+ 5 - 5
src/Composer/Composer.php

@@ -12,7 +12,7 @@
 
 namespace Composer;
 
-use Composer\Package\PackageInterface;
+use Composer\Package\RootPackageInterface;
 use Composer\Package\Locker;
 use Composer\Repository\RepositoryManager;
 use Composer\Installer\InstallationManager;
@@ -27,7 +27,7 @@ class Composer
     const VERSION = '@package_version@';
 
     /**
-     * @var Package\PackageInterface
+     * @var Package\RootPackageInterface
      */
     private $package;
 
@@ -57,16 +57,16 @@ class Composer
     private $config;
 
     /**
-     * @param  Package\PackageInterface $package
+     * @param  Package\RootPackageInterface $package
      * @return void
      */
-    public function setPackage(PackageInterface $package)
+    public function setPackage(RootPackageInterface $package)
     {
         $this->package = $package;
     }
 
     /**
-     * @return Package\PackageInterface
+     * @return Package\RootPackageInterface
      */
     public function getPackage()
     {

+ 203 - 30
src/Composer/DependencyResolver/Pool.php

@@ -13,10 +13,15 @@
 namespace Composer\DependencyResolver;
 
 use Composer\Package\BasePackage;
+use Composer\Package\AliasPackage;
+use Composer\Package\Version\VersionParser;
+use Composer\Package\Link;
 use Composer\Package\LinkConstraint\LinkConstraintInterface;
+use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Repository\RepositoryInterface;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\InstalledRepositoryInterface;
+use Composer\Repository\StreamableRepositoryInterface;
 use Composer\Repository\PlatformRepository;
 
 /**
@@ -27,15 +32,23 @@ use Composer\Repository\PlatformRepository;
  */
 class Pool
 {
+    const MATCH_NAME = -1;
+    const MATCH_NONE = 0;
+    const MATCH = 1;
+    const MATCH_PROVIDE = 2;
+    const MATCH_REPLACE = 3;
+
     protected $repositories = array();
     protected $packages = array();
     protected $packageByName = array();
     protected $acceptableStabilities;
     protected $stabilityFlags;
+    protected $versionParser;
 
     public function __construct($minimumStability = 'stable', array $stabilityFlags = array())
     {
         $stabilities = BasePackage::$stabilities;
+        $this->versionParser = new VersionParser;
         $this->acceptableStabilities = array();
         foreach (BasePackage::$stabilities as $stability => $value) {
             if ($value <= BasePackage::$stabilities[$minimumStability]) {
@@ -48,9 +61,10 @@ class Pool
     /**
      * Adds a repository and its packages to this package pool
      *
-     * @param RepositoryInterface $repo A package repository
+     * @param RepositoryInterface $repo        A package repository
+     * @param array               $rootAliases
      */
-    public function addRepository(RepositoryInterface $repo)
+    public function addRepository(RepositoryInterface $repo, $rootAliases = array())
     {
         if ($repo instanceof CompositeRepository) {
             $repos = $repo->getRepositories();
@@ -63,25 +77,93 @@ class Pool
             $this->repositories[] = $repo;
 
             $exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface;
-            foreach ($repo->getPackages() as $package) {
-                $name = $package->getName();
-                $stability = $package->getStability();
-                if (
-                    // always allow exempt repos
-                    $exempt
-                    // allow if package matches the global stability requirement and has no exception
-                    || (!isset($this->stabilityFlags[$name])
-                        && isset($this->acceptableStabilities[$stability]))
-                    // allow if package matches the package-specific stability flag
-                    || (isset($this->stabilityFlags[$name])
-                        && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$name]
-                    )
-                ) {
-                    $package->setId($id++);
-                    $this->packages[] = $package;
-
-                    foreach ($package->getNames() as $name) {
-                        $this->packageByName[$name][] = $package;
+            if ($repo instanceof StreamableRepositoryInterface) {
+                foreach ($repo->getMinimalPackages() as $package) {
+                    $name = $package['name'];
+                    $version = $package['version'];
+                    $stability = VersionParser::parseStability($version);
+                    if ($exempt || $this->isPackageAcceptable($name, $stability)) {
+                        $package['id'] = $id++;
+                        $this->packages[] = $package;
+
+                        // collect names
+                        $names = array(
+                            $name => true,
+                        );
+                        if (isset($package['provide'])) {
+                            foreach ($package['provide'] as $target => $constraint) {
+                                $names[$target] = true;
+                            }
+                        }
+                        if (isset($package['replace'])) {
+                            foreach ($package['replace'] as $target => $constraint) {
+                                $names[$target] = true;
+                            }
+                        }
+
+                        foreach (array_keys($names) as $name) {
+                            $this->packageByName[$name][] =& $this->packages[$id-2];
+                        }
+
+                        // handle root package aliases
+                        if (isset($rootAliases[$name][$version])) {
+                            $alias = $package;
+                            unset($alias['raw']);
+                            $alias['version'] = $rootAliases[$name][$version]['alias_normalized'];
+                            $alias['alias'] = $rootAliases[$name][$version]['alias'];
+                            $alias['alias_of'] = $package['id'];
+                            $alias['id'] = $id++;
+                            $alias['root_alias'] = true;
+                            $this->packages[] = $alias;
+
+                            foreach (array_keys($names) as $name) {
+                                $this->packageByName[$name][] =& $this->packages[$id-2];
+                            }
+                        }
+
+                        // handle normal package aliases
+                        if (isset($package['alias'])) {
+                            $alias = $package;
+                            unset($alias['raw']);
+                            $alias['version'] = $package['alias_normalized'];
+                            $alias['alias'] = $package['alias'];
+                            $alias['alias_of'] = $package['id'];
+                            $alias['id'] = $id++;
+                            $this->packages[] = $alias;
+
+                            foreach (array_keys($names) as $name) {
+                                $this->packageByName[$name][] =& $this->packages[$id-2];
+                            }
+                        }
+                    }
+                }
+            } else {
+                foreach ($repo->getPackages() as $package) {
+                    $name = $package->getName();
+                    $stability = $package->getStability();
+                    if ($exempt || $this->isPackageAcceptable($name, $stability)) {
+                        $package->setId($id++);
+                        $this->packages[] = $package;
+
+                        foreach ($package->getNames() as $name) {
+                            $this->packageByName[$name][] = $package;
+                        }
+
+                        // handle root package aliases
+                        if (isset($rootAliases[$name][$package->getVersion()])) {
+                            $alias = $rootAliases[$name][$package->getVersion()];
+                            $package->setAlias($alias['alias_normalized']);
+                            $package->setPrettyAlias($alias['alias']);
+                            $package->getRepository()->addPackage($aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
+                            $aliasPackage->setRootPackageAlias(true);
+                            $aliasPackage->setId($id++);
+
+                            $this->packages[] = $aliasPackage;
+
+                            foreach ($aliasPackage->getNames() as $name) {
+                                $this->packageByName[$name][] = $aliasPackage;
+                            }
+                        }
                     }
                 }
             }
@@ -107,6 +189,8 @@ class Pool
     */
     public function packageById($id)
     {
+        $this->ensurePackageIsLoaded($this->packages[$id - 1]);
+
         return $this->packages[$id - 1];
     }
 
@@ -137,6 +221,10 @@ class Pool
         $candidates = $this->packageByName[$name];
 
         if (null === $constraint) {
+            foreach ($candidates as $key => $candidate) {
+                $candidates[$key] = $this->ensurePackageIsLoaded($candidate);
+            }
+
             return $candidates;
         }
 
@@ -144,25 +232,25 @@ class Pool
         $nameMatch = false;
 
         foreach ($candidates as $candidate) {
-            switch ($candidate->matches($name, $constraint)) {
-                case BasePackage::MATCH_NONE:
+            switch ($this->match($candidate, $name, $constraint)) {
+                case self::MATCH_NONE:
                     break;
 
-                case BasePackage::MATCH_NAME:
+                case self::MATCH_NAME:
                     $nameMatch = true;
                     break;
 
-                case BasePackage::MATCH:
+                case self::MATCH:
                     $nameMatch = true;
-                    $matches[] = $candidate;
+                    $matches[] = $this->ensurePackageIsLoaded($candidate);
                     break;
 
-                case BasePackage::MATCH_PROVIDE:
-                    $provideMatches[] = $candidate;
+                case self::MATCH_PROVIDE:
+                    $provideMatches[] = $this->ensurePackageIsLoaded($candidate);
                     break;
 
-                case BasePackage::MATCH_REPLACE:
-                    $matches[] = $candidate;
+                case self::MATCH_REPLACE:
+                    $matches[] = $this->ensurePackageIsLoaded($candidate);
                     break;
 
                 default:
@@ -202,4 +290,89 @@ class Pool
 
         return $prefix.' '.$package->getPrettyString();
     }
+
+    private function isPackageAcceptable($name, $stability)
+    {
+        // allow if package matches the global stability requirement and has no exception
+        if (!isset($this->stabilityFlags[$name]) && isset($this->acceptableStabilities[$stability])) {
+            return true;
+        }
+
+        // allow if package matches the package-specific stability flag
+        if (isset($this->stabilityFlags[$name]) && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$name]) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private function ensurePackageIsLoaded($data)
+    {
+        if (is_array($data)) {
+            if (isset($data['alias_of'])) {
+                $aliasOf = $this->packageById($data['alias_of']);
+                $package = $this->packages[$data['id'] - 1] = $data['repo']->loadAliasPackage($data, $aliasOf);
+                $package->setRootPackageAlias(!empty($data['root_alias']));
+            } else {
+                $package = $this->packages[$data['id'] - 1] = $data['repo']->loadPackage($data);
+            }
+
+            $package->setId($data['id']);
+            $data = $package;
+        }
+
+        return $data;
+    }
+
+    /**
+     * Checks if the package matches the given constraint directly or through
+     * provided or replaced packages
+     *
+     * @param  array|PackageInterface  $candidate
+     * @param  string                  $name       Name of the package to be matched
+     * @param  LinkConstraintInterface $constraint The constraint to verify
+     * @return int                     One of the MATCH* constants of this class or 0 if there is no match
+     */
+    private function match($candidate, $name, LinkConstraintInterface $constraint)
+    {
+        // handle array packages
+        if (is_array($candidate)) {
+            $candidateName = $candidate['name'];
+            $candidateVersion = $candidate['version'];
+        } else {
+            // handle object packages
+            $candidateName = $candidate->getName();
+            $candidateVersion = $candidate->getVersion();
+        }
+
+        if ($candidateName === $name) {
+            return $constraint->matches(new VersionConstraint('==', $candidateVersion)) ? self::MATCH : self::MATCH_NAME;
+        }
+
+        if (is_array($candidate)) {
+            $provides = isset($candidate['provide'])
+                ? $this->versionParser->parseLinks($candidateName, $candidateVersion, 'provides', $candidate['provide'])
+                : array();
+            $replaces = isset($candidate['replace'])
+                ? $this->versionParser->parseLinks($candidateName, $candidateVersion, 'replaces', $candidate['replace'])
+                : array();
+        } else {
+            $provides = $candidate->getProvides();
+            $replaces = $candidate->getReplaces();
+        }
+
+        foreach ($provides as $link) {
+            if ($link->getTarget() === $name && $constraint->matches($link->getConstraint())) {
+                return self::MATCH_PROVIDE;
+            }
+        }
+
+        foreach ($replaces as $link) {
+            if ($link->getTarget() === $name && $constraint->matches($link->getConstraint())) {
+                return self::MATCH_REPLACE;
+            }
+        }
+
+        return self::MATCH_NONE;
+    }
 }

+ 51 - 24
src/Composer/Installer.php

@@ -29,6 +29,7 @@ use Composer\Package\Link;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Package\Locker;
 use Composer\Package\PackageInterface;
+use Composer\Package\RootPackageInterface;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\InstalledArrayRepository;
 use Composer\Repository\PlatformRepository;
@@ -55,7 +56,7 @@ class Installer
     protected $config;
 
     /**
-     * @var PackageInterface
+     * @var RootPackageInterface
      */
     protected $package;
 
@@ -112,7 +113,7 @@ class Installer
      *
      * @param IOInterface         $io
      * @param Config              $config
-     * @param PackageInterface    $package
+     * @param RootPackageInterface    $package
      * @param DownloadManager     $downloadManager
      * @param RepositoryManager   $repositoryManager
      * @param Locker              $locker
@@ -120,7 +121,7 @@ class Installer
      * @param EventDispatcher     $eventDispatcher
      * @param AutoloadGenerator   $autoloadGenerator
      */
-    public function __construct(IOInterface $io, Config $config, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator)
+    public function __construct(IOInterface $io, Config $config, RootPackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator)
     {
         $this->io = $io;
         $this->config = $config;
@@ -166,7 +167,8 @@ class Installer
             $installedRepo->addRepository($this->additionalInstalledRepository);
         }
 
-        $aliases = $this->aliasPackages($platformRepo);
+        $aliases = $this->getRootAliases();
+        $this->aliasPlatformPackages($platformRepo, $aliases);
 
         if ($this->runScripts) {
             // dispatch pre event
@@ -244,9 +246,10 @@ class Installer
 
         // creating repository pool
         $pool = new Pool($minimumStability, $stabilityFlags);
-        $pool->addRepository($installedRepo);
-        foreach ($this->repositoryManager->getRepositories() as $repository) {
-            $pool->addRepository($repository);
+        $pool->addRepository($installedRepo, $aliases);
+        $repositories = $this->repositoryManager->getRepositories();
+        foreach ($repositories as $repository) {
+            $pool->addRepository($repository, $aliases);
         }
 
         // creating requirements request
@@ -276,11 +279,8 @@ class Installer
 
             foreach ($lockedPackages as $package) {
                 $version = $package->getVersion();
-                foreach ($aliases as $alias) {
-                    if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) {
-                        $version = $alias['alias_normalized'];
-                        break;
-                    }
+                if (isset($aliases[$package->getName()][$version])) {
+                    $version = $aliases[$package->getName()][$version]['alias_normalized'];
                 }
                 $constraint = new VersionConstraint('=', $version);
                 $request->install($package->getName(), $constraint);
@@ -361,6 +361,10 @@ class Installer
                 continue;
             }
 
+            if ($package instanceof AliasPackage) {
+                continue;
+            }
+
             // skip packages that will be updated/uninstalled
             foreach ($operations as $operation) {
                 if (('update' === $operation->getJobType() && $operation->getInitialPackage()->equals($package))
@@ -392,7 +396,18 @@ class Installer
                         continue;
                     }
 
-                    $newPackage = $this->repositoryManager->findPackage($package->getName(), $package->getVersion());
+                    $newPackage = null;
+                    $matches = $pool->whatProvides($package->getName(), new VersionConstraint('=', $package->getVersion()));
+                    foreach ($matches as $match) {
+                        // skip local packages
+                        if (!in_array($match->getRepository(), $repositories, true)) {
+                            continue;
+                        }
+
+                        $newPackage = $match;
+                        break;
+                    }
+
                     if ($newPackage && $newPackage->getSourceReference() !== $package->getSourceReference()) {
                         $operations[] = new UpdateOperation($package, $newPackage);
                     }
@@ -487,7 +502,7 @@ class Installer
         return true;
     }
 
-    private function aliasPackages(PlatformRepository $platformRepo)
+    private function getRootAliases()
     {
         if (!$this->update && $this->locker->isLocked()) {
             $aliases = $this->locker->getAliases();
@@ -495,20 +510,32 @@ class Installer
             $aliases = $this->package->getAliases();
         }
 
+        $normalizedAliases = array();
+
         foreach ($aliases as $alias) {
-            $packages = array_merge(
-                $platformRepo->findPackages($alias['package'], $alias['version']),
-                $this->repositoryManager->findPackages($alias['package'], $alias['version'])
+            $normalizedAliases[$alias['package']][$alias['version']] = array(
+                'alias' => $alias['alias'],
+                'alias_normalized' => $alias['alias_normalized']
             );
-            foreach ($packages as $package) {
-                $package->setAlias($alias['alias_normalized']);
-                $package->setPrettyAlias($alias['alias']);
-                $package->getRepository()->addPackage($aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
-                $aliasPackage->setRootPackageAlias(true);
-            }
         }
 
-        return $aliases;
+        return $normalizedAliases;
+    }
+
+    private function aliasPlatformPackages(PlatformRepository $platformRepo, $aliases)
+    {
+        foreach ($aliases as $package => $versions) {
+            foreach ($versions as $version => $alias) {
+                $packages = $platformRepo->findPackages($package, $version);
+                foreach ($packages as $package) {
+                    $package->setAlias($alias['alias_normalized']);
+                    $package->setPrettyAlias($alias['alias']);
+                    $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
+                    $aliasPackage->setRootPackageAlias(true);
+                    $platformRepo->addPackage($aliasPackage);
+                }
+            }
+        }
     }
 
     private function isUpdateable(PackageInterface $package)

+ 1 - 1
src/Composer/Package/AliasPackage.php

@@ -18,7 +18,7 @@ use Composer\Package\Version\VersionParser;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class AliasPackage extends BasePackage
+class AliasPackage extends BasePackage implements CompletePackageInterface
 {
     protected $version;
     protected $prettyVersion;

+ 0 - 37
src/Composer/Package/BasePackage.php

@@ -12,8 +12,6 @@
 
 namespace Composer\Package;
 
-use Composer\Package\LinkConstraint\LinkConstraintInterface;
-use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Repository\RepositoryInterface;
 use Composer\Repository\PlatformRepository;
 
@@ -38,12 +36,6 @@ abstract class BasePackage implements PackageInterface
     const STABILITY_ALPHA   = 15;
     const STABILITY_DEV     = 20;
 
-    const MATCH_NAME = -1;
-    const MATCH_NONE = 0;
-    const MATCH = 1;
-    const MATCH_PROVIDE = 2;
-    const MATCH_REPLACE = 3;
-
     public static $stabilities = array(
         'stable' => self::STABILITY_STABLE,
         'RC'     => self::STABILITY_RC,
@@ -122,35 +114,6 @@ abstract class BasePackage implements PackageInterface
         return $this->id;
     }
 
-    /**
-     * Checks if the package matches the given constraint directly or through
-     * provided or replaced packages
-     *
-     * @param  string                  $name       Name of the package to be matched
-     * @param  LinkConstraintInterface $constraint The constraint to verify
-     * @return int                     One of the MATCH* constants of this class or 0 if there is no match
-     */
-    public function matches($name, LinkConstraintInterface $constraint)
-    {
-        if ($this->name === $name) {
-            return $constraint->matches(new VersionConstraint('==', $this->getVersion())) ? self::MATCH : self::MATCH_NAME;
-        }
-
-        foreach ($this->getProvides() as $link) {
-            if ($link->getTarget() === $name && $constraint->matches($link->getConstraint())) {
-                return self::MATCH_PROVIDE;
-            }
-        }
-
-        foreach ($this->getReplaces() as $link) {
-            if ($link->getTarget() === $name && $constraint->matches($link->getConstraint())) {
-                return self::MATCH_REPLACE;
-            }
-        }
-
-        return self::MATCH_NONE;
-    }
-
     public function getRepository()
     {
         return $this->repository;

+ 174 - 0
src/Composer/Package/CompletePackage.php

@@ -0,0 +1,174 @@
+<?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\Package;
+
+use Composer\Package\Version\VersionParser;
+
+/**
+ * Package containing additional metadata that is not used by the solver
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class CompletePackage extends Package implements CompletePackageInterface
+{
+    protected $repositories;
+    protected $license = array();
+    protected $keywords;
+    protected $authors;
+    protected $description;
+    protected $homepage;
+    protected $scripts = array();
+    protected $support = array();
+
+    /**
+     * @param array $scripts
+     */
+    public function setScripts(array $scripts)
+    {
+        $this->scripts = $scripts;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getScripts()
+    {
+        return $this->scripts;
+    }
+
+    /**
+     * Set the repositories
+     *
+     * @param string $repositories
+     */
+    public function setRepositories($repositories)
+    {
+        $this->repositories = $repositories;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getRepositories()
+    {
+        return $this->repositories;
+    }
+
+    /**
+     * Set the license
+     *
+     * @param array $license
+     */
+    public function setLicense(array $license)
+    {
+        $this->license = $license;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getLicense()
+    {
+        return $this->license;
+    }
+
+    /**
+     * Set the keywords
+     *
+     * @param array $keywords
+     */
+    public function setKeywords(array $keywords)
+    {
+        $this->keywords = $keywords;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getKeywords()
+    {
+        return $this->keywords;
+    }
+
+    /**
+     * Set the authors
+     *
+     * @param array $authors
+     */
+    public function setAuthors(array $authors)
+    {
+        $this->authors = $authors;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getAuthors()
+    {
+        return $this->authors;
+    }
+
+    /**
+     * Set the description
+     *
+     * @param string $description
+     */
+    public function setDescription($description)
+    {
+        $this->description = $description;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * Set the homepage
+     *
+     * @param string $homepage
+     */
+    public function setHomepage($homepage)
+    {
+        $this->homepage = $homepage;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getHomepage()
+    {
+        return $this->homepage;
+    }
+
+    /**
+     * Set the support information
+     *
+     * @param array $support
+     */
+    public function setSupport(array $support)
+    {
+        $this->support = $support;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getSupport()
+    {
+        return $this->support;
+    }
+}

+ 81 - 0
src/Composer/Package/CompletePackageInterface.php

@@ -0,0 +1,81 @@
+<?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\Package;
+
+/**
+ * Defines package metadata that is not necessarily needed for solving and installing packages
+ *
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+interface CompletePackageInterface extends PackageInterface
+{
+    /**
+     * Returns the scripts of this package
+     *
+     * @return array array('script name' => array('listeners'))
+     */
+    public function getScripts();
+
+    /**
+     * Returns an array of repositories
+     *
+     * {"<type>": {<config key/values>}}
+     *
+     * @return array Repositories
+     */
+    public function getRepositories();
+
+    /**
+     * Returns the package license, e.g. MIT, BSD, GPL
+     *
+     * @return array The package licenses
+     */
+    public function getLicense();
+
+    /**
+     * Returns an array of keywords relating to the package
+     *
+     * @return array
+     */
+    public function getKeywords();
+
+    /**
+     * Returns the package description
+     *
+     * @return string
+     */
+    public function getDescription();
+
+    /**
+     * Returns the package homepage
+     *
+     * @return string
+     */
+    public function getHomepage();
+
+    /**
+     * Returns an array of authors of the package
+     *
+     * Each item can contain name/homepage/email keys
+     *
+     * @return array
+     */
+    public function getAuthors();
+
+    /**
+     * Returns the support information
+     *
+     * @return array
+     */
+    public function getSupport();
+}

+ 27 - 12
src/Composer/Package/Dumper/ArrayDumper.php

@@ -14,6 +14,7 @@ namespace Composer\Package\Dumper;
 
 use Composer\Package\BasePackage;
 use Composer\Package\PackageInterface;
+use Composer\Package\CompletePackageInterface;
 
 /**
  * @author Konstantin Kudryashiv <ever.zet@gmail.com>
@@ -25,19 +26,11 @@ class ArrayDumper
     {
         $keys = array(
             'binaries' => 'bin',
-            'scripts',
             'type',
             'extra',
             'installationSource' => 'installation-source',
-            'license',
-            'authors',
-            'description',
-            'homepage',
-            'keywords',
             'autoload',
-            'repositories',
             'includePaths' => 'include-path',
-            'support',
         );
 
         $data = array();
@@ -49,10 +42,6 @@ class ArrayDumper
             $data['target-dir'] = $package->getTargetDir();
         }
 
-        if ($package->getReleaseDate()) {
-            $data['time'] = $package->getReleaseDate()->format('Y-m-d H:i:s');
-        }
-
         if ($package->getSourceType()) {
             $data['source']['type'] = $package->getSourceType();
             $data['source']['url'] = $package->getSourceUrl();
@@ -78,6 +67,32 @@ class ArrayDumper
             $data['suggest'] = $packages;
         }
 
+        if ($package->getReleaseDate()) {
+            $data['time'] = $package->getReleaseDate()->format('Y-m-d H:i:s');
+        }
+
+        $data = $this->dumpValues($package, $keys, $data);
+
+        if ($package instanceof CompletePackageInterface) {
+            $keys = array(
+                'scripts',
+                'license',
+                'authors',
+                'description',
+                'homepage',
+                'keywords',
+                'repositories',
+                'support',
+            );
+
+            $data = $this->dumpValues($package, $keys, $data);
+        }
+
+        return $data;
+    }
+
+    private function dumpValues(PackageInterface $package, array $keys, array $data)
+    {
         foreach ($keys as $method => $key) {
             if (is_numeric($method)) {
                 $method = $key;

+ 82 - 73
src/Composer/Package/Loader/ArrayLoader.php

@@ -31,7 +31,7 @@ class ArrayLoader implements LoaderInterface
         $this->versionParser = $parser;
     }
 
-    public function load(array $config)
+    public function load(array $config, $class = 'Composer\Package\CompletePackage')
     {
         if (!isset($config['name'])) {
             throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').');
@@ -46,7 +46,7 @@ class ArrayLoader implements LoaderInterface
         } else {
             $version = $this->versionParser->normalize($config['version']);
         }
-        $package = new Package\MemoryPackage($config['name'], $version, $config['version']);
+        $package = new $class($config['name'], $version, $config['version']);
         $package->setType(isset($config['type']) ? strtolower($config['type']) : 'library');
 
         if (isset($config['target-dir'])) {
@@ -67,42 +67,6 @@ class ArrayLoader implements LoaderInterface
             $package->setBinaries($config['bin']);
         }
 
-        if (isset($config['scripts']) && is_array($config['scripts'])) {
-            foreach ($config['scripts'] as $event => $listeners) {
-                $config['scripts'][$event]= (array) $listeners;
-            }
-            $package->setScripts($config['scripts']);
-        }
-
-        if (!empty($config['description']) && is_string($config['description'])) {
-            $package->setDescription($config['description']);
-        }
-
-        if (!empty($config['homepage']) && is_string($config['homepage'])) {
-            $package->setHomepage($config['homepage']);
-        }
-
-        if (!empty($config['keywords']) && is_array($config['keywords'])) {
-            $package->setKeywords($config['keywords']);
-        }
-
-        if (!empty($config['license'])) {
-            $package->setLicense(is_array($config['license']) ? $config['license'] : array($config['license']));
-        }
-
-        if (!empty($config['time'])) {
-            try {
-                $date = new \DateTime($config['time']);
-                $date->setTimezone(new \DateTimeZone('UTC'));
-                $package->setReleaseDate($date);
-            } catch (\Exception $e) {
-            }
-        }
-
-        if (!empty($config['authors']) && is_array($config['authors'])) {
-            $package->setAuthors($config['authors']);
-        }
-
         if (isset($config['installation-source'])) {
             $package->setInstallationSource($config['installation-source']);
         }
@@ -134,35 +98,21 @@ class ArrayLoader implements LoaderInterface
             $package->setDistSha1Checksum(isset($config['dist']['shasum']) ? $config['dist']['shasum'] : null);
         }
 
-        // check for a branch alias (dev-master => 1.0.x-dev for example) if this is a named branch
-        if ('dev-' === substr($package->getPrettyVersion(), 0, 4) && isset($config['extra']['branch-alias']) && is_array($config['extra']['branch-alias'])) {
-            foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
-                // ensure it is an alias to a -dev package
-                if ('-dev' !== substr($targetBranch, -4)) {
-                    continue;
-                }
-                // normalize without -dev and ensure it's a numeric branch that is parseable
-                $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
-                if ('-dev' !== substr($validatedTargetBranch, -4)) {
-                    continue;
-                }
-
-                // ensure that it is the current branch aliasing itself
-                if (strtolower($package->getPrettyVersion()) !== strtolower($sourceBranch)) {
-                    continue;
-                }
-
-                $package->setAlias($validatedTargetBranch);
-                $package->setPrettyAlias(preg_replace('{(\.9{7})+}', '.x', $validatedTargetBranch));
-                break;
-            }
+        if ($aliasNormalized = $this->getBranchAlias($config)) {
+            $package->setAlias($aliasNormalized);
+            $package->setPrettyAlias(preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
         }
 
         foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) {
             if (isset($config[$type])) {
                 $method = 'set'.ucfirst($opts['method']);
                 $package->{$method}(
-                    $this->loadLinksFromConfig($package, $opts['description'], $config[$type])
+                    $this->versionParser->parseLinks(
+                        $package->getName(),
+                        $package->getPrettyVersion(),
+                        $opts['description'],
+                        $config[$type]
+                    )
                 );
             }
         }
@@ -184,25 +134,84 @@ class ArrayLoader implements LoaderInterface
             $package->setIncludePaths($config['include-path']);
         }
 
-        if (isset($config['support'])) {
-            $package->setSupport($config['support']);
+        if (!empty($config['time'])) {
+            try {
+                $date = new \DateTime($config['time']);
+                $date->setTimezone(new \DateTimeZone('UTC'));
+                $package->setReleaseDate($date);
+            } catch (\Exception $e) {
+            }
+        }
+
+        if ($package instanceof Package\CompletePackageInterface) {
+            if (isset($config['scripts']) && is_array($config['scripts'])) {
+                foreach ($config['scripts'] as $event => $listeners) {
+                    $config['scripts'][$event]= (array) $listeners;
+                }
+                $package->setScripts($config['scripts']);
+            }
+
+            if (!empty($config['description']) && is_string($config['description'])) {
+                $package->setDescription($config['description']);
+            }
+
+            if (!empty($config['homepage']) && is_string($config['homepage'])) {
+                $package->setHomepage($config['homepage']);
+            }
+
+            if (!empty($config['keywords']) && is_array($config['keywords'])) {
+                $package->setKeywords($config['keywords']);
+            }
+
+            if (!empty($config['license'])) {
+                $package->setLicense(is_array($config['license']) ? $config['license'] : array($config['license']));
+            }
+
+            if (!empty($config['authors']) && is_array($config['authors'])) {
+                $package->setAuthors($config['authors']);
+            }
+
+            if (isset($config['support'])) {
+                $package->setSupport($config['support']);
+            }
         }
 
         return $package;
     }
 
-    private function loadLinksFromConfig($package, $description, array $linksSpecs)
+    /**
+     * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists
+     *
+     * @param  array       $config the entire package config
+     * @return string|null normalized version of the branch alias or null if there is none
+     */
+    public function getBranchAlias(array $config)
     {
-        $links = array();
-        foreach ($linksSpecs as $packageName => $constraint) {
-            if ('self.version' === $constraint) {
-                $parsedConstraint = $this->versionParser->parseConstraints($package->getPrettyVersion());
-            } else {
-                $parsedConstraint = $this->versionParser->parseConstraints($constraint);
-            }
-            $links[] = new Package\Link($package->getName(), $packageName, $parsedConstraint, $description, $constraint);
+        if ('dev-' !== substr($config['version'], 0, 4)
+            || !isset($config['extra']['branch-alias'])
+            || !is_array($config['extra']['branch-alias'])
+        ) {
+            return;
         }
 
-        return $links;
+        foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
+            // ensure it is an alias to a -dev package
+            if ('-dev' !== substr($targetBranch, -4)) {
+                continue;
+            }
+
+            // normalize without -dev and ensure it's a numeric branch that is parseable
+            $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
+            if ('-dev' !== substr($validatedTargetBranch, -4)) {
+                continue;
+            }
+
+            // ensure that it is the current branch aliasing itself
+            if (strtolower($config['version']) !== strtolower($sourceBranch)) {
+                continue;
+            }
+
+            return $validatedTargetBranch;
+        }
     }
 }

+ 2 - 1
src/Composer/Package/Loader/LoaderInterface.php

@@ -23,7 +23,8 @@ interface LoaderInterface
      * Converts a package from an array to a real instance
      *
      * @param  array                              $package Package config
+     * @param  string                             $class   Package class to use
      * @return \Composer\Package\PackageInterface
      */
-    public function load(array $package);
+    public function load(array $package, $class = 'Composer\Package\CompletePackage');
 }

+ 2 - 2
src/Composer/Package/Loader/RootPackageLoader.php

@@ -40,7 +40,7 @@ class RootPackageLoader extends ArrayLoader
         parent::__construct($parser);
     }
 
-    public function load(array $config)
+    public function load(array $config, $class = 'Composer\Package\RootPackage')
     {
         if (!isset($config['name'])) {
             $config['name'] = '__root__';
@@ -62,7 +62,7 @@ class RootPackageLoader extends ArrayLoader
             $version = $config['version'];
         }
 
-        $package = parent::load($config);
+        $package = parent::load($config, $class);
 
         $aliases = array();
         $stabilityFlags = array();

+ 2 - 2
src/Composer/Package/Loader/ValidatingArrayLoader.php

@@ -36,7 +36,7 @@ class ValidatingArrayLoader implements LoaderInterface
         $this->versionParser = $parser;
     }
 
-    public function load(array $config)
+    public function load(array $config, $class = 'Composer\Package\CompletePackage')
     {
         $this->config = $config;
 
@@ -168,7 +168,7 @@ class ValidatingArrayLoader implements LoaderInterface
             throw new \Exception(implode("\n", $this->errors));
         }
 
-        $package = $this->loader->load($this->config);
+        $package = $this->loader->load($this->config, $class);
         $this->errors = array();
         $this->config = null;
 

+ 12 - 1
src/Composer/Package/Locker.php

@@ -177,11 +177,22 @@ class Locker
             'hash' => $this->hash,
             'packages' => null,
             'packages-dev' => null,
-            'aliases' => $aliases,
+            'aliases' => array(),
             'minimum-stability' => $minimumStability,
             'stability-flags' => $stabilityFlags,
         );
 
+        foreach ($aliases as $package => $versions) {
+            foreach ($versions as $version => $alias) {
+                $lock['aliases'][] = array(
+                    'alias' => $alias['alias'],
+                    'alias_normalized' => $alias['alias_normalized'],
+                    'version' => $version,
+                    'package' => $package,
+                );
+            }
+        }
+
         $lock['packages'] = $this->lockPackages($packages);
         if (null !== $devPackages) {
             $lock['packages-dev'] = $this->lockPackages($devPackages);

+ 9 - 216
src/Composer/Package/MemoryPackage.php → src/Composer/Package/Package.php

@@ -15,11 +15,11 @@ namespace Composer\Package;
 use Composer\Package\Version\VersionParser;
 
 /**
- * A package with setters for all members to create it dynamically in memory
+ * Core package definitions that are needed to resolve dependencies and install packages
  *
  * @author Nils Adermann <naderman@naderman.de>
  */
-class MemoryPackage extends BasePackage
+class Package extends BasePackage
 {
     protected $type;
     protected $targetDir;
@@ -33,24 +33,14 @@ class MemoryPackage extends BasePackage
     protected $distSha1Checksum;
     protected $version;
     protected $prettyVersion;
-    protected $repositories;
-    protected $license = array();
     protected $releaseDate;
-    protected $keywords;
-    protected $authors;
-    protected $description;
-    protected $homepage;
     protected $extra = array();
     protected $binaries = array();
-    protected $scripts = array();
     protected $aliases = array();
     protected $alias;
     protected $prettyAlias;
     protected $dev;
-
-    protected $minimumStability = 'stable';
-    protected $stabilityFlags = array();
-    protected $references = array();
+    protected $stability;
 
     protected $requires = array();
     protected $conflicts = array();
@@ -60,7 +50,6 @@ class MemoryPackage extends BasePackage
     protected $suggests = array();
     protected $autoload = array();
     protected $includePaths = array();
-    protected $support = array();
 
     /**
      * Creates a new in memory package.
@@ -160,22 +149,6 @@ class MemoryPackage extends BasePackage
         return $this->binaries;
     }
 
-    /**
-     * @param array $scripts
-     */
-    public function setScripts(array $scripts)
-    {
-        $this->scripts = $scripts;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getScripts()
-    {
-        return $this->scripts;
-    }
-
     /**
      * @param array $aliases
      */
@@ -352,24 +325,6 @@ class MemoryPackage extends BasePackage
         return $this->distSha1Checksum;
     }
 
-    /**
-     * Set the repositories
-     *
-     * @param string $repositories
-     */
-    public function setRepositories($repositories)
-    {
-        $this->repositories = $repositories;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getRepositories()
-    {
-        return $this->repositories;
-    }
-
     /**
      * {@inheritDoc}
      */
@@ -387,21 +342,21 @@ class MemoryPackage extends BasePackage
     }
 
     /**
-     * Set the license
+     * Set the releaseDate
      *
-     * @param array $license
+     * @param DateTime $releaseDate
      */
-    public function setLicense(array $license)
+    public function setReleaseDate(\DateTime $releaseDate)
     {
-        $this->license = $license;
+        $this->releaseDate = $releaseDate;
     }
 
     /**
      * {@inheritDoc}
      */
-    public function getLicense()
+    public function getReleaseDate()
     {
-        return $this->license;
+        return $this->releaseDate;
     }
 
     /**
@@ -512,150 +467,6 @@ class MemoryPackage extends BasePackage
         return $this->suggests;
     }
 
-    /**
-     * Set the releaseDate
-     *
-     * @param DateTime $releaseDate
-     */
-    public function setReleaseDate(\DateTime $releaseDate)
-    {
-        $this->releaseDate = $releaseDate;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getReleaseDate()
-    {
-        return $this->releaseDate;
-    }
-
-    /**
-     * Set the keywords
-     *
-     * @param array $keywords
-     */
-    public function setKeywords(array $keywords)
-    {
-        $this->keywords = $keywords;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getKeywords()
-    {
-        return $this->keywords;
-    }
-
-    /**
-     * Set the authors
-     *
-     * @param array $authors
-     */
-    public function setAuthors(array $authors)
-    {
-        $this->authors = $authors;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getAuthors()
-    {
-        return $this->authors;
-    }
-
-    /**
-     * Set the description
-     *
-     * @param string $description
-     */
-    public function setDescription($description)
-    {
-        $this->description = $description;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getDescription()
-    {
-        return $this->description;
-    }
-
-    /**
-     * Set the homepage
-     *
-     * @param string $homepage
-     */
-    public function setHomepage($homepage)
-    {
-        $this->homepage = $homepage;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getHomepage()
-    {
-        return $this->homepage;
-    }
-
-    /**
-     * Set the minimumStability
-     *
-     * @param string $minimumStability
-     */
-    public function setMinimumStability($minimumStability)
-    {
-        $this->minimumStability = $minimumStability;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getMinimumStability()
-    {
-        return $this->minimumStability;
-    }
-
-    /**
-     * Set the stabilityFlags
-     *
-     * @param array $stabilityFlags
-     */
-    public function setStabilityFlags(array $stabilityFlags)
-    {
-        $this->stabilityFlags = $stabilityFlags;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getStabilityFlags()
-    {
-        return $this->stabilityFlags;
-    }
-
-    /**
-     * Set the references
-     *
-     * @param array $references
-     */
-    public function setReferences(array $references)
-    {
-        $this->references = $references;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getReferences()
-    {
-        return $this->references;
-    }
-
     /**
      * Set the autoload mapping
      *
@@ -691,22 +502,4 @@ class MemoryPackage extends BasePackage
     {
         return $this->includePaths;
     }
-
-    /**
-     * Set the support information
-     *
-     * @param array $support
-     */
-    public function setSupport(array $support)
-    {
-        $this->support = $support;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getSupport()
-    {
-        return $this->support;
-    }
 }

+ 9 - 78
src/Composer/Package/PackageInterface.php

@@ -12,11 +12,12 @@
 
 namespace Composer\Package;
 
-use Composer\Package\LinkConstraint\LinkConstraintInterface;
 use Composer\Repository\RepositoryInterface;
 
 /**
- * @author Nils Adermann <naderman@naderman.de>
+ * Defines the essential information a package has that is used during solving/installation
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
  */
 interface PackageInterface
 {
@@ -58,16 +59,6 @@ interface PackageInterface
      */
     public function getId();
 
-    /**
-     * Checks if the package matches the given constraint directly or through
-     * provided or replaced packages
-     *
-     * @param  string                  $name       Name of the package to be matched
-     * @param  LinkConstraintInterface $constraint The constraint to verify
-     * @return bool                    Whether this package matches the name and constraint
-     */
-    public function matches($name, LinkConstraintInterface $constraint);
-
     /**
      * Returns whether the package is a development virtual package or a concrete one
      *
@@ -159,13 +150,6 @@ interface PackageInterface
      */
     public function getDistSha1Checksum();
 
-    /**
-     * Returns the scripts of this package
-     *
-     * @return array array('script name' => array('listeners'))
-     */
-    public function getScripts();
-
     /**
      * Returns the version of this package
      *
@@ -181,18 +165,18 @@ interface PackageInterface
     public function getPrettyVersion();
 
     /**
-     * Returns the stability of this package: one of (dev, alpha, beta, RC, stable)
+     * Returns the release date of the package
      *
-     * @return string
+     * @return \DateTime
      */
-    public function getStability();
+    public function getReleaseDate();
 
     /**
-     * Returns the package license, e.g. MIT, BSD, GPL
+     * Returns the stability of this package: one of (dev, alpha, beta, RC, stable)
      *
-     * @return array The package licenses
+     * @return string
      */
-    public function getLicense();
+    public function getStability();
 
     /**
      * Returns a set of links to packages which need to be installed before
@@ -262,15 +246,6 @@ interface PackageInterface
      */
     public function getIncludePaths();
 
-    /**
-     * Returns an array of repositories
-     *
-     * {"<type>": {<config key/values>}}
-     *
-     * @return array Repositories
-     */
-    public function getRepositories();
-
     /**
      * Stores a reference to the repository that owns the package
      *
@@ -285,27 +260,6 @@ interface PackageInterface
      */
     public function getRepository();
 
-    /**
-     * Returns the release date of the package
-     *
-     * @return \DateTime
-     */
-    public function getReleaseDate();
-
-    /**
-     * Returns an array of keywords relating to the package
-     *
-     * @return array
-     */
-    public function getKeywords();
-
-    /**
-     * Returns the package description
-     *
-     * @return string
-     */
-    public function getDescription();
-
     /**
      * Returns the package binaries
      *
@@ -313,22 +267,6 @@ interface PackageInterface
      */
     public function getBinaries();
 
-    /**
-     * Returns the package homepage
-     *
-     * @return string
-     */
-    public function getHomepage();
-
-    /**
-     * Returns an array of authors of the package
-     *
-     * Each item can contain name/homepage/email keys
-     *
-     * @return array
-     */
-    public function getAuthors();
-
     /**
      * Returns a version this package should be aliased to
      *
@@ -363,11 +301,4 @@ interface PackageInterface
      * @return string
      */
     public function getPrettyString();
-
-    /**
-     * Returns the support information
-     *
-     * @return array
-     */
-    public function getSupport();
 }

+ 81 - 0
src/Composer/Package/RootPackage.php

@@ -0,0 +1,81 @@
+<?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\Package;
+
+use Composer\Package\Version\VersionParser;
+
+/**
+ * The root package represents the project's composer.json and contains additional metadata
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class RootPackage extends CompletePackage implements RootPackageInterface
+{
+    protected $minimumStability = 'stable';
+    protected $stabilityFlags = array();
+    protected $references = array();
+
+    /**
+     * Set the minimumStability
+     *
+     * @param string $minimumStability
+     */
+    public function setMinimumStability($minimumStability)
+    {
+        $this->minimumStability = $minimumStability;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMinimumStability()
+    {
+        return $this->minimumStability;
+    }
+
+    /**
+     * Set the stabilityFlags
+     *
+     * @param array $stabilityFlags
+     */
+    public function setStabilityFlags(array $stabilityFlags)
+    {
+        $this->stabilityFlags = $stabilityFlags;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getStabilityFlags()
+    {
+        return $this->stabilityFlags;
+    }
+
+    /**
+     * Set the references
+     *
+     * @param array $references
+     */
+    public function setReferences(array $references)
+    {
+        $this->references = $references;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getReferences()
+    {
+        return $this->references;
+    }
+}

+ 46 - 0
src/Composer/Package/RootPackageInterface.php

@@ -0,0 +1,46 @@
+<?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\Package;
+
+/**
+ * Defines additional fields that are only needed for the root package
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+interface RootPackageInterface extends CompletePackageInterface
+{
+    /**
+     * Returns the minimum stability of the package
+     *
+     * @return string
+     */
+    public function getMinimumStability();
+
+    /**
+     * Returns the stability flags to apply to dependencies
+     *
+     * array('foo/bar' => 'dev')
+     *
+     * @return array
+     */
+    public function getStabilityFlags();
+
+    /**
+     * Returns a set of package names and source references that must be enforced on them
+     *
+     * array('foo/bar' => 'abcd1234')
+     *
+     * @return array
+     */
+    public function getReferences();
+}

+ 23 - 0
src/Composer/Package/Version/VersionParser.php

@@ -14,6 +14,7 @@ namespace Composer\Package\Version;
 
 use Composer\Package\BasePackage;
 use Composer\Package\PackageInterface;
+use Composer\Package\Link;
 use Composer\Package\LinkConstraint\MultiConstraint;
 use Composer\Package\LinkConstraint\VersionConstraint;
 
@@ -164,6 +165,28 @@ class VersionParser
         return 'dev-'.$name;
     }
 
+    /**
+     * @param string $source source package name
+     * @param string $sourceVersion source package version (pretty version ideally)
+     * @param string $description link description (e.g. requires, replaces, ..)
+     * @param array $links array of package name => constraint mappings
+     * @return Link[]
+     */
+    public function parseLinks($source, $sourceVersion, $description, $links)
+    {
+        $res = array();
+        foreach ($links as $target => $constraint) {
+            if ('self.version' === $constraint) {
+                $parsedConstraint = $this->parseConstraints($sourceVersion);
+            } else {
+                $parsedConstraint = $this->parseConstraints($constraint);
+            }
+            $res[] = new Link($source, $target, $parsedConstraint, $description, $constraint);
+        }
+
+        return $res;
+    }
+
     /**
      * Parses as constraint string into LinkConstraint objects
      *

+ 14 - 0
src/Composer/Repository/ArrayRepository.php

@@ -112,6 +112,20 @@ class ArrayRepository implements RepositoryInterface
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function filterPackages($callback, $class = 'Composer\Package\Package')
+    {
+        foreach ($this->getPackages() as $package) {
+            if (false === $callback($package)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     protected function createAliasPackage(PackageInterface $package, $alias = null, $prettyAlias = null)
     {
         return new AliasPackage($package, $alias ?: $package->getAlias(), $prettyAlias ?: $package->getPrettyAlias());

+ 115 - 8
src/Composer/Repository/ComposerRepository.php

@@ -14,6 +14,7 @@ namespace Composer\Repository;
 
 use Composer\Package\Loader\ArrayLoader;
 use Composer\Package\PackageInterface;
+use Composer\Package\Version\VersionParser;
 use Composer\Json\JsonFile;
 use Composer\Cache;
 use Composer\Config;
@@ -23,14 +24,16 @@ use Composer\Util\RemoteFilesystem;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class ComposerRepository extends ArrayRepository implements NotifiableRepositoryInterface
+class ComposerRepository extends ArrayRepository implements NotifiableRepositoryInterface, StreamableRepositoryInterface
 {
     protected $config;
     protected $url;
     protected $io;
-    protected $packages;
     protected $cache;
     protected $notifyUrl;
+    protected $loader;
+    private $rawData;
+    private $minimalPackages;
 
     public function __construct(array $repoConfig, IOInterface $io, Config $config)
     {
@@ -47,6 +50,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
         $this->url = $repoConfig['url'];
         $this->io = $io;
         $this->cache = new Cache($io, $config->get('home').'/cache/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url));
+        $this->loader = new ArrayLoader();
     }
 
     /**
@@ -78,10 +82,110 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
         @file_get_contents($url, false, $context);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function getMinimalPackages()
+    {
+        if (isset($this->minimalPackages)) {
+            return $this->minimalPackages;
+        }
+
+        if (null === $this->rawData) {
+            $this->rawData = $this->loadDataFromServer();
+        }
+
+        $this->minimalPackages = array();
+        $versionParser = new VersionParser;
+
+        foreach ($this->rawData as $package) {
+            $version = !empty($package['version_normalized']) ? $package['version_normalized'] : $versionParser->normalize($package['version']);
+            $data = array(
+                'name' => strtolower($package['name']),
+                'repo' => $this,
+                'version' => $version,
+                'raw' => $package,
+            );
+            if (!empty($package['replace'])) {
+                $data['replace'] = $package['replace'];
+            }
+            if (!empty($package['provide'])) {
+                $data['provide'] = $package['provide'];
+            }
+
+            // add branch aliases
+            if ($aliasNormalized = $this->loader->getBranchAlias($package)) {
+                $data['alias'] = preg_replace('{(\.9{7})+}', '.x', $aliasNormalized);
+                $data['alias_normalized'] = $aliasNormalized;
+            }
+
+            $this->minimalPackages[] = $data;
+        }
+
+        return $this->minimalPackages;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function filterPackages($callback, $class = 'Composer\Package\Package')
+    {
+        if (null === $this->rawData) {
+            $this->rawData = $this->loadDataFromServer();
+        }
+
+        foreach ($this->rawData as $package) {
+            if (false === $callback($package = $this->loader->load($package, $class))) {
+                return false;
+            }
+            if ($package->getAlias()) {
+                if (false === $callback($this->createAliasPackage($package))) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function loadPackage(array $data)
+    {
+        $package = $this->loader->load($data['raw'], 'Composer\Package\Package');
+        $package->setRepository($this);
+
+        return $package;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function loadAliasPackage(array $data, PackageInterface $aliasOf)
+    {
+        $aliasPackage = $this->createAliasPackage($aliasOf, $data['version'], $data['alias']);
+        $aliasPackage->setRepository($this);
+
+        return $aliasPackage;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     protected function initialize()
     {
         parent::initialize();
 
+        $repoData = $this->loadDataFromServer();
+
+        foreach ($repoData as $package) {
+            $this->addPackage($this->loader->load($package, 'Composer\Package\CompletePackage'));
+        }
+    }
+
+    protected function loadDataFromServer()
+    {
         if (!extension_loaded('openssl') && 'https' === substr($this->url, 0, 5)) {
             throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url);
         }
@@ -109,17 +213,18 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
             }
         }
 
-        $loader = new ArrayLoader();
-        $this->loadRepository($loader, $data);
+        return $this->loadIncludes($data);
     }
 
-    protected function loadRepository(ArrayLoader $loader, $data)
+    protected function loadIncludes($data)
     {
+        $packages = array();
+
         // legacy repo handling
         if (!isset($data['packages']) && !isset($data['includes'])) {
             foreach ($data as $pkg) {
                 foreach ($pkg['versions'] as $metadata) {
-                    $this->addPackage($loader->load($metadata));
+                    $packages[] = $metadata;
                 }
             }
 
@@ -129,7 +234,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
         if (isset($data['packages'])) {
             foreach ($data['packages'] as $package => $versions) {
                 foreach ($versions as $version => $metadata) {
-                    $this->addPackage($loader->load($metadata));
+                    $packages[] = $metadata;
                 }
             }
         }
@@ -143,8 +248,10 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
                     $includedData = $json->read();
                     $this->cache->write($include, json_encode($includedData));
                 }
-                $this->loadRepository($loader, $includedData);
+                $packages = array_merge($packages, $this->loadIncludes($includedData));
             }
         }
+
+        return $packages;
     }
 }

+ 14 - 0
src/Composer/Repository/CompositeRepository.php

@@ -91,6 +91,20 @@ class CompositeRepository implements RepositoryInterface
         return call_user_func_array('array_merge', $packages);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function filterPackages($callback, $class = 'Composer\Package\Package')
+    {
+        foreach ($this->repositories as $repository) {
+            if (false === $repository->filterPackages($callback, $class)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     /**
      * {@inheritdoc}
      */

+ 1 - 1
src/Composer/Repository/Pear/ChannelReader.php

@@ -17,7 +17,7 @@ use Composer\Util\RemoteFilesystem;
 /**
  * PEAR Channel package reader.
  *
- * Reads channel packages info from and builds MemoryPackage's
+ * Reads channel packages info from and builds Package's
  *
  * @author Alexey Prilipko <palex@farpost.com>
  */

+ 4 - 4
src/Composer/Repository/PearRepository.php

@@ -15,7 +15,7 @@ namespace Composer\Repository;
 use Composer\IO\IOInterface;
 use Composer\Package\Version\VersionParser;
 use Composer\Repository\Pear\ChannelReader;
-use Composer\Package\MemoryPackage;
+use Composer\Package\CompletePackage;
 use Composer\Repository\Pear\ChannelInfo;
 use Composer\Package\Link;
 use Composer\Package\LinkConstraint\VersionConstraint;
@@ -81,10 +81,10 @@ class PearRepository extends ArrayRepository
     }
 
     /**
-     * Builds MemoryPackages from PEAR package definition data.
+     * Builds CompletePackages from PEAR package definition data.
      *
      * @param  ChannelInfo   $channelInfo
-     * @return MemoryPackage
+     * @return CompletePackage
      */
     private function buildComposerPackages(ChannelInfo $channelInfo, VersionParser $versionParser)
     {
@@ -152,7 +152,7 @@ class PearRepository extends ArrayRepository
                     }
                 }
 
-                $package = new MemoryPackage($composerPackageName, $normalizedVersion, $version);
+                $package = new CompletePackage($composerPackageName, $normalizedVersion, $version);
                 $package->setType('pear-library');
                 $package->setDescription($packageDefinition->getDescription());
                 $package->setDistType('file');

+ 4 - 4
src/Composer/Repository/PlatformRepository.php

@@ -12,7 +12,7 @@
 
 namespace Composer\Repository;
 
-use Composer\Package\MemoryPackage;
+use Composer\Package\CompletePackage;
 use Composer\Package\Version\VersionParser;
 
 /**
@@ -34,7 +34,7 @@ class PlatformRepository extends ArrayRepository
             $version = $versionParser->normalize($prettyVersion);
         }
 
-        $php = new MemoryPackage('php', $version, $prettyVersion);
+        $php = new CompletePackage('php', $version, $prettyVersion);
         $php->setDescription('The PHP interpreter');
         parent::addPackage($php);
 
@@ -55,7 +55,7 @@ class PlatformRepository extends ArrayRepository
                 $version = $versionParser->normalize($prettyVersion);
             }
 
-            $ext = new MemoryPackage('ext-'.$name, $version, $prettyVersion);
+            $ext = new CompletePackage('ext-'.$name, $version, $prettyVersion);
             $ext->setDescription('The '.$name.' PHP extension');
             parent::addPackage($ext);
         }
@@ -107,7 +107,7 @@ class PlatformRepository extends ArrayRepository
                 continue;
             }
 
-            $lib = new MemoryPackage('lib-'.$name, $version, $prettyVersion);
+            $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion);
             $lib->setDescription('The '.$name.' PHP library');
             parent::addPackage($lib);
         }

+ 14 - 0
src/Composer/Repository/RepositoryInterface.php

@@ -51,6 +51,20 @@ interface RepositoryInterface extends \Countable
      */
     public function findPackages($name, $version = null);
 
+    /**
+     * Filters all the packages through a callback
+     *
+     * The packages are not guaranteed to be instances in the repository
+     * and this can only be used for streaming through a list of packages.
+     *
+     * If the callback returns false, the process stops
+     *
+     * @param callable $callback
+     * @param string   $class
+     * @return bool false if the process was interrupted, true otherwise
+     */
+    public function filterPackages($callback, $class = 'Composer\Package\Package');
+
     /**
      * Returns list of registered packages.
      *

+ 61 - 0
src/Composer/Repository/StreamableRepositoryInterface.php

@@ -0,0 +1,61 @@
+<?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;
+
+use Composer\Package\AliasPackage;
+use Composer\Package\PackageInterface;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+interface StreamableRepositoryInterface extends RepositoryInterface
+{
+    /**
+     * Return partial package data without loading them all to save on memory
+     *
+     * The function must return an array of package arrays.
+     *
+     * The package array must contain the following fields:
+     *  - name: package name (normalized/lowercased)
+     *  - repo: reference to the repository instance
+     *  - version: normalized version
+     *  - replace: array of package name => version constraint, optional
+     *  - provide: array of package name => version constraint, optional
+     *  - alias: pretty alias that this package should be aliased to, optional
+     *  - alias_normalized: normalized alias that this package should be aliased to, optional
+     *
+     * Any additional information can be returned and will be sent back
+     * into loadPackage/loadAliasPackage for completing the package loading
+     * when it's needed.
+     *
+     * @return array
+     */
+    public function getMinimalPackages();
+
+    /**
+     * Loads a package from minimal info of the package
+     *
+     * @param  array            $data the minimal info as was returned by getMinimalPackage
+     * @return PackageInterface
+     */
+    public function loadPackage(array $data);
+
+    /**
+     * Loads an alias package from minimal info of the package
+     *
+     * @param  array            $data    the minimal info as was returned by getMinimalPackage
+     * @param  PackageInterface $aliasOf the package which this alias is an alias of
+     * @return AliasPackage
+     */
+    public function loadAliasPackage(array $data, PackageInterface $aliasOf);
+}

+ 29 - 29
tests/Composer/Test/Autoload/AutoloadGeneratorTest.php

@@ -15,7 +15,7 @@ namespace Composer\Test\Autoload;
 use Composer\Autoload\AutoloadGenerator;
 use Composer\Util\Filesystem;
 use Composer\Package\AliasPackage;
-use Composer\Package\MemoryPackage;
+use Composer\Package\Package;
 use Composer\Test\TestCase;
 
 class AutoloadGeneratorTest extends TestCase
@@ -76,7 +76,7 @@ class AutoloadGeneratorTest extends TestCase
 
     public function testMainPackageAutoloading()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
         $package->setAutoload(array(
             'psr-0' => array('Main' => 'src/', 'Lala' => array('src/', 'lib/')),
             'classmap' => array('composersrc/'),
@@ -101,7 +101,7 @@ class AutoloadGeneratorTest extends TestCase
     {
         $this->vendorDir = $this->workingDir;
 
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
         $package->setAutoload(array(
             'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'),
             'classmap' => array('composersrc/'),
@@ -124,7 +124,7 @@ class AutoloadGeneratorTest extends TestCase
 
     public function testMainPackageAutoloadingAlternativeVendorDir()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
         $package->setAutoload(array(
             'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'),
             'classmap' => array('composersrc/'),
@@ -147,7 +147,7 @@ class AutoloadGeneratorTest extends TestCase
 
     public function testMainPackageAutoloadingWithTargetDir()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
         $package->setAutoload(array(
             'psr-0' => array('Main\\Foo' => '', 'Main\\Bar' => ''),
         ));
@@ -166,11 +166,11 @@ class AutoloadGeneratorTest extends TestCase
 
     public function testVendorsAutoloading()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
 
         $packages = array();
-        $packages[] = $a = new MemoryPackage('a/a', '1.0', '1.0');
-        $packages[] = $b = new MemoryPackage('b/b', '1.0', '1.0');
+        $packages[] = $a = new Package('a/a', '1.0', '1.0');
+        $packages[] = $b = new Package('b/b', '1.0', '1.0');
         $packages[] = $c = new AliasPackage($b, '1.2', '1.2');
         $a->setAutoload(array('psr-0' => array('A' => 'src/', 'A\\B' => 'lib/')));
         $b->setAutoload(array('psr-0' => array('B\\Sub\\Name' => 'src/')));
@@ -191,11 +191,11 @@ class AutoloadGeneratorTest extends TestCase
 
     public function testVendorsClassMapAutoloading()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
 
         $packages = array();
-        $packages[] = $a = new MemoryPackage('a/a', '1.0', '1.0');
-        $packages[] = $b = new MemoryPackage('b/b', '1.0', '1.0');
+        $packages[] = $a = new Package('a/a', '1.0', '1.0');
+        $packages[] = $b = new Package('b/b', '1.0', '1.0');
         $a->setAutoload(array('classmap' => array('src/')));
         $b->setAutoload(array('classmap' => array('src/', 'lib/')));
 
@@ -226,12 +226,12 @@ class AutoloadGeneratorTest extends TestCase
 
     public function testClassMapAutoloadingEmptyDirAndExactFile()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
 
         $packages = array();
-        $packages[] = $a = new MemoryPackage('a/a', '1.0', '1.0');
-        $packages[] = $b = new MemoryPackage('b/b', '1.0', '1.0');
-        $packages[] = $c = new MemoryPackage('c/c', '1.0', '1.0');
+        $packages[] = $a = new Package('a/a', '1.0', '1.0');
+        $packages[] = $b = new Package('b/b', '1.0', '1.0');
+        $packages[] = $c = new Package('c/c', '1.0', '1.0');
         $a->setAutoload(array('classmap' => array('')));
         $b->setAutoload(array('classmap' => array('test.php')));
         $c->setAutoload(array('classmap' => array('./')));
@@ -263,12 +263,12 @@ class AutoloadGeneratorTest extends TestCase
 
     public function testFilesAutoloadGeneration()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
         $package->setAutoload(array('files' => array('root.php')));
 
         $packages = array();
-        $packages[] = $a = new MemoryPackage('a/a', '1.0', '1.0');
-        $packages[] = $b = new MemoryPackage('b/b', '1.0', '1.0');
+        $packages[] = $a = new Package('a/a', '1.0', '1.0');
+        $packages[] = $b = new Package('b/b', '1.0', '1.0');
         $a->setAutoload(array('files' => array('test.php')));
         $b->setAutoload(array('files' => array('test2.php')));
 
@@ -297,12 +297,12 @@ class AutoloadGeneratorTest extends TestCase
 
     public function testOverrideVendorsAutoloading()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
         $package->setAutoload(array('psr-0' => array('A\\B' => $this->workingDir.'/lib')));
 
         $packages = array();
-        $packages[] = $a = new MemoryPackage('a/a', '1.0', '1.0');
-        $packages[] = $b = new MemoryPackage('b/b', '1.0', '1.0');
+        $packages[] = $a = new Package('a/a', '1.0', '1.0');
+        $packages[] = $b = new Package('b/b', '1.0', '1.0');
         $a->setAutoload(array('psr-0' => array('A' => 'src/', 'A\\B' => 'lib/')));
         $b->setAutoload(array('psr-0' => array('B\\Sub\\Name' => 'src/')));
 
@@ -356,16 +356,16 @@ EOF;
 
     public function testIncludePathFileGeneration()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
         $packages = array();
 
-        $a = new MemoryPackage("a/a", "1.0", "1.0");
+        $a = new Package("a/a", "1.0", "1.0");
         $a->setIncludePaths(array("lib/"));
 
-        $b = new MemoryPackage("b/b", "1.0", "1.0");
+        $b = new Package("b/b", "1.0", "1.0");
         $b->setIncludePaths(array("library"));
 
-        $c = new MemoryPackage("c", "1.0", "1.0");
+        $c = new Package("c", "1.0", "1.0");
         $c->setIncludePaths(array("library"));
 
         $packages[] = $a;
@@ -393,10 +393,10 @@ EOF;
 
     public function testIncludePathsArePrependedInAutoloadFile()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
         $packages = array();
 
-        $a = new MemoryPackage("a/a", "1.0", "1.0");
+        $a = new Package("a/a", "1.0", "1.0");
         $a->setIncludePaths(array("lib/"));
 
         $packages[] = $a;
@@ -426,10 +426,10 @@ EOF;
 
     public function testIncludePathFileWithoutPathsIsSkipped()
     {
-        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package = new Package('a', '1.0', '1.0');
         $packages = array();
 
-        $a = new MemoryPackage("a/a", "1.0", "1.0");
+        $a = new Package("a/a", "1.0", "1.0");
         $packages[] = $a;
 
         $this->repository->expects($this->once())

+ 1 - 1
tests/Composer/Test/ComposerTest.php

@@ -18,7 +18,7 @@ class ComposerTest extends TestCase
     public function testSetGetPackage()
     {
         $composer = new Composer();
-        $package = $this->getMock('Composer\Package\PackageInterface');
+        $package = $this->getMock('Composer\Package\RootPackageInterface');
         $composer->setPackage($package);
 
         $this->assertSame($package, $composer->getPackage());

+ 14 - 4
tests/Composer/Test/Fixtures/installer/update-alias-lock.test

@@ -1,5 +1,5 @@
 --TEST--
-Update aliased package to non-aliased version
+Update aliased package does not mess up the lock file
 --COMPOSER--
 {
     "repositories": [
@@ -12,12 +12,22 @@ Update aliased package to non-aliased version
                     "source": { "reference": "master", "type": "git", "url": "" }
                 }
             ]
+        },
+        {
+            "type": "package",
+            "package": [
+                {
+                    "name": "a/a", "version": "dev-master",
+                    "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } },
+                    "source": { "reference": "lowpriomaster", "type": "git", "url": "" }
+                }
+            ]
         }
     ],
     "require": {
         "a/a": "1.*"
     },
-    "minimum-stability": "stable"
+    "minimum-stability": "dev"
 }
 --LOCK--
 {
@@ -27,7 +37,7 @@ Update aliased package to non-aliased version
     ],
     "packages-dev": null,
     "aliases": [],
-    "minimum-stability": "stable",
+    "minimum-stability": "dev",
     "stability-flags": []
 }
 --INSTALLED--
@@ -48,7 +58,7 @@ update
     ],
     "packages-dev": null,
     "aliases": [],
-    "minimum-stability": "stable",
+    "minimum-stability": "dev",
     "stability-flags": []
 }
 --EXPECT--

+ 1 - 1
tests/Composer/Test/Installer/LibraryInstallerTest.php

@@ -235,7 +235,7 @@ class LibraryInstallerTest extends TestCase
 
     protected function createPackageMock()
     {
-        return $this->getMockBuilder('Composer\Package\MemoryPackage')
+        return $this->getMockBuilder('Composer\Package\Package')
             ->setConstructorArgs(array(md5(rand()), '1.0.0.0', '1.0.0'))
             ->getMock();
     }

+ 1 - 1
tests/Composer/Test/Installer/MetapackageInstallerTest.php

@@ -94,7 +94,7 @@ class MetapackageInstallerTest extends \PHPUnit_Framework_TestCase
 
     private function createPackageMock()
     {
-        return $this->getMockBuilder('Composer\Package\MemoryPackage')
+        return $this->getMockBuilder('Composer\Package\Package')
             ->setConstructorArgs(array(md5(rand()), '1.0.0.0', '1.0.0'))
             ->getMock();
     }

+ 4 - 4
tests/Composer/Test/InstallerTest.php

@@ -17,7 +17,7 @@ use Composer\Config;
 use Composer\Json\JsonFile;
 use Composer\Repository\ArrayRepository;
 use Composer\Repository\RepositoryManager;
-use Composer\Package\PackageInterface;
+use Composer\Package\RootPackageInterface;
 use Composer\Package\Link;
 use Composer\Package\Locker;
 use Composer\Test\Mock\FactoryMock;
@@ -32,7 +32,7 @@ class InstallerTest extends TestCase
     /**
      * @dataProvider provideInstaller
      */
-    public function testInstaller(PackageInterface $rootPackage, $repositories, array $options)
+    public function testInstaller(RootPackageInterface $rootPackage, $repositories, array $options)
     {
         $io = $this->getMock('Composer\IO\IOInterface');
 
@@ -80,7 +80,7 @@ class InstallerTest extends TestCase
         // when A requires B and B requires A, and A is a non-published root package
         // the install of B should succeed
 
-        $a = $this->getPackage('A', '1.0.0');
+        $a = $this->getPackage('A', '1.0.0', 'Composer\Package\RootPackage');
         $a->setRequires(array(
             new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')),
         ));
@@ -100,7 +100,7 @@ class InstallerTest extends TestCase
         // #480: when A requires B and B requires A, and A is a published root package
         // only B should be installed, as A is the root
 
-        $a = $this->getPackage('A', '1.0.0');
+        $a = $this->getPackage('A', '1.0.0', 'Composer\Package\RootPackage');
         $a->setRequires(array(
             new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')),
         ));

+ 8 - 8
tests/Composer/Test/Package/MemoryPackageTest.php → tests/Composer/Test/Package/CompletePackageTest.php

@@ -12,11 +12,11 @@
 
 namespace Composer\Test\Package;
 
-use Composer\Package\MemoryPackage;
+use Composer\Package\Package;
 use Composer\Package\Version\VersionParser;
 use Composer\Test\TestCase;
 
-class MemoryPackageTest extends TestCase
+class CompletePackageTest extends TestCase
 {
     /**
      * Memory package naming, versioning, and marshalling semantics provider
@@ -38,11 +38,11 @@ class MemoryPackageTest extends TestCase
      * Tests memory package naming semantics
      * @dataProvider providerVersioningSchemes
      */
-    public function testMemoryPackageHasExpectedNamingSemantics($name, $version)
+    public function testPackageHasExpectedNamingSemantics($name, $version)
     {
         $versionParser = new VersionParser();
         $normVersion = $versionParser->normalize($version);
-        $package = new MemoryPackage($name, $normVersion, $version);
+        $package = new Package($name, $normVersion, $version);
         $this->assertEquals(strtolower($name), $package->getName());
     }
 
@@ -50,11 +50,11 @@ class MemoryPackageTest extends TestCase
      * Tests memory package versioning semantics
      * @dataProvider providerVersioningSchemes
      */
-    public function testMemoryPackageHasExpectedVersioningSemantics($name, $version)
+    public function testPackageHasExpectedVersioningSemantics($name, $version)
     {
         $versionParser = new VersionParser();
         $normVersion = $versionParser->normalize($version);
-        $package = new MemoryPackage($name, $normVersion, $version);
+        $package = new Package($name, $normVersion, $version);
         $this->assertEquals($version, $package->getPrettyVersion());
         $this->assertEquals($normVersion, $package->getVersion());
     }
@@ -63,11 +63,11 @@ class MemoryPackageTest extends TestCase
      * Tests memory package marshalling/serialization semantics
      * @dataProvider providerVersioningSchemes
      */
-    public function testMemoryPackageHasExpectedMarshallingSemantics($name, $version)
+    public function testPackageHasExpectedMarshallingSemantics($name, $version)
     {
         $versionParser = new VersionParser();
         $normVersion = $versionParser->normalize($version);
-        $package = new MemoryPackage($name, $normVersion, $version);
+        $package = new Package($name, $normVersion, $version);
         $this->assertEquals(strtolower($name).'-'.$normVersion, (string) $package);
     }
 

+ 2 - 2
tests/Composer/Test/Package/Dumper/ArrayDumperTest.php

@@ -23,14 +23,14 @@ class ArrayDumperTest extends \PHPUnit_Framework_TestCase
      */
     private $dumper;
     /**
-     * @var \Composer\Package\PackageInterface|\PHPUnit_Framework_MockObject_MockObject
+     * @var \Composer\Package\CompletePackageInterface|\PHPUnit_Framework_MockObject_MockObject
      */
     private $package;
 
     public function setUp()
     {
         $this->dumper = new ArrayDumper();
-        $this->package = $this->getMock('Composer\Package\PackageInterface');
+        $this->package = $this->getMock('Composer\Package\CompletePackageInterface');
     }
 
     public function testRequiredInformation()

+ 2 - 2
tests/Composer/Test/Repository/Pear/ChannelReaderTest.php

@@ -16,7 +16,7 @@ use Composer\Test\TestCase;
 use Composer\Package\Version\VersionParser;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Package\Link;
-use Composer\Package\MemoryPackage;
+use Composer\Package\CompletePackage;
 use Composer\Test\Mock\RemoteFilesystemMock;
 
 class ChannelReaderTest extends TestCase
@@ -117,7 +117,7 @@ class ChannelReaderTest extends TestCase
 
         $packages = $ref->invoke($reader, $channelInfo, new VersionParser());
 
-        $expectedPackage = new MemoryPackage('pear-test.loc/sample', '1.0.0.1' , '1.0.0.1');
+        $expectedPackage = new CompletePackage('pear-test.loc/sample', '1.0.0.1' , '1.0.0.1');
         $expectedPackage->setType('pear-library');
         $expectedPackage->setDistType('file');
         $expectedPackage->setDescription('description');

+ 3 - 3
tests/Composer/Test/TestCase.php

@@ -13,7 +13,7 @@
 namespace Composer\Test;
 
 use Composer\Package\Version\VersionParser;
-use Composer\Package\MemoryPackage;
+use Composer\Package\Package;
 use Composer\Package\AliasPackage;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Util\Filesystem;
@@ -43,11 +43,11 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
         return $constraint;
     }
 
-    protected function getPackage($name, $version)
+    protected function getPackage($name, $version, $class = 'Composer\Package\Package')
     {
         $normVersion = self::getVersionParser()->normalize($version);
 
-        return new MemoryPackage($name, $normVersion, $version);
+        return new $class($name, $normVersion, $version);
     }
 
     protected function getAliasPackage($package, $version)