vendor/overblog/graphql-bundle/src/Config/Processor/InheritanceProcessor.php line 126

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Overblog\GraphQLBundle\Config\Processor;
  4. use Exception;
  5. use InvalidArgumentException;
  6. use function array_column;
  7. use function array_filter;
  8. use function array_flip;
  9. use function array_intersect_key;
  10. use function array_keys;
  11. use function array_merge;
  12. use function array_replace_recursive;
  13. use function array_search;
  14. use function array_unique;
  15. use function array_values;
  16. use function implode;
  17. use function in_array;
  18. use function is_array;
  19. use function json_encode;
  20. use function sprintf;
  21. use function uksort;
  22. final class InheritanceProcessor implements ProcessorInterface
  23. {
  24. public const HEIRS_KEY = 'heirs';
  25. public const INHERITS_KEY = 'inherits';
  26. public static function process(array $configs): array
  27. {
  28. $configs = self::processConfigsHeirs($configs);
  29. $configs = self::processConfigsInherits($configs);
  30. $configs = self::removedDecorators($configs);
  31. return $configs;
  32. }
  33. private static function removedDecorators(array $configs): array
  34. {
  35. return array_filter($configs, fn ($config) => !isset($config['decorator']) || true !== $config['decorator']);
  36. }
  37. private static function processConfigsHeirs(array $configs): array
  38. {
  39. foreach ($configs as $parentName => &$config) {
  40. if (!empty($config[self::HEIRS_KEY])) {
  41. // allows shorthand configuration `heirs: QueryFooDecorator` is equivalent to `heirs: [QueryFooDecorator]`
  42. if (!is_array($config[self::HEIRS_KEY])) {
  43. $config[self::HEIRS_KEY] = [$config[self::HEIRS_KEY]];
  44. }
  45. foreach ($config[self::HEIRS_KEY] as $heirs) {
  46. if (!isset($configs[$heirs])) {
  47. throw new InvalidArgumentException(sprintf(
  48. 'Type %s child of %s not found.',
  49. json_encode($heirs),
  50. json_encode($parentName)
  51. ));
  52. }
  53. if (!isset($configs[$heirs][self::INHERITS_KEY])) {
  54. $configs[$heirs][self::INHERITS_KEY] = [];
  55. }
  56. $configs[$heirs][self::INHERITS_KEY][] = $parentName;
  57. }
  58. }
  59. unset($config[self::HEIRS_KEY]);
  60. }
  61. return $configs;
  62. }
  63. private static function processConfigsInherits(array $configs): array
  64. {
  65. foreach ($configs as $name => &$config) {
  66. if (!isset($config['type'])) {
  67. continue;
  68. }
  69. $allowedTypes = [$config['type']];
  70. if ('object' === $config['type']) {
  71. $allowedTypes[] = 'interface';
  72. }
  73. $flattenInherits = self::flattenInherits($name, $configs, $allowedTypes);
  74. if (empty($flattenInherits)) {
  75. continue;
  76. }
  77. $config = self::inheritsTypeConfig($name, $flattenInherits, $configs);
  78. }
  79. return $configs;
  80. }
  81. /**
  82. * @throws Exception
  83. */
  84. private static function inheritsTypeConfig(string $child, array $parents, array $configs): array
  85. {
  86. $parentTypes = array_intersect_key($configs, array_flip($parents));
  87. // Restore initial order
  88. uksort($parentTypes, fn ($a, $b) => (int) (array_search($a, $parents, true) > array_search($b, $parents, true)));
  89. $mergedParentsConfig = self::mergeConfigs(...array_column($parentTypes, 'config'));
  90. $childType = $configs[$child];
  91. // unset resolveType field resulting from the merge of a "interface" type
  92. if ('object' === $childType['type']) {
  93. unset($mergedParentsConfig['resolveType']);
  94. }
  95. if (isset($mergedParentsConfig['interfaces'], $childType['config']['interfaces'])) {
  96. $childType['config']['interfaces'] = array_merge($mergedParentsConfig['interfaces'], $childType['config']['interfaces']);
  97. }
  98. $configs = array_replace_recursive(['config' => $mergedParentsConfig], $childType);
  99. return $configs;
  100. }
  101. private static function flattenInherits(
  102. string $name,
  103. array $configs,
  104. array $allowedTypes,
  105. string $child = null,
  106. array $typesTreated = []
  107. ): array {
  108. self::checkTypeExists($name, $configs, $child);
  109. self::checkCircularReferenceInheritsTypes($name, $typesTreated);
  110. self::checkAllowedInheritsTypes($name, $configs[$name], $allowedTypes, $child);
  111. // flatten
  112. $config = $configs[$name];
  113. if (empty($config[self::INHERITS_KEY]) || !is_array($config[self::INHERITS_KEY])) {
  114. return [];
  115. }
  116. $typesTreated[$name] = true;
  117. $flattenInheritsTypes = [];
  118. foreach ($config[self::INHERITS_KEY] as $typeToInherit) {
  119. $flattenInheritsTypes = array_merge(
  120. $flattenInheritsTypes,
  121. self::flattenInherits($typeToInherit, $configs, $allowedTypes, $name, $typesTreated)
  122. );
  123. $flattenInheritsTypes[] = $typeToInherit;
  124. }
  125. return $flattenInheritsTypes;
  126. }
  127. private static function checkTypeExists(string $name, array $configs, ?string $child): void
  128. {
  129. if (!isset($configs[$name])) {
  130. throw new InvalidArgumentException(sprintf(
  131. 'Type %s inherited by %s not found.',
  132. json_encode($name),
  133. json_encode($child)
  134. ));
  135. }
  136. }
  137. private static function checkCircularReferenceInheritsTypes(string $name, array $typesTreated): void
  138. {
  139. if (isset($typesTreated[$name])) {
  140. throw new InvalidArgumentException(sprintf(
  141. 'Type circular inheritance detected (%s).',
  142. implode('->', array_merge(array_keys($typesTreated), [$name]))
  143. ));
  144. }
  145. }
  146. private static function checkAllowedInheritsTypes(string $name, array $config, array $allowedTypes, ?string $child): void
  147. {
  148. if (empty($config['decorator']) && isset($config['type']) && !in_array($config['type'], $allowedTypes)) {
  149. throw new InvalidArgumentException(sprintf(
  150. 'Type %s can\'t inherit %s because its type (%s) is not allowed type (%s).',
  151. json_encode($child),
  152. json_encode($name),
  153. json_encode($config['type']),
  154. json_encode($allowedTypes)
  155. ));
  156. }
  157. }
  158. private static function mergeConfigs(array ...$configs): array
  159. {
  160. $result = [];
  161. foreach ($configs as $config) {
  162. $interfaces = $result['interfaces'] ?? null;
  163. $result = array_replace_recursive($result, $config);
  164. if (!empty($interfaces) && !empty($config['interfaces'])) {
  165. $result['interfaces'] = array_values(array_unique(array_merge($interfaces, $config['interfaces'])));
  166. }
  167. }
  168. return $result;
  169. }
  170. }