/src/Composer/DependencyResolver/Transaction.php

https://github.com/ecoleman/composer · PHP · 243 lines · 180 code · 48 blank · 15 comment · 21 complexity · e96d3fa2dce30014a27560ecca7c87b6 MD5 · raw file

  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. use Composer\Package\AliasPackage;
  13. use Composer\DependencyResolver\Operation;
  14. /**
  15. * @author Nils Adermann <naderman@naderman.de>
  16. */
  17. class Transaction
  18. {
  19. protected $policy;
  20. protected $pool;
  21. protected $installedMap;
  22. protected $decisions;
  23. protected $transaction;
  24. public function __construct($policy, $pool, $installedMap, $decisions)
  25. {
  26. $this->policy = $policy;
  27. $this->pool = $pool;
  28. $this->installedMap = $installedMap;
  29. $this->decisions = $decisions;
  30. $this->transaction = array();
  31. }
  32. public function getOperations()
  33. {
  34. $installMeansUpdateMap = $this->findUpdates();
  35. $updateMap = array();
  36. $installMap = array();
  37. $uninstallMap = array();
  38. foreach ($this->decisions as $i => $decision) {
  39. $literal = $decision[Decisions::DECISION_LITERAL];
  40. $reason = $decision[Decisions::DECISION_REASON];
  41. $package = $this->pool->literalToPackage($literal);
  42. // wanted & installed || !wanted & !installed
  43. if (($literal > 0) == (isset($this->installedMap[$package->getId()]))) {
  44. continue;
  45. }
  46. if ($literal > 0) {
  47. if (isset($installMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) {
  48. $source = $installMeansUpdateMap[abs($literal)];
  49. $updateMap[$package->getId()] = array(
  50. 'package' => $package,
  51. 'source' => $source,
  52. 'reason' => $reason,
  53. );
  54. // avoid updates to one package from multiple origins
  55. unset($installMeansUpdateMap[abs($literal)]);
  56. $ignoreRemove[$source->getId()] = true;
  57. } else {
  58. $installMap[$package->getId()] = array(
  59. 'package' => $package,
  60. 'reason' => $reason,
  61. );
  62. }
  63. }
  64. }
  65. foreach ($this->decisions as $i => $decision) {
  66. $literal = $decision[Decisions::DECISION_LITERAL];
  67. $package = $this->pool->literalToPackage($literal);
  68. if ($literal <= 0 &&
  69. isset($this->installedMap[$package->getId()]) &&
  70. !isset($ignoreRemove[$package->getId()])) {
  71. $uninstallMap[$package->getId()] = array(
  72. 'package' => $package,
  73. 'reason' => $reason,
  74. );
  75. }
  76. }
  77. $this->transactionFromMaps($installMap, $updateMap, $uninstallMap);
  78. return $this->transaction;
  79. }
  80. protected function transactionFromMaps($installMap, $updateMap, $uninstallMap)
  81. {
  82. $queue = array_map(function ($operation) {
  83. return $operation['package'];
  84. },
  85. $this->findRootPackages($installMap, $updateMap)
  86. );
  87. $visited = array();
  88. while (!empty($queue)) {
  89. $package = array_pop($queue);
  90. $packageId = $package->getId();
  91. if (!isset($visited[$packageId])) {
  92. array_push($queue, $package);
  93. if ($package instanceof AliasPackage) {
  94. array_push($queue, $package->getAliasOf());
  95. } else {
  96. foreach ($package->getRequires() as $link) {
  97. $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  98. foreach ($possibleRequires as $require) {
  99. array_push($queue, $require);
  100. }
  101. }
  102. }
  103. $visited[$package->getId()] = true;
  104. } else {
  105. if (isset($installMap[$packageId])) {
  106. $this->install(
  107. $installMap[$packageId]['package'],
  108. $installMap[$packageId]['reason']
  109. );
  110. unset($installMap[$packageId]);
  111. }
  112. if (isset($updateMap[$packageId])) {
  113. $this->update(
  114. $updateMap[$packageId]['source'],
  115. $updateMap[$packageId]['package'],
  116. $updateMap[$packageId]['reason']
  117. );
  118. unset($updateMap[$packageId]);
  119. }
  120. }
  121. }
  122. foreach ($uninstallMap as $uninstall) {
  123. $this->uninstall($uninstall['package'], $uninstall['reason']);
  124. }
  125. }
  126. protected function findRootPackages($installMap, $updateMap)
  127. {
  128. $packages = $installMap + $updateMap;
  129. $roots = $packages;
  130. foreach ($packages as $packageId => $operation) {
  131. $package = $operation['package'];
  132. if (!isset($roots[$packageId])) {
  133. continue;
  134. }
  135. foreach ($package->getRequires() as $link) {
  136. $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  137. foreach ($possibleRequires as $require) {
  138. unset($roots[$require->getId()]);
  139. }
  140. }
  141. }
  142. return $roots;
  143. }
  144. protected function findUpdates()
  145. {
  146. $installMeansUpdateMap = array();
  147. foreach ($this->decisions as $i => $decision) {
  148. $literal = $decision[Decisions::DECISION_LITERAL];
  149. $package = $this->pool->literalToPackage($literal);
  150. if ($package instanceof AliasPackage) {
  151. continue;
  152. }
  153. // !wanted & installed
  154. if ($literal <= 0 && isset($this->installedMap[$package->getId()])) {
  155. $updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);
  156. $literals = array($package->getId());
  157. foreach ($updates as $update) {
  158. $literals[] = $update->getId();
  159. }
  160. foreach ($literals as $updateLiteral) {
  161. if ($updateLiteral !== $literal) {
  162. $installMeansUpdateMap[abs($updateLiteral)] = $package;
  163. }
  164. }
  165. }
  166. }
  167. return $installMeansUpdateMap;
  168. }
  169. protected function install($package, $reason)
  170. {
  171. if ($package instanceof AliasPackage) {
  172. return $this->markAliasInstalled($package, $reason);
  173. }
  174. $this->transaction[] = new Operation\InstallOperation($package, $reason);
  175. }
  176. protected function update($from, $to, $reason)
  177. {
  178. $this->transaction[] = new Operation\UpdateOperation($from, $to, $reason);
  179. }
  180. protected function uninstall($package, $reason)
  181. {
  182. if ($package instanceof AliasPackage) {
  183. return $this->markAliasUninstalled($package, $reason);
  184. }
  185. $this->transaction[] = new Operation\UninstallOperation($package, $reason);
  186. }
  187. protected function markAliasInstalled($package, $reason)
  188. {
  189. $this->transaction[] = new Operation\MarkAliasInstalledOperation($package, $reason);
  190. }
  191. protected function markAliasUninstalled($package, $reason)
  192. {
  193. $this->transaction[] = new Operation\MarkAliasUninstalledOperation($package, $reason);
  194. }
  195. }