BuildSettingsCommand.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. declare(strict_types = 1);
  3. namespace Fgm\Drupal\Composer;
  4. use Drupal\Component\Utility\NestedArray;
  5. use Symfony\Component\Console\Input\InputArgument;
  6. use Symfony\Component\Console\Input\InputDefinition;
  7. use Symfony\Component\Console\Input\InputInterface;
  8. use Symfony\Component\Console\Output\OutputInterface;
  9. use Symfony\Component\Yaml\Exception\ParseException;
  10. use Symfony\Component\Yaml\Yaml;
  11. use Twig\Environment;
  12. use Twig\Extension\DebugExtension;
  13. use Twig\Loader\FilesystemLoader;
  14. use Twig\TemplateWrapper;
  15. class BuildSettingsCommand extends BaseBuilderCommand
  16. {
  17. /**
  18. * The name of the argument defining the file to generate.
  19. */
  20. const ARG_FILE = 'file';
  21. /**
  22. * @var string
  23. */
  24. protected $eventName;
  25. /**
  26. * {@inheritDoc}
  27. */
  28. public function configure()
  29. {
  30. parent::configure();
  31. $this->eventName = $this->getName();
  32. $this
  33. ->setName('build:settings')
  34. ->setDescription('Builds the *.settings.local.php files.')
  35. ->setDefinition(
  36. new InputDefinition([
  37. new InputArgument(static::ARG_FILE, InputArgument::OPTIONAL),
  38. ])
  39. )
  40. ->setHelp(
  41. <<<EOT
  42. The build:settings command combines shared and per-environment parameters and passes
  43. them to the settings.local.php.twig template to build the settings/(build|run).settings.local.php files.
  44. EOT
  45. );
  46. }
  47. /**
  48. * {@inheritDoc}
  49. */
  50. public function execute(InputInterface $input, OutputInterface $output)
  51. {
  52. [$templateName, $err] = $this->validateTemplateName($input, $output);
  53. if ($err) {
  54. return 1;
  55. };
  56. $settingsPath = $this->getSettingsPath();
  57. $templatePath = "${settingsPath}/${templateName}";
  58. $realTemplatePath = realpath($templatePath);
  59. if (empty($realTemplatePath)) {
  60. $output->writeln(sprintf("Could not load template %s: no such file", $templateName));
  61. return 2;
  62. }
  63. $paramsPath = "${settingsPath}/merged.params.local.yml";
  64. $realParamsPath = realpath($paramsPath);
  65. if (empty($realTemplatePath)) {
  66. $output->writeln(sprintf("Could not load parameters %s: no such file", $paramsPath));
  67. return 3;
  68. }
  69. $yaml = new Yaml();
  70. try {
  71. $params = $yaml->parseFile($realParamsPath);
  72. } catch (ParseException $e) {
  73. $output->writeln(sprintf("Could not parse %s: %s", $realParamsPath, $e->getMessage()));
  74. return 4;
  75. }
  76. $loader = new FilesystemLoader($settingsPath, $settingsPath);
  77. $twig = new Environment($loader, [
  78. 'auto_reload' => true,
  79. 'cache' => false,
  80. 'debug' => true,
  81. 'strict_variables' => true,
  82. ]);
  83. $twig->addExtension(new DebugExtension());
  84. $wrapper = $twig->load($templateName);
  85. foreach ($params['sites'] as $name => $siteParams) {
  86. foreach (['build', 'run'] as $stage) {
  87. $stageSettings = NestedArray::mergeDeepArray([
  88. $siteParams['settings']['both'] ?? [],
  89. $siteParams['settings'][$stage] ?? []
  90. ], true);
  91. $stageParams = $siteParams;
  92. $stageParams['settings'] = $stageSettings;
  93. $context = [
  94. 'instance' => $params['instance'],
  95. 'name' => $name,
  96. 'stage' => $stage,
  97. 'site' => $stageParams,
  98. ];
  99. $error = $this->render($wrapper, $context, $output);
  100. if ($error) {
  101. $output->writeln(sprintf("Failed rendering %s settings for site %s", $stage, $name));
  102. return 5;
  103. }
  104. }
  105. }
  106. }
  107. protected function render(TemplateWrapper $wrapper, array $context, OutputInterface $output): int
  108. {
  109. $settingsPath = "web/sites/${context['name']}/${context['stage']}.settings.local.php";
  110. if (file_exists($settingsPath)) {
  111. $ok = unlink($settingsPath);
  112. if (!$ok) {
  113. $output->writeln(sprintf(
  114. "Could not remove old %s file",
  115. $settingsPath
  116. ));
  117. return 1;
  118. }
  119. }
  120. $rendered = $wrapper->render($context);
  121. $ok = file_put_contents($settingsPath, $rendered, LOCK_EX);
  122. if (!$ok) {
  123. $output->writeln(sprintf('Could not write new %s file', $settingsPath));
  124. return 2;
  125. }
  126. return 0;
  127. }
  128. /**
  129. * @param \Symfony\Component\Console\Input\InputInterface $input
  130. * @param \Symfony\Component\Console\Output\OutputInterface $output
  131. *
  132. * @return array
  133. * - string Template name
  134. * - int Error
  135. */
  136. protected function validateTemplateName(InputInterface $input, OutputInterface $output): array {
  137. $file = $input->getArgument(static::ARG_FILE);
  138. $conf = $this->getBuilderConfig();
  139. $templateName = $conf['templates'][$file] ?? '';
  140. if (empty($templateName)) {
  141. $output->writeln(sprintf(
  142. 'Could not build file %s: no such template in composer.json extra section',
  143. $file
  144. ));
  145. return ["", 1];
  146. }
  147. return [$templateName, 0];
  148. }
  149. }