Decisions.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\DependencyResolver;
  12. /**
  13. * Stores decisions on installing, removing or keeping packages
  14. *
  15. * @author Nils Adermann <naderman@naderman.de>
  16. */
  17. class Decisions implements \Iterator, \Countable
  18. {
  19. const DECISION_LITERAL = 0;
  20. const DECISION_REASON = 1;
  21. protected $pool;
  22. protected $decisionMap;
  23. protected $decisionQueue = array();
  24. public function __construct($pool)
  25. {
  26. $this->pool = $pool;
  27. $this->decisionMap = array();
  28. }
  29. public function decide($literal, $level, $why)
  30. {
  31. $this->addDecision($literal, $level);
  32. $this->decisionQueue[] = array(
  33. self::DECISION_LITERAL => $literal,
  34. self::DECISION_REASON => $why,
  35. );
  36. }
  37. public function satisfy($literal)
  38. {
  39. $packageId = abs($literal);
  40. return (
  41. $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 ||
  42. $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0
  43. );
  44. }
  45. public function conflict($literal)
  46. {
  47. $packageId = abs($literal);
  48. return (
  49. (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) ||
  50. (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0)
  51. );
  52. }
  53. public function decided($literalOrPackageId)
  54. {
  55. return !empty($this->decisionMap[abs($literalOrPackageId)]);
  56. }
  57. public function undecided($literalOrPackageId)
  58. {
  59. return empty($this->decisionMap[abs($literalOrPackageId)]);
  60. }
  61. public function decidedInstall($literalOrPackageId)
  62. {
  63. $packageId = abs($literalOrPackageId);
  64. return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0;
  65. }
  66. public function decisionLevel($literalOrPackageId)
  67. {
  68. $packageId = abs($literalOrPackageId);
  69. if (isset($this->decisionMap[$packageId])) {
  70. return abs($this->decisionMap[$packageId]);
  71. }
  72. return 0;
  73. }
  74. public function decisionRule($literalOrPackageId)
  75. {
  76. $packageId = abs($literalOrPackageId);
  77. foreach ($this->decisionQueue as $i => $decision) {
  78. if ($packageId === abs($decision[self::DECISION_LITERAL])) {
  79. return $decision[self::DECISION_REASON];
  80. }
  81. }
  82. return null;
  83. }
  84. public function atOffset($queueOffset)
  85. {
  86. return $this->decisionQueue[$queueOffset];
  87. }
  88. public function validOffset($queueOffset)
  89. {
  90. return $queueOffset >= 0 && $queueOffset < count($this->decisionQueue);
  91. }
  92. public function lastReason()
  93. {
  94. return $this->decisionQueue[count($this->decisionQueue) - 1][self::DECISION_REASON];
  95. }
  96. public function lastLiteral()
  97. {
  98. return $this->decisionQueue[count($this->decisionQueue) - 1][self::DECISION_LITERAL];
  99. }
  100. public function reset()
  101. {
  102. while ($decision = array_pop($this->decisionQueue)) {
  103. $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
  104. }
  105. }
  106. public function resetToOffset($offset)
  107. {
  108. while (count($this->decisionQueue) > $offset + 1) {
  109. $decision = array_pop($this->decisionQueue);
  110. $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
  111. }
  112. }
  113. public function revertLast()
  114. {
  115. $this->decisionMap[abs($this->lastLiteral())] = 0;
  116. array_pop($this->decisionQueue);
  117. }
  118. public function count()
  119. {
  120. return count($this->decisionQueue);
  121. }
  122. public function rewind()
  123. {
  124. end($this->decisionQueue);
  125. }
  126. public function current()
  127. {
  128. return current($this->decisionQueue);
  129. }
  130. public function key()
  131. {
  132. return key($this->decisionQueue);
  133. }
  134. public function next()
  135. {
  136. return prev($this->decisionQueue);
  137. }
  138. public function valid()
  139. {
  140. return false !== current($this->decisionQueue);
  141. }
  142. public function isEmpty()
  143. {
  144. return count($this->decisionQueue) === 0;
  145. }
  146. protected function addDecision($literal, $level)
  147. {
  148. $packageId = abs($literal);
  149. $previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null;
  150. if ($previousDecision != 0) {
  151. $literalString = $this->pool->literalToString($literal);
  152. $package = $this->pool->literalToPackage($literal);
  153. throw new SolverBugException(
  154. "Trying to decide $literalString on level $level, even though $package was previously decided as ".(int) $previousDecision."."
  155. );
  156. }
  157. if ($literal > 0) {
  158. $this->decisionMap[$packageId] = $level;
  159. } else {
  160. $this->decisionMap[$packageId] = -$level;
  161. }
  162. }
  163. }