PatchRegistry.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Setup\Patch;
  7. /**
  8. * Allows to read all patches through the whole system
  9. */
  10. class PatchRegistry implements \IteratorAggregate
  11. {
  12. /**
  13. *
  14. * @var array
  15. */
  16. private $dependents = [];
  17. /**
  18. * @var string[]
  19. */
  20. private $patches = [];
  21. /**
  22. * @var PatchFactory
  23. */
  24. private $patchFactory;
  25. /**
  26. * This classes need to do revert
  27. *
  28. * @var string[]
  29. */
  30. private $appliedPatches = [];
  31. /**
  32. * @var PatchHistory
  33. */
  34. private $patchHistory;
  35. /**
  36. * @var \Iterator
  37. */
  38. private $iterator = null;
  39. /**
  40. * @var \Iterator
  41. */
  42. private $reverseIterator = null;
  43. /**
  44. * @var array
  45. */
  46. private $cyclomaticStack = [];
  47. /**
  48. * PatchRegistry constructor.
  49. * @param PatchFactory $patchFactory
  50. * @param PatchHistory $patchHistory
  51. */
  52. public function __construct(PatchFactory $patchFactory, PatchHistory $patchHistory)
  53. {
  54. $this->patchFactory = $patchFactory;
  55. $this->patchHistory = $patchHistory;
  56. }
  57. /**
  58. * Register all dependents to patch
  59. *
  60. * @param string | DependentPatchInterface $patchName
  61. */
  62. private function registerDependents(string $patchName)
  63. {
  64. $dependencies = $patchName::getDependencies();
  65. foreach ($dependencies as $dependency) {
  66. $this->dependents[$dependency][] = $patchName;
  67. }
  68. }
  69. /**
  70. * Register patch and create chain of patches
  71. *
  72. * @param string $patchName
  73. * @return PatchInterface | bool
  74. */
  75. public function registerPatch(string $patchName)
  76. {
  77. if ($this->patchHistory->isApplied($patchName)) {
  78. $this->appliedPatches[$patchName] = $patchName;
  79. $this->registerDependents($patchName);
  80. return false;
  81. }
  82. if (isset($this->patches[$patchName])) {
  83. return $this->patches[$patchName];
  84. }
  85. $this->patches[$patchName] = $patchName;
  86. return $patchName;
  87. }
  88. /**
  89. * Retrieve all patches, that depends on current one
  90. *
  91. * @param string $patch
  92. * @return string[]
  93. */
  94. private function getDependentPatches(string $patch)
  95. {
  96. $patches = [];
  97. $patchName = $patch;
  98. /**
  99. * Let`s check if patch is dependency for other patches
  100. */
  101. if (isset($this->dependents[$patchName])) {
  102. foreach ($this->dependents[$patchName] as $dependent) {
  103. if (isset($this->appliedPatches[$dependent])) {
  104. $dependent = $this->appliedPatches[$dependent];
  105. $patches = array_replace($patches, $this->getDependentPatches($dependent));
  106. $patches[$dependent] = $dependent;
  107. unset($this->appliedPatches[$dependent]);
  108. }
  109. }
  110. }
  111. return $patches;
  112. }
  113. /**
  114. * @param string $patch
  115. * @return string[]
  116. */
  117. private function getDependencies(string $patch)
  118. {
  119. $depInstances = [];
  120. $deps = call_user_func([$patch, 'getDependencies']);
  121. $this->cyclomaticStack[$patch] = true;
  122. foreach ($deps as $dep) {
  123. if (isset($this->cyclomaticStack[$dep])) {
  124. throw new \LogicException("Cyclomatic dependency during patch installation");
  125. }
  126. $depInstance = $this->registerPatch($dep);
  127. /**
  128. * If a patch already have applied dependency - than we definently know
  129. * that all other dependencies in dependency chain are applied too, so we can skip this dep
  130. */
  131. if (!$depInstance) {
  132. continue;
  133. }
  134. $depInstances = array_replace($depInstances, $this->getDependencies($this->patches[$dep]));
  135. $depInstances[$depInstance] = $depInstance;
  136. }
  137. unset($this->cyclomaticStack[$patch]);
  138. return $depInstances;
  139. }
  140. /**
  141. * If you want to uninstall system, there you will run all patches in reverse order
  142. *
  143. * But note, that patches also have dependencies, and if patch is dependency to any other patch
  144. * you will to revert it dependencies first and only then patch
  145. *
  146. * @return \ArrayIterator
  147. */
  148. public function getReverseIterator()
  149. {
  150. if ($this->reverseIterator === null) {
  151. $reversePatches = [];
  152. while (!empty($this->appliedPatches)) {
  153. $patch = array_pop($this->appliedPatches);
  154. $reversePatches = array_replace($reversePatches, $this->getDependentPatches($patch));
  155. $reversePatches[$patch] = $patch;
  156. }
  157. $this->reverseIterator = new \ArrayIterator($reversePatches);
  158. }
  159. return $this->reverseIterator;
  160. }
  161. /**
  162. * Retrieve iterator of all patch instances
  163. *
  164. * If patch have dependencies, than first of all dependencies should be installed and only then desired patch
  165. *
  166. * @return \ArrayIterator
  167. */
  168. public function getIterator()
  169. {
  170. if ($this->iterator === null) {
  171. $installPatches = [];
  172. $patchInstances = $this->patches;
  173. while (!empty($patchInstances)) {
  174. $firstPatch = array_shift($patchInstances);
  175. $deps = $this->getDependencies($firstPatch);
  176. /**
  177. * Remove deps from patchInstances
  178. */
  179. foreach ($deps as $dep) {
  180. unset($patchInstances[$dep]);
  181. }
  182. $installPatches = array_replace($installPatches, $deps);
  183. $installPatches[$firstPatch] = $firstPatch;
  184. }
  185. $this->iterator = new \ArrayIterator($installPatches);
  186. }
  187. return $this->iterator;
  188. }
  189. }