ConfigDiffer.php 5.3 KB

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