Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. VersionSelector.php

class VersionSelector

Selects the best possible version for a package

@author Ryan Weaver <ryan@knpuniversity.com> @author Jordi Boggiano <j.boggiano@seld.be>

Hierarchy

  • class \Composer\Package\Version\VersionSelector

Expanded class hierarchy of VersionSelector

7 files declare their use of VersionSelector
ArchiveCommand.php in vendor/composer/composer/src/Composer/Command/ArchiveCommand.php
CreateProjectCommand.php in vendor/composer/composer/src/Composer/Command/CreateProjectCommand.php
PackageDiscoveryTrait.php in vendor/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php
Plugin.php in vendor/php-http/discovery/src/Composer/Plugin.php
RequireCommand.php in vendor/composer/composer/src/Composer/Command/RequireCommand.php

... See full list

File

vendor/composer/composer/src/Composer/Package/Version/VersionSelector.php, line 38

Namespace

Composer\Package\Version
View source
class VersionSelector {
    
    /** @var RepositorySet */
    private $repositorySet;
    
    /** @var array<string, ConstraintInterface[]> */
    private $platformConstraints = [];
    
    /** @var VersionParser */
    private $parser;
    
    /**
     * @param PlatformRepository $platformRepo If passed in, the versions found will be filtered against their requirements to eliminate any not matching the current platform packages
     */
    public function __construct(RepositorySet $repositorySet, ?PlatformRepository $platformRepo = null) {
        $this->repositorySet = $repositorySet;
        if ($platformRepo) {
            foreach ($platformRepo->getPackages() as $package) {
                $this->platformConstraints[$package->getName()][] = new Constraint('==', $package->getVersion());
            }
        }
    }
    
    /**
     * Given a package name and optional version, returns the latest PackageInterface
     * that matches.
     *
     * @param string                                           $targetPackageVersion
     * @param PlatformRequirementFilterInterface|bool|string[] $platformRequirementFilter
     * @param IOInterface|null                                 $io                        If passed, warnings will be output there in case versions cannot be selected due to platform requirements
     * @param callable(PackageInterface):bool|bool             $showWarnings
     * @return PackageInterface|false
     */
    public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null, $showWarnings = true) {
        if (!isset(BasePackage::STABILITIES[$preferredStability])) {
            // If you get this, maybe you are still relying on the Composer 1.x signature where the 3rd arg was the php version
            throw new \UnexpectedValueException('Expected a valid stability name as 3rd argument, got ' . $preferredStability);
        }
        if (null === $platformRequirementFilter) {
            $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();
        }
        elseif (!$platformRequirementFilter instanceof PlatformRequirementFilterInterface) {
            trigger_error('VersionSelector::findBestCandidate with ignored platform reqs as bool|array is deprecated since Composer 2.2, use an instance of PlatformRequirementFilterInterface instead.', E_USER_DEPRECATED);
            $platformRequirementFilter = PlatformRequirementFilterFactory::fromBoolOrList($platformRequirementFilter);
        }
        $constraint = $targetPackageVersion ? $this->getParser()
            ->parseConstraints($targetPackageVersion) : null;
        $candidates = $this->repositorySet
            ->findPackages(strtolower($packageName), $constraint, $repoSetFlags);
        $minPriority = BasePackage::STABILITIES[$preferredStability];
        usort($candidates, static function (PackageInterface $a, PackageInterface $b) use ($minPriority) {
            $aPriority = $a->getStabilityPriority();
            $bPriority = $b->getStabilityPriority();
            // A is less stable than our preferred stability,
            // and B is more stable than A, select B
            if ($minPriority < $aPriority && $bPriority < $aPriority) {
                return 1;
            }
            // A is less stable than our preferred stability,
            // and B is less stable than A, select A
            if ($minPriority < $aPriority && $aPriority < $bPriority) {
                return -1;
            }
            // A is more stable than our preferred stability,
            // and B is less stable than preferred stability, select A
            if ($minPriority >= $aPriority && $minPriority < $bPriority) {
                return -1;
            }
            // select highest version of the two
            return version_compare($b->getVersion(), $a->getVersion());
        });
        if (count($this->platformConstraints) > 0 && !$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) {
            
            /** @var array<string, true> $alreadyWarnedNames */
            $alreadyWarnedNames = [];
            
            /** @var array<string, true> $alreadySeenNames */
            $alreadySeenNames = [];
            foreach ($candidates as $pkg) {
                $reqs = $pkg->getRequires();
                $skip = false;
                foreach ($reqs as $name => $link) {
                    if (!PlatformRepository::isPlatformPackage($name) || $platformRequirementFilter->isIgnored($name)) {
                        continue;
                    }
                    if (isset($this->platformConstraints[$name])) {
                        foreach ($this->platformConstraints[$name] as $providedConstraint) {
                            if ($link->getConstraint()
                                ->matches($providedConstraint)) {
                                // constraint satisfied, go to next require
                                continue 2;
                            }
                            if ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter && $platformRequirementFilter->isUpperBoundIgnored($name)) {
                                $filteredConstraint = $platformRequirementFilter->filterConstraint($name, $link->getConstraint());
                                if ($filteredConstraint->matches($providedConstraint)) {
                                    // constraint satisfied with the upper bound ignored, go to next require
                                    continue 2;
                                }
                            }
                        }
                        // constraint not satisfied
                        $reason = 'is not satisfied by your platform';
                    }
                    else {
                        // Package requires a platform package that is unknown on current platform.
                        // It means that current platform cannot validate this constraint and so package is not installable.
                        $reason = 'is missing from your platform';
                    }
                    $isLatestVersion = !isset($alreadySeenNames[$pkg->getName()]);
                    $alreadySeenNames[$pkg->getName()] = true;
                    if ($io !== null && ($showWarnings === true || is_callable($showWarnings) && $showWarnings($pkg))) {
                        $isFirstWarning = !isset($alreadyWarnedNames[$pkg->getName() . '/' . $link->getTarget()]);
                        $alreadyWarnedNames[$pkg->getName() . '/' . $link->getTarget()] = true;
                        $latest = $isLatestVersion ? "'s latest version" : '';
                        $io->writeError('<warning>Cannot use ' . $pkg->getPrettyName() . $latest . ' ' . $pkg->getPrettyVersion() . ' as it ' . $link->getDescription() . ' ' . $link->getTarget() . ' ' . $link->getPrettyConstraint() . ' which ' . $reason . '.</>', true, $isFirstWarning ? IOInterface::NORMAL : IOInterface::VERBOSE);
                    }
                    // skip candidate
                    $skip = true;
                }
                if ($skip) {
                    continue;
                }
                $package = $pkg;
                break;
            }
        }
        else {
            $package = count($candidates) > 0 ? $candidates[0] : null;
        }
        if (!isset($package)) {
            return false;
        }
        // if we end up with 9999999-dev as selected package, make sure we use the original version instead of the alias
        if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
            $package = $package->getAliasOf();
        }
        return $package;
    }
    
    /**
     * Given a concrete version, this returns a ^ constraint (when possible)
     * that should be used, for example, in composer.json.
     *
     * For example:
     *  * 1.2.1         -> ^1.2
     *  * 1.2.1.2       -> ^1.2
     *  * 1.2           -> ^1.2
     *  * v3.2.1        -> ^3.2
     *  * 2.0-beta.1    -> ^2.0@beta
     *  * dev-master    -> ^2.1@dev      (dev version with alias)
     *  * dev-master    -> dev-master    (dev versions are untouched)
     */
    public function findRecommendedRequireVersion(PackageInterface $package) : string {
        // Extensions which are versioned in sync with PHP should rather be required as "*" to simplify
        // the requires and have only one required version to change when bumping the php requirement
        if (0 === strpos($package->getName(), 'ext-')) {
            $phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
            $extVersion = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3));
            if ($phpVersion === $extVersion) {
                return '*';
            }
        }
        $version = $package->getVersion();
        if (!$package->isDev()) {
            return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability());
        }
        $loader = new ArrayLoader($this->getParser());
        $dumper = new ArrayDumper();
        $extra = $loader->getBranchAlias($dumper->dump($package));
        if ($extra && $extra !== VersionParser::DEFAULT_BRANCH_ALIAS) {
            $extra = Preg::replace('{^(\\d+\\.\\d+\\.\\d+)(\\.9999999)-dev$}', '$1.0', $extra, -1, $count);
            if ($count > 0) {
                $extra = str_replace('.9999999', '.0', $extra);
                return $this->transformVersion($extra, $extra, 'dev');
            }
        }
        return $package->getPrettyVersion();
    }
    private function transformVersion(string $version, string $prettyVersion, string $stability) : string {
        // attempt to transform 2.1.1 to 2.1
        // this allows you to upgrade through minor versions
        $semanticVersionParts = explode('.', $version);
        // check to see if we have a semver-looking version
        if (count($semanticVersionParts) === 4 && Preg::isMatch('{^\\d+\\D?}', $semanticVersionParts[3])) {
            // remove the last parts (i.e. the patch version number and any extra)
            if ($semanticVersionParts[0] === '0') {
                unset($semanticVersionParts[3]);
            }
            else {
                unset($semanticVersionParts[2], $semanticVersionParts[3]);
            }
            $version = implode('.', $semanticVersionParts);
        }
        else {
            return $prettyVersion;
        }
        // append stability flag if not default
        if ($stability !== 'stable') {
            $version .= '@' . $stability;
        }
        // 2.1 -> ^2.1
        return '^' . $version;
    }
    private function getParser() : VersionParser {
        if ($this->parser === null) {
            $this->parser = new VersionParser();
        }
        return $this->parser;
    }

}

Members

Title Sort descending Modifiers Object type Summary
VersionSelector::$parser private property @var VersionParser
VersionSelector::$platformConstraints private property @var array&lt;string, ConstraintInterface[]&gt;
VersionSelector::$repositorySet private property @var RepositorySet
VersionSelector::findBestCandidate public function Given a package name and optional version, returns the latest PackageInterface
that matches.
VersionSelector::findRecommendedRequireVersion public function Given a concrete version, this returns a ^ constraint (when possible)
that should be used, for example, in composer.json.
VersionSelector::getParser private function
VersionSelector::transformVersion private function
VersionSelector::__construct public function

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal