Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view Chris@0: * the LICENSE file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Composer\Semver\Constraint; Chris@0: Chris@0: /** Chris@0: * Defines a constraint. Chris@0: */ Chris@0: class Constraint implements ConstraintInterface Chris@0: { Chris@0: /* operator integer values */ Chris@0: const OP_EQ = 0; Chris@0: const OP_LT = 1; Chris@0: const OP_LE = 2; Chris@0: const OP_GT = 3; Chris@0: const OP_GE = 4; Chris@0: const OP_NE = 5; Chris@0: Chris@0: /** Chris@0: * Operator to integer translation table. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: private static $transOpStr = array( Chris@0: '=' => self::OP_EQ, Chris@0: '==' => self::OP_EQ, Chris@0: '<' => self::OP_LT, Chris@0: '<=' => self::OP_LE, Chris@0: '>' => self::OP_GT, Chris@0: '>=' => self::OP_GE, Chris@0: '<>' => self::OP_NE, Chris@0: '!=' => self::OP_NE, Chris@0: ); Chris@0: Chris@0: /** Chris@0: * Integer to operator translation table. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: private static $transOpInt = array( Chris@0: self::OP_EQ => '==', Chris@0: self::OP_LT => '<', Chris@0: self::OP_LE => '<=', Chris@0: self::OP_GT => '>', Chris@0: self::OP_GE => '>=', Chris@0: self::OP_NE => '!=', Chris@0: ); Chris@0: Chris@0: /** @var string */ Chris@0: protected $operator; Chris@0: Chris@0: /** @var string */ Chris@0: protected $version; Chris@0: Chris@0: /** @var string */ Chris@0: protected $prettyString; Chris@0: Chris@0: /** Chris@0: * @param ConstraintInterface $provider Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function matches(ConstraintInterface $provider) Chris@0: { Chris@0: if ($provider instanceof $this) { Chris@0: return $this->matchSpecific($provider); Chris@0: } Chris@0: Chris@0: // turn matching around to find a match Chris@0: return $provider->matches($this); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $prettyString Chris@0: */ Chris@0: public function setPrettyString($prettyString) Chris@0: { Chris@0: $this->prettyString = $prettyString; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return string Chris@0: */ Chris@0: public function getPrettyString() Chris@0: { Chris@0: if ($this->prettyString) { Chris@0: return $this->prettyString; Chris@0: } Chris@0: Chris@0: return $this->__toString(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get all supported comparison operators. Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public static function getSupportedOperators() Chris@0: { Chris@0: return array_keys(self::$transOpStr); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets operator and version to compare with. Chris@0: * Chris@0: * @param string $operator Chris@0: * @param string $version Chris@0: * Chris@0: * @throws \InvalidArgumentException if invalid operator is given. Chris@0: */ Chris@0: public function __construct($operator, $version) Chris@0: { Chris@0: if (!isset(self::$transOpStr[$operator])) { Chris@0: throw new \InvalidArgumentException(sprintf( Chris@0: 'Invalid operator "%s" given, expected one of: %s', Chris@0: $operator, Chris@0: implode(', ', self::getSupportedOperators()) Chris@0: )); Chris@0: } Chris@0: Chris@0: $this->operator = self::$transOpStr[$operator]; Chris@0: $this->version = $version; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $a Chris@0: * @param string $b Chris@0: * @param string $operator Chris@0: * @param bool $compareBranches Chris@0: * Chris@0: * @throws \InvalidArgumentException if invalid operator is given. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function versionCompare($a, $b, $operator, $compareBranches = false) Chris@0: { Chris@0: if (!isset(self::$transOpStr[$operator])) { Chris@0: throw new \InvalidArgumentException(sprintf( Chris@0: 'Invalid operator "%s" given, expected one of: %s', Chris@0: $operator, Chris@0: implode(', ', self::getSupportedOperators()) Chris@0: )); Chris@0: } Chris@0: Chris@0: $aIsBranch = 'dev-' === substr($a, 0, 4); Chris@0: $bIsBranch = 'dev-' === substr($b, 0, 4); Chris@0: Chris@0: if ($aIsBranch && $bIsBranch) { Chris@0: return $operator === '==' && $a === $b; Chris@0: } Chris@0: Chris@0: // when branches are not comparable, we make sure dev branches never match anything Chris@0: if (!$compareBranches && ($aIsBranch || $bIsBranch)) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: return version_compare($a, $b, $operator); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param Constraint $provider Chris@0: * @param bool $compareBranches Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function matchSpecific(Constraint $provider, $compareBranches = false) Chris@0: { Chris@0: $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]); Chris@0: $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]); Chris@0: Chris@0: $isEqualOp = self::OP_EQ === $this->operator; Chris@0: $isNonEqualOp = self::OP_NE === $this->operator; Chris@0: $isProviderEqualOp = self::OP_EQ === $provider->operator; Chris@0: $isProviderNonEqualOp = self::OP_NE === $provider->operator; Chris@0: Chris@0: // '!=' operator is match when other operator is not '==' operator or version is not match Chris@0: // these kinds of comparisons always have a solution Chris@0: if ($isNonEqualOp || $isProviderNonEqualOp) { Chris@0: return !$isEqualOp && !$isProviderEqualOp Chris@0: || $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); Chris@0: } Chris@0: Chris@0: // an example for the condition is <= 2.0 & < 1.0 Chris@0: // these kinds of comparisons always have a solution Chris@0: if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) { Chris@0: return true; Chris@0: } Chris@0: Chris@0: if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->operator], $compareBranches)) { Chris@0: // special case, e.g. require >= 1.0 and provide < 1.0 Chris@0: // 1.0 >= 1.0 but 1.0 is outside of the provided interval Chris@0: if ($provider->version === $this->version Chris@0: && self::$transOpInt[$provider->operator] === $providerNoEqualOp Chris@0: && self::$transOpInt[$this->operator] !== $noEqualOp) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return string Chris@0: */ Chris@0: public function __toString() Chris@0: { Chris@0: return self::$transOpInt[$this->operator] . ' ' . $this->version; Chris@0: } Chris@0: }