瀏覽代碼

Reduce memory usage by only loading packages that are actually needed, fixes #456

Jordi Boggiano 12 年之前
父節點
當前提交
c8a685be6b

+ 150 - 28
src/Composer/DependencyResolver/Pool.php

@@ -13,10 +13,15 @@
 namespace Composer\DependencyResolver;
 
 use Composer\Package\BasePackage;
+use Composer\Package\Version\VersionParser;
+use Composer\Package\Loader\ArrayLoader;
+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,25 @@ 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 $loader;
+    protected $versionParser;
 
     public function __construct($minimumStability = 'stable', array $stabilityFlags = array())
     {
         $stabilities = BasePackage::$stabilities;
+        $this->loader = new ArrayLoader;
+        $this->versionParser = new VersionParser;
         $this->acceptableStabilities = array();
         foreach (BasePackage::$stabilities as $stability => $value) {
             if ($value <= BasePackage::$stabilities[$minimumStability]) {
@@ -63,25 +78,65 @@ 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'];
+                    $stability = VersionParser::parseStability($package['version']);
+                    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['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];
+                        }
+                    }
+                }
+            } else {
+                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;
+                        }
                     }
                 }
             }
@@ -107,6 +162,8 @@ class Pool
     */
     public function packageById($id)
     {
+        $this->ensurePackageIsLoaded($this->packages[$id - 1]);
+
         return $this->packages[$id - 1];
     }
 
@@ -137,6 +194,10 @@ class Pool
         $candidates = $this->packageByName[$name];
 
         if (null === $constraint) {
+            foreach ($candidates as $key => $candidate) {
+                $candidates[$key] = $this->ensurePackageIsLoaded($candidate);
+            }
+
             return $candidates;
         }
 
@@ -144,25 +205,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 +263,65 @@ class Pool
 
         return $prefix.' '.$package->getPrettyString();
     }
+
+    private function ensurePackageIsLoaded($data)
+    {
+        if (is_array($data)) {
+            $data = $this->packages[$data['id'] - 1] = $data['repo']->loadPackage($data, $data['id']);
+        }
+
+        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)
+    {
+        if (is_array($candidate)) {
+            $candidateName = $candidate['name'];
+            $candidateVersion = $candidate['version'];
+            foreach (array('provides', 'replaces') as $linkType) {
+                $$linkType = isset($candidate[rtrim($linkType, 's')]) ? $candidate[rtrim($linkType, 's')] : array();
+                foreach ($$linkType as $target => $constraintDef) {
+                    if ('self.version' === $constraintDef) {
+                        $parsedConstraint = $this->versionParser->parseConstraints($candidateVersion);
+                    } else {
+                        $parsedConstraint = $this->versionParser->parseConstraints($constraintDef);
+                    }
+                    ${$linkType}[$target] = new Link($candidateName, $target, $parsedConstraint, $linkType, $constraintDef);
+                }
+            }
+        } else {
+            $candidateName = $candidate->getName();
+            $candidateVersion = $candidate->getVersion();
+            $provides = $candidate->getProvides();
+            $replaces = $candidate->getReplaces();
+        }
+
+        if ($candidateName === $name) {
+            return $constraint->matches(new VersionConstraint('==', $candidateVersion)) ? self::MATCH : self::MATCH_NAME;
+        }
+
+        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;
+    }
+
 }

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

@@ -38,12 +38,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 +116,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;

+ 298 - 0
src/Composer/Package/CorePackageInterface.php

@@ -0,0 +1,298 @@
+<?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\LinkConstraint\LinkConstraintInterface;
+use Composer\Repository\RepositoryInterface;
+
+/**
+ * Defines the essential information a package has that is used during solving/installation
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+interface CorePackageInterface
+{
+    /**
+     * Returns the package's name without version info, thus not a unique identifier
+     *
+     * @return string package name
+     */
+    public function getName();
+
+    /**
+     * Returns the package's pretty (i.e. with proper case) name
+     *
+     * @return string package name
+     */
+    public function getPrettyName();
+
+    /**
+     * Returns a set of names that could refer to this package
+     *
+     * No version or release type information should be included in any of the
+     * names. Provided or replaced package names need to be returned as well.
+     *
+     * @return array An array of strings referring to this package
+     */
+    public function getNames();
+
+    /**
+     * Allows the solver to set an id for this package to refer to it.
+     *
+     * @param int $id
+     */
+    public function setId($id);
+
+    /**
+     * Retrieves the package's id set through setId
+     *
+     * @return int The previously set package id
+     */
+    public function getId();
+
+    /**
+     * Returns whether the package is a development virtual package or a concrete one
+     *
+     * @return bool
+     */
+    public function isDev();
+
+    /**
+     * Returns the package type, e.g. library
+     *
+     * @return string The package type
+     */
+    public function getType();
+
+    /**
+     * Returns the package targetDir property
+     *
+     * @return string The package targetDir
+     */
+    public function getTargetDir();
+
+    /**
+     * Returns the package extra data
+     *
+     * @return array The package extra data
+     */
+    public function getExtra();
+
+    /**
+     * Sets source from which this package was installed (source/dist).
+     *
+     * @param string $type source/dist
+     */
+    public function setInstallationSource($type);
+
+    /**
+     * Returns source from which this package was installed (source/dist).
+     *
+     * @param string $type source/dist
+     */
+    public function getInstallationSource();
+
+    /**
+     * Returns the repository type of this package, e.g. git, svn
+     *
+     * @return string The repository type
+     */
+    public function getSourceType();
+
+    /**
+     * Returns the repository url of this package, e.g. git://github.com/naderman/composer.git
+     *
+     * @return string The repository url
+     */
+    public function getSourceUrl();
+
+    /**
+     * Returns the repository reference of this package, e.g. master, 1.0.0 or a commit hash for git
+     *
+     * @return string The repository reference
+     */
+    public function getSourceReference();
+
+    /**
+     * Returns the type of the distribution archive of this version, e.g. zip, tarball
+     *
+     * @return string The repository type
+     */
+    public function getDistType();
+
+    /**
+     * Returns the url of the distribution archive of this version
+     *
+     * @return string
+     */
+    public function getDistUrl();
+
+    /**
+     * Returns the reference of the distribution archive of this version, e.g. master, 1.0.0 or a commit hash for git
+     *
+     * @return string
+     */
+    public function getDistReference();
+
+    /**
+     * Returns the sha1 checksum for the distribution archive of this version
+     *
+     * @return string
+     */
+    public function getDistSha1Checksum();
+
+    /**
+     * Returns the version of this package
+     *
+     * @return string version
+     */
+    public function getVersion();
+
+    /**
+     * Returns the pretty (i.e. non-normalized) version string of this package
+     *
+     * @return string version
+     */
+    public function getPrettyVersion();
+
+    /**
+     * Returns the stability of this package: one of (dev, alpha, beta, RC, stable)
+     *
+     * @return string
+     */
+    public function getStability();
+
+    /**
+     * Returns a set of links to packages which need to be installed before
+     * this package can be installed
+     *
+     * @return array An array of package links defining required packages
+     */
+    public function getRequires();
+
+    /**
+     * Returns a set of links to packages which must not be installed at the
+     * same time as this package
+     *
+     * @return array An array of package links defining conflicting packages
+     */
+    public function getConflicts();
+
+    /**
+     * Returns a set of links to virtual packages that are provided through
+     * this package
+     *
+     * @return array An array of package links defining provided packages
+     */
+    public function getProvides();
+
+    /**
+     * Returns a set of links to packages which can alternatively be
+     * satisfied by installing this package
+     *
+     * @return array An array of package links defining replaced packages
+     */
+    public function getReplaces();
+
+    /**
+     * Returns a set of links to packages which are required to develop
+     * this package. These are installed if in dev mode.
+     *
+     * @return array An array of package links defining packages required for development
+     */
+    public function getDevRequires();
+
+    /**
+     * Returns a set of package names and reasons why they are useful in
+     * combination with this package.
+     *
+     * @return array An array of package suggestions with descriptions
+     */
+    public function getSuggests();
+
+    /**
+     * Returns an associative array of autoloading rules
+     *
+     * {"<type>": {"<namespace": "<directory>"}}
+     *
+     * Type is either "psr-0" or "pear". Namespaces are mapped to directories
+     * for autoloading using the type specified.
+     *
+     * @return array Mapping of autoloading rules
+     */
+    public function getAutoload();
+
+    /**
+     * Returns a list of directories which should get added to PHP's
+     * include path.
+     *
+     * @return array
+     */
+    public function getIncludePaths();
+
+    /**
+     * Stores a reference to the repository that owns the package
+     *
+     * @param RepositoryInterface $repository
+     */
+    public function setRepository(RepositoryInterface $repository);
+
+    /**
+     * Returns a reference to the repository that owns the package
+     *
+     * @return RepositoryInterface
+     */
+    public function getRepository();
+
+    /**
+     * Returns the package binaries
+     *
+     * @return array
+     */
+    public function getBinaries();
+
+    /**
+     * Returns a version this package should be aliased to
+     *
+     * @return string
+     */
+    public function getAlias();
+
+    /**
+     * Returns a non-normalized version this package should be aliased to
+     *
+     * @return string
+     */
+    public function getPrettyAlias();
+
+    /**
+     * Returns package unique name, constructed from name and version.
+     *
+     * @return string
+     */
+    public function getUniqueName();
+
+    /**
+     * Converts the package into a readable and unique string
+     *
+     * @return string
+     */
+    public function __toString();
+
+    /**
+     * Converts the package into a pretty readable string
+     *
+     * @return string
+     */
+    public function getPrettyString();
+}

+ 6 - 288
src/Composer/Package/PackageInterface.php

@@ -16,149 +16,12 @@ use Composer\Package\LinkConstraint\LinkConstraintInterface;
 use Composer\Repository\RepositoryInterface;
 
 /**
+ * Defines package metadata that is not necessarily needed for solving and installing packages
+ *
  * @author Nils Adermann <naderman@naderman.de>
  */
-interface PackageInterface
+interface PackageInterface extends CorePackageInterface
 {
-    /**
-     * Returns the package's name without version info, thus not a unique identifier
-     *
-     * @return string package name
-     */
-    public function getName();
-
-    /**
-     * Returns the package's pretty (i.e. with proper case) name
-     *
-     * @return string package name
-     */
-    public function getPrettyName();
-
-    /**
-     * Returns a set of names that could refer to this package
-     *
-     * No version or release type information should be included in any of the
-     * names. Provided or replaced package names need to be returned as well.
-     *
-     * @return array An array of strings referring to this package
-     */
-    public function getNames();
-
-    /**
-     * Allows the solver to set an id for this package to refer to it.
-     *
-     * @param int $id
-     */
-    public function setId($id);
-
-    /**
-     * Retrieves the package's id set through setId
-     *
-     * @return int The previously set package id
-     */
-    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
-     *
-     * @return bool
-     */
-    public function isDev();
-
-    /**
-     * Returns the package type, e.g. library
-     *
-     * @return string The package type
-     */
-    public function getType();
-
-    /**
-     * Returns the package targetDir property
-     *
-     * @return string The package targetDir
-     */
-    public function getTargetDir();
-
-    /**
-     * Returns the package extra data
-     *
-     * @return array The package extra data
-     */
-    public function getExtra();
-
-    /**
-     * Sets source from which this package was installed (source/dist).
-     *
-     * @param string $type source/dist
-     */
-    public function setInstallationSource($type);
-
-    /**
-     * Returns source from which this package was installed (source/dist).
-     *
-     * @param string $type source/dist
-     */
-    public function getInstallationSource();
-
-    /**
-     * Returns the repository type of this package, e.g. git, svn
-     *
-     * @return string The repository type
-     */
-    public function getSourceType();
-
-    /**
-     * Returns the repository url of this package, e.g. git://github.com/naderman/composer.git
-     *
-     * @return string The repository url
-     */
-    public function getSourceUrl();
-
-    /**
-     * Returns the repository reference of this package, e.g. master, 1.0.0 or a commit hash for git
-     *
-     * @return string The repository reference
-     */
-    public function getSourceReference();
-
-    /**
-     * Returns the type of the distribution archive of this version, e.g. zip, tarball
-     *
-     * @return string The repository type
-     */
-    public function getDistType();
-
-    /**
-     * Returns the url of the distribution archive of this version
-     *
-     * @return string
-     */
-    public function getDistUrl();
-
-    /**
-     * Returns the reference of the distribution archive of this version, e.g. master, 1.0.0 or a commit hash for git
-     *
-     * @return string
-     */
-    public function getDistReference();
-
-    /**
-     * Returns the sha1 checksum for the distribution archive of this version
-     *
-     * @return string
-     */
-    public function getDistSha1Checksum();
-
     /**
      * Returns the scripts of this package
      *
@@ -166,102 +29,6 @@ interface PackageInterface
      */
     public function getScripts();
 
-    /**
-     * Returns the version of this package
-     *
-     * @return string version
-     */
-    public function getVersion();
-
-    /**
-     * Returns the pretty (i.e. non-normalized) version string of this package
-     *
-     * @return string version
-     */
-    public function getPrettyVersion();
-
-    /**
-     * Returns the stability of this package: one of (dev, alpha, beta, RC, stable)
-     *
-     * @return string
-     */
-    public function getStability();
-
-    /**
-     * Returns the package license, e.g. MIT, BSD, GPL
-     *
-     * @return array The package licenses
-     */
-    public function getLicense();
-
-    /**
-     * Returns a set of links to packages which need to be installed before
-     * this package can be installed
-     *
-     * @return array An array of package links defining required packages
-     */
-    public function getRequires();
-
-    /**
-     * Returns a set of links to packages which must not be installed at the
-     * same time as this package
-     *
-     * @return array An array of package links defining conflicting packages
-     */
-    public function getConflicts();
-
-    /**
-     * Returns a set of links to virtual packages that are provided through
-     * this package
-     *
-     * @return array An array of package links defining provided packages
-     */
-    public function getProvides();
-
-    /**
-     * Returns a set of links to packages which can alternatively be
-     * satisfied by installing this package
-     *
-     * @return array An array of package links defining replaced packages
-     */
-    public function getReplaces();
-
-    /**
-     * Returns a set of links to packages which are required to develop
-     * this package. These are installed if in dev mode.
-     *
-     * @return array An array of package links defining packages required for development
-     */
-    public function getDevRequires();
-
-    /**
-     * Returns a set of package names and reasons why they are useful in
-     * combination with this package.
-     *
-     * @return array An array of package suggestions with descriptions
-     */
-    public function getSuggests();
-
-    /**
-     * Returns an associative array of autoloading rules
-     *
-     * {"<type>": {"<namespace": "<directory>"}}
-     *
-     * Type is either "psr-0" or "pear". Namespaces are mapped to directories
-     * for autoloading using the type specified.
-     *
-     * @return array Mapping of autoloading rules
-     */
-    public function getAutoload();
-
-    /**
-     * Returns a list of directories which should get added to PHP's
-     * include path.
-     *
-     * @return array
-     */
-    public function getIncludePaths();
-
     /**
      * Returns an array of repositories
      *
@@ -272,18 +39,11 @@ interface PackageInterface
     public function getRepositories();
 
     /**
-     * Stores a reference to the repository that owns the package
-     *
-     * @param RepositoryInterface $repository
-     */
-    public function setRepository(RepositoryInterface $repository);
-
-    /**
-     * Returns a reference to the repository that owns the package
+     * Returns the package license, e.g. MIT, BSD, GPL
      *
-     * @return RepositoryInterface
+     * @return array The package licenses
      */
-    public function getRepository();
+    public function getLicense();
 
     /**
      * Returns the release date of the package
@@ -306,13 +66,6 @@ interface PackageInterface
      */
     public function getDescription();
 
-    /**
-     * Returns the package binaries
-     *
-     * @return array
-     */
-    public function getBinaries();
-
     /**
      * Returns the package homepage
      *
@@ -329,41 +82,6 @@ interface PackageInterface
      */
     public function getAuthors();
 
-    /**
-     * Returns a version this package should be aliased to
-     *
-     * @return string
-     */
-    public function getAlias();
-
-    /**
-     * Returns a non-normalized version this package should be aliased to
-     *
-     * @return string
-     */
-    public function getPrettyAlias();
-
-    /**
-     * Returns package unique name, constructed from name and version.
-     *
-     * @return string
-     */
-    public function getUniqueName();
-
-    /**
-     * Converts the package into a readable and unique string
-     *
-     * @return string
-     */
-    public function __toString();
-
-    /**
-     * Converts the package into a pretty readable string
-     *
-     * @return string
-     */
-    public function getPrettyString();
-
     /**
      * Returns the support information
      *

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

@@ -0,0 +1,49 @@
+<?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\LinkConstraint\LinkConstraintInterface;
+use Composer\Repository\RepositoryInterface;
+
+/**
+ * Defines additional fields that are only needed for the root package
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+interface RootPackageInterface extends PackageInterface
+{
+    /**
+     * 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();
+}

+ 64 - 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,15 @@ 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 $minimalPackages;
+    protected $loader;
 
     public function __construct(array $repoConfig, IOInterface $io, Config $config)
     {
@@ -47,6 +49,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 +81,60 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
         @file_get_contents($url, false, $context);
     }
 
+    public function getMinimalPackages()
+    {
+        if (isset($this->minimalPackages)) {
+            return $this->minimalPackages;
+        }
+
+        $repoData = $this->loadDataFromServer();
+
+        $this->minimalPackages = array();
+        $versionParser = new VersionParser;
+
+        foreach ($repoData 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'];
+            }
+
+            $this->minimalPackages[] = $data;
+        }
+
+        return $this->minimalPackages;
+    }
+
+    public function loadPackage(array $data, $id)
+    {
+        $package = $this->loader->load($data);
+        $package->setId($id);
+        $package->setRepository($data['repo']);
+
+        return $package;
+    }
+
     protected function initialize()
     {
         parent::initialize();
 
+        $repoData = $this->loadDataFromServer();
+
+        foreach ($repoData as $package) {
+            $this->addPackage($this->loader->load($package));
+        }
+    }
+
+    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 +162,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 +183,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 +197,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;
     }
 }

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

@@ -0,0 +1,30 @@
+<?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\PackageInterface;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+interface StreamableRepositoryInterface extends RepositoryInterface
+{
+    /**
+     * Return partial package data without loading them all to save on memory
+     *
+     * @return array
+     */
+    public function getMinimalPackages();
+
+    public function loadPackage(array $data, $id);
+}