Constraint.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <?php
  2. /*
  3. * This file is part of composer/semver.
  4. *
  5. * (c) Composer <https://github.com/composer>
  6. *
  7. * For the full copyright and license information, please view
  8. * the LICENSE file that was distributed with this source code.
  9. */
  10. namespace Composer\Semver\Constraint;
  11. /**
  12. * Defines a constraint.
  13. */
  14. class Constraint implements ConstraintInterface
  15. {
  16. /* operator integer values */
  17. const OP_EQ = 0;
  18. const OP_LT = 1;
  19. const OP_LE = 2;
  20. const OP_GT = 3;
  21. const OP_GE = 4;
  22. const OP_NE = 5;
  23. /**
  24. * Operator to integer translation table.
  25. *
  26. * @var array
  27. */
  28. private static $transOpStr = array(
  29. '=' => self::OP_EQ,
  30. '==' => self::OP_EQ,
  31. '<' => self::OP_LT,
  32. '<=' => self::OP_LE,
  33. '>' => self::OP_GT,
  34. '>=' => self::OP_GE,
  35. '<>' => self::OP_NE,
  36. '!=' => self::OP_NE,
  37. );
  38. /**
  39. * Integer to operator translation table.
  40. *
  41. * @var array
  42. */
  43. private static $transOpInt = array(
  44. self::OP_EQ => '==',
  45. self::OP_LT => '<',
  46. self::OP_LE => '<=',
  47. self::OP_GT => '>',
  48. self::OP_GE => '>=',
  49. self::OP_NE => '!=',
  50. );
  51. /** @var string */
  52. protected $operator;
  53. /** @var string */
  54. protected $version;
  55. /** @var string */
  56. protected $prettyString;
  57. /**
  58. * @param ConstraintInterface $provider
  59. *
  60. * @return bool
  61. */
  62. public function matches(ConstraintInterface $provider)
  63. {
  64. if ($provider instanceof $this) {
  65. return $this->matchSpecific($provider);
  66. }
  67. // turn matching around to find a match
  68. return $provider->matches($this);
  69. }
  70. /**
  71. * @param string $prettyString
  72. */
  73. public function setPrettyString($prettyString)
  74. {
  75. $this->prettyString = $prettyString;
  76. }
  77. /**
  78. * @return string
  79. */
  80. public function getPrettyString()
  81. {
  82. if ($this->prettyString) {
  83. return $this->prettyString;
  84. }
  85. return $this->__toString();
  86. }
  87. /**
  88. * Get all supported comparison operators.
  89. *
  90. * @return array
  91. */
  92. public static function getSupportedOperators()
  93. {
  94. return array_keys(self::$transOpStr);
  95. }
  96. /**
  97. * Sets operator and version to compare with.
  98. *
  99. * @param string $operator
  100. * @param string $version
  101. *
  102. * @throws \InvalidArgumentException if invalid operator is given.
  103. */
  104. public function __construct($operator, $version)
  105. {
  106. if (!isset(self::$transOpStr[$operator])) {
  107. throw new \InvalidArgumentException(sprintf(
  108. 'Invalid operator "%s" given, expected one of: %s',
  109. $operator,
  110. implode(', ', self::getSupportedOperators())
  111. ));
  112. }
  113. $this->operator = self::$transOpStr[$operator];
  114. $this->version = $version;
  115. }
  116. /**
  117. * @param string $a
  118. * @param string $b
  119. * @param string $operator
  120. * @param bool $compareBranches
  121. *
  122. * @throws \InvalidArgumentException if invalid operator is given.
  123. *
  124. * @return bool
  125. */
  126. public function versionCompare($a, $b, $operator, $compareBranches = false)
  127. {
  128. if (!isset(self::$transOpStr[$operator])) {
  129. throw new \InvalidArgumentException(sprintf(
  130. 'Invalid operator "%s" given, expected one of: %s',
  131. $operator,
  132. implode(', ', self::getSupportedOperators())
  133. ));
  134. }
  135. $aIsBranch = 'dev-' === substr($a, 0, 4);
  136. $bIsBranch = 'dev-' === substr($b, 0, 4);
  137. if ($aIsBranch && $bIsBranch) {
  138. return $operator === '==' && $a === $b;
  139. }
  140. // when branches are not comparable, we make sure dev branches never match anything
  141. if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
  142. return false;
  143. }
  144. return version_compare($a, $b, $operator);
  145. }
  146. /**
  147. * @param Constraint $provider
  148. * @param bool $compareBranches
  149. *
  150. * @return bool
  151. */
  152. public function matchSpecific(Constraint $provider, $compareBranches = false)
  153. {
  154. $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]);
  155. $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]);
  156. $isEqualOp = self::OP_EQ === $this->operator;
  157. $isNonEqualOp = self::OP_NE === $this->operator;
  158. $isProviderEqualOp = self::OP_EQ === $provider->operator;
  159. $isProviderNonEqualOp = self::OP_NE === $provider->operator;
  160. // '!=' operator is match when other operator is not '==' operator or version is not match
  161. // these kinds of comparisons always have a solution
  162. if ($isNonEqualOp || $isProviderNonEqualOp) {
  163. return (!$isEqualOp && !$isProviderEqualOp)
  164. || $this->versionCompare($provider->version, $this->version, '!=', $compareBranches);
  165. }
  166. // an example for the condition is <= 2.0 & < 1.0
  167. // these kinds of comparisons always have a solution
  168. if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) {
  169. return true;
  170. }
  171. if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->operator], $compareBranches)) {
  172. // special case, e.g. require >= 1.0 and provide < 1.0
  173. // 1.0 >= 1.0 but 1.0 is outside of the provided interval
  174. return !($provider->version === $this->version
  175. && self::$transOpInt[$provider->operator] === $providerNoEqualOp
  176. && self::$transOpInt[$this->operator] !== $noEqualOp);
  177. }
  178. return false;
  179. }
  180. /**
  181. * @return string
  182. */
  183. public function __toString()
  184. {
  185. return self::$transOpInt[$this->operator] . ' ' . $this->version;
  186. }
  187. }