ConfigDiffer.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. <?php
  2. namespace Drupal\config_update;
  3. use Drupal\Component\Diff\Diff;
  4. use Drupal\Core\StringTranslation\StringTranslationTrait;
  5. use Drupal\Core\StringTranslation\TranslationInterface;
  6. /**
  7. * Provides methods related to config differences.
  8. */
  9. class ConfigDiffer implements ConfigDiffInterface {
  10. use StringTranslationTrait;
  11. /**
  12. * List of elements to ignore when comparing config.
  13. *
  14. * @var string[]
  15. *
  16. * @see ConfigDiffer::format().
  17. */
  18. protected $ignore;
  19. /**
  20. * Prefix to use to indicate config hierarchy.
  21. *
  22. * @var string
  23. *
  24. * @see ConfigDiffer::format().
  25. */
  26. protected $hierarchyPrefix;
  27. /**
  28. * Prefix to use to indicate config values.
  29. *
  30. * @var string
  31. *
  32. * @see ConfigDiffer::format().
  33. */
  34. protected $valuePrefix;
  35. /**
  36. * Constructs a ConfigDiffer.
  37. *
  38. * @param TranslationInterface $translation
  39. * String translation service.
  40. * @param string[] $ignore
  41. * Config components to ignore.
  42. * @param string $hierarchy_prefix
  43. * Prefix to use in diffs for array hierarchy.
  44. * @param string $value_prefix
  45. * Prefix to use in diffs for array value.
  46. */
  47. public function __construct(TranslationInterface $translation, $ignore = ['uuid', '_core'], $hierarchy_prefix = '::', $value_prefix = ' : ') {
  48. $this->stringTranslation = $translation;
  49. $this->hierarchyPrefix = $hierarchy_prefix;
  50. $this->valuePrefix = $value_prefix;
  51. $this->ignore = $ignore;
  52. }
  53. /**
  54. * Normalizes config for comparison.
  55. *
  56. * Recursively removes elements in the ignore list from configuration,
  57. * as well as empty array values, and sorts at each level by array key, so
  58. * that config from different storage can be compared meaningfully.
  59. *
  60. * @param array $config
  61. * Configuration array to normalize.
  62. *
  63. * @return array
  64. * Normalized configuration array.
  65. *
  66. * @see ConfigDiffer::format()
  67. * @see ConfigDiffer::$ignore
  68. */
  69. protected function normalize($config) {
  70. // Remove "ignore" elements.
  71. foreach ($this->ignore as $element) {
  72. unset($config[$element]);
  73. }
  74. // Recursively normalize remaining elements, if they are arrays.
  75. foreach ($config as $key => $value) {
  76. if (is_array($value)) {
  77. $new = $this->normalize($value);
  78. if (count($new)) {
  79. $config[$key] = $new;
  80. }
  81. else {
  82. unset($config[$key]);
  83. }
  84. }
  85. }
  86. // Sort and return.
  87. ksort($config);
  88. return $config;
  89. }
  90. /**
  91. * {@inheritdoc}
  92. */
  93. public function same($source, $target) {
  94. $source = $this->normalize($source);
  95. $target = $this->normalize($target);
  96. return $source == $target;
  97. }
  98. /**
  99. * Formats config for showing differences.
  100. *
  101. * To compute differences, we need to separate the config into lines and use
  102. * line-by-line differencer. The obvious way to split into lines is:
  103. * @code
  104. * explode("\n", Yaml::encode($config))
  105. * @endcode
  106. * But this would highlight meaningless differences due to the often different
  107. * order of config files, and also loses the indentation and context of the
  108. * config hierarchy when differences are computed, making the difference
  109. * difficult to interpret.
  110. *
  111. * So, what we do instead is to take the YAML hierarchy and format it so that
  112. * the hierarchy is shown on each line. So, if you're in element
  113. * $config['foo']['bar'] and the value is 'value', you will see
  114. * 'foo::bar : value'.
  115. *
  116. * @param array $config
  117. * Config array to format. Normalize it first if you want to do diffs.
  118. * @param string $prefix
  119. * (optional) When called recursively, the prefix to put on each line. Omit
  120. * when initially calling this function.
  121. *
  122. * @return string[] Array of config lines formatted so that a line-by-line
  123. * diff will show the context in each line, and meaningful differences will
  124. * be computed.
  125. *
  126. * @see ConfigDiffer::normalize()
  127. * @see ConfigDiffer::$hierarchyPrefix
  128. * @see ConfigDiffer::$valuePrefix
  129. */
  130. protected function format($config, $prefix = '') {
  131. $lines = [];
  132. foreach ($config as $key => $value) {
  133. $section_prefix = ($prefix) ? $prefix . $this->hierarchyPrefix . $key : $key;
  134. if (is_array($value)) {
  135. $lines[] = $section_prefix;
  136. $newlines = $this->format($value, $section_prefix);
  137. foreach ($newlines as $line) {
  138. $lines[] = $line;
  139. }
  140. }
  141. elseif (is_null($value)) {
  142. $lines[] = $section_prefix . $this->valuePrefix . $this->t('(NULL)');
  143. }
  144. else {
  145. $lines[] = $section_prefix . $this->valuePrefix . $value;
  146. }
  147. }
  148. return $lines;
  149. }
  150. /**
  151. * {@inheritdoc}
  152. */
  153. public function diff($source, $target) {
  154. $source = $this->normalize($source);
  155. $target = $this->normalize($target);
  156. $source_lines = $this->format($source);
  157. $target_lines = $this->format($target);
  158. return new Diff($source_lines, $target_lines);
  159. }
  160. }