MergeParamsCommand.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. <?php
  2. declare(strict_types = 1);
  3. namespace Fgm\Drupal\Composer;
  4. use Symfony\Component\Console\Input\InputInterface;
  5. use Symfony\Component\Console\Output\OutputInterface;
  6. use Symfony\Component\Yaml\Yaml;
  7. /**
  8. * Merge params.local.yml into dist.params.local.yml.
  9. *
  10. * The former overrides the latter.
  11. *
  12. * @package Fgm\Drupal\Composer
  13. */
  14. class MergeParamsCommand extends BaseBuilderCommand {
  15. const DEFAULT_SITE = '_default';
  16. /**
  17. * The event triggering this command.
  18. *
  19. * @var string
  20. */
  21. protected $eventName;
  22. /**
  23. * Configures the current command.
  24. */
  25. public function configure() {
  26. parent::configure();
  27. $this->eventName = $this->getName();
  28. $this
  29. ->setName('build:merge_params')
  30. ->setDescription('Step 1: merges the params.local.yml with dist.params.local.yml.')
  31. ->setDefinition([])
  32. ->setHelp(
  33. <<<EOT
  34. Step 1 of a build, the build:merge_params command combines shared and per-environment
  35. parameters and generates merged.params.local.yml containing the merged result.
  36. EOT
  37. );
  38. }
  39. /**
  40. * Perform merging.
  41. *
  42. * @param array $dist
  43. * Distribution parameters (default).
  44. * @param array $local
  45. * Local parameters (overrides).
  46. *
  47. * @return array
  48. * The merge result.
  49. */
  50. protected function merge(array $dist, array $local): array {
  51. if (empty($local)) {
  52. return $dist;
  53. }
  54. // Merge local into defaults.
  55. $merged = static::mergeDeepArray([$dist, $local]);
  56. // Generate per-site data from settings/_default.
  57. $default = $merged['sites'][static::DEFAULT_SITE] ?? [];
  58. $sites = array_diff_key($merged['sites'], [static::DEFAULT_SITE => NULL]);
  59. $merged['sites'] = [];
  60. foreach ($sites as $name => $params) {
  61. $merged['sites'][$name] = static::mergeDeepArray([$default, $params]);
  62. }
  63. return $merged;
  64. }
  65. /**
  66. * Executes the current command.
  67. *
  68. * {@inheritDoc}
  69. */
  70. public function execute(InputInterface $input, OutputInterface $output) {
  71. $settingsPath = $this->getSettingsPath();
  72. $yaml = new Yaml();
  73. // Load defaults.
  74. $defaultsPath = "${settingsPath}/dist.params.local.yml";
  75. $realDefaultsPath = realpath($defaultsPath);
  76. if (empty($realDefaultsPath)) {
  77. $output->writeln("Failed to open $defaultsPath");
  78. return 1;
  79. }
  80. $defaults = $yaml->parseFile($realDefaultsPath);
  81. // Load local.
  82. $localPath = "${settingsPath}/params.local.yml";
  83. $realLocalPath = realpath($localPath);
  84. if (empty($realLocalPath)) {
  85. if ($output->isVerbose()) {
  86. $output->writeln("File $localPath not found, using only defaults");
  87. }
  88. $local = [];
  89. }
  90. else {
  91. $local = $yaml->parseFile($realLocalPath);
  92. }
  93. // Merge.
  94. $merged = $this->merge($defaults, $local);
  95. // Write.
  96. $ok = file_put_contents("${settingsPath}/merged.params.local.yml",
  97. $yaml->dump($merged, 10, 2));
  98. if (!$ok) {
  99. $output->writeln("Failed to write merged params.");
  100. return 2;
  101. }
  102. return 0;
  103. }
  104. /**
  105. * Merges multiple arrays, recursively, and returns the merged array.
  106. *
  107. * Only keyed arrays are merged recursively, indexed arrays are not merged :
  108. * the deepest array replace the less deep array. This is unlike the original
  109. * function introduced in Drupal 8.x, from which this derives.
  110. *
  111. * @param array $arrays
  112. * An arrays of arrays to merge.
  113. *
  114. * @return array
  115. * The merged array.
  116. *
  117. * @see \Fgm\Drupal\Composer\MergeParamsCommandTest::testMergeDeepArray()
  118. */
  119. public static function mergeDeepArray(array $arrays) {
  120. $result = [];
  121. foreach ($arrays as $array) {
  122. foreach ($array as $key => $value) {
  123. // Remove keys having NULL value to allow deletion of key previously
  124. // defined.
  125. if ($value === NULL) {
  126. unset($result[$key]);
  127. continue;
  128. }
  129. // Renumber integer keys as array_merge_recursive() does. Note that
  130. // PHP automatically converts array keys that are integer strings
  131. // (e.g., '1') to integers.
  132. if (is_int($key)) {
  133. // When an indexed array is overrided, the new values replace
  134. // all overrided values (no merge).
  135. if ($key === 0) {
  136. $result = [];
  137. }
  138. $result[] = $value;
  139. }
  140. // Recurse when both values are arrays.
  141. elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
  142. $result[$key] = static::mergeDeepArray([$result[$key], $value]);
  143. }
  144. // Otherwise, use the latter value, overriding any previous value.
  145. else {
  146. $result[$key] = $value;
  147. }
  148. }
  149. }
  150. return $result;
  151. }
  152. }