ArrayConverter.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Translation\Util;
  11. /**
  12. * ArrayConverter generates tree like structure from a message catalogue.
  13. * e.g. this
  14. * 'foo.bar1' => 'test1',
  15. * 'foo.bar2' => 'test2'
  16. * converts to follows:
  17. * foo:
  18. * bar1: test1
  19. * bar2: test2.
  20. *
  21. * @author Gennady Telegin <gtelegin@gmail.com>
  22. */
  23. class ArrayConverter
  24. {
  25. /**
  26. * Converts linear messages array to tree-like array.
  27. * For example this array('foo.bar' => 'value') will be converted to ['foo' => ['bar' => 'value']].
  28. *
  29. * @param array $messages Linear messages array
  30. */
  31. public static function expandToTree(array $messages): array
  32. {
  33. $tree = [];
  34. foreach ($messages as $id => $value) {
  35. $referenceToElement = &self::getElementByPath($tree, self::getKeyParts($id));
  36. $referenceToElement = $value;
  37. unset($referenceToElement);
  38. }
  39. return $tree;
  40. }
  41. private static function &getElementByPath(array &$tree, array $parts): mixed
  42. {
  43. $elem = &$tree;
  44. $parentOfElem = null;
  45. foreach ($parts as $i => $part) {
  46. if (isset($elem[$part]) && \is_string($elem[$part])) {
  47. /* Process next case:
  48. * 'foo': 'test1',
  49. * 'foo.bar': 'test2'
  50. *
  51. * $tree['foo'] was string before we found array {bar: test2}.
  52. * Treat new element as string too, e.g. add $tree['foo.bar'] = 'test2';
  53. */
  54. $elem = &$elem[implode('.', \array_slice($parts, $i))];
  55. break;
  56. }
  57. $parentOfElem = &$elem;
  58. $elem = &$elem[$part];
  59. }
  60. if ($elem && \is_array($elem) && $parentOfElem) {
  61. /* Process next case:
  62. * 'foo.bar': 'test1'
  63. * 'foo': 'test2'
  64. *
  65. * $tree['foo'] was array = {bar: 'test1'} before we found string constant `foo`.
  66. * Cancel treating $tree['foo'] as array and cancel back it expansion,
  67. * e.g. make it $tree['foo.bar'] = 'test1' again.
  68. */
  69. self::cancelExpand($parentOfElem, $part, $elem);
  70. }
  71. return $elem;
  72. }
  73. private static function cancelExpand(array &$tree, string $prefix, array $node): void
  74. {
  75. $prefix .= '.';
  76. foreach ($node as $id => $value) {
  77. if (\is_string($value)) {
  78. $tree[$prefix.$id] = $value;
  79. } else {
  80. self::cancelExpand($tree, $prefix.$id, $value);
  81. }
  82. }
  83. }
  84. /**
  85. * @return string[]
  86. */
  87. private static function getKeyParts(string $key): array
  88. {
  89. $parts = explode('.', $key);
  90. $partsCount = \count($parts);
  91. $result = [];
  92. $buffer = '';
  93. foreach ($parts as $index => $part) {
  94. if (0 === $index && '' === $part) {
  95. $buffer = '.';
  96. continue;
  97. }
  98. if ($index === $partsCount - 1 && '' === $part) {
  99. $buffer .= '.';
  100. $result[] = $buffer;
  101. continue;
  102. }
  103. if (isset($parts[$index + 1]) && '' === $parts[$index + 1]) {
  104. $buffer .= $part;
  105. continue;
  106. }
  107. if ($buffer) {
  108. $result[] = $buffer.$part;
  109. $buffer = '';
  110. continue;
  111. }
  112. $result[] = $part;
  113. }
  114. return $result;
  115. }
  116. }