vendor/jms/serializer-bundle/DependencyInjection/Compiler/CustomHandlersPass.php line 82

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace JMS\SerializerBundle\DependencyInjection\Compiler;
  4. use JMS\Serializer\GraphNavigatorInterface;
  5. use JMS\Serializer\Handler\HandlerRegistry;
  6. use JMS\Serializer\Handler\LazyHandlerRegistry;
  7. use JMS\SerializerBundle\DependencyInjection\ScopedContainer;
  8. use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
  9. use Symfony\Component\DependencyInjection\Reference;
  10. /** @internal */
  11. final class CustomHandlersPass extends PerInstancePass
  12. {
  13. protected function processInstance(ScopedContainer $container): void
  14. {
  15. $handlersByDirection = $this->findHandlers($container);
  16. $handlerRegistryDef = $container->findDefinition('jms_serializer.handler_registry');
  17. $isLazyHandlerRegistry = is_a($handlerRegistryDef->getClass(), LazyHandlerRegistry::class, true);
  18. $handlerServices = [];
  19. $handlers = [];
  20. foreach ($handlersByDirection as $direction => $handlersByType) {
  21. foreach ($handlersByType as $type => $handlersByFormat) {
  22. foreach ($handlersByFormat as $format => $handlerCallable) {
  23. $id = (string) $handlerCallable[0];
  24. $handlerServices[$id] = new ServiceClosureArgument($handlerCallable[0]);
  25. $handlerCallable[0] = $id;
  26. if (!$isLazyHandlerRegistry) {
  27. $handlerRegistryDef->addMethodCall('registerHandler', [$direction, $type, $format, $handlerCallable]);
  28. } else {
  29. $handlers[$direction][$type][$format] = $handlerCallable;
  30. }
  31. }
  32. }
  33. }
  34. if ($isLazyHandlerRegistry) {
  35. $handlerRegistryDef->addArgument($handlers);
  36. }
  37. $container->findDefinition('jms_serializer.handler_registry.service_locator')
  38. ->setArgument(0, $handlerServices);
  39. }
  40. private function findHandlers(ScopedContainer $container): array
  41. {
  42. $handlers = [];
  43. foreach ($container->findTaggedServiceIds('jms_serializer.handler') as $id => $tags) {
  44. foreach ($tags as $attrs) {
  45. if (!isset($attrs['type'], $attrs['format'])) {
  46. throw new \RuntimeException(sprintf('Each tag named "jms_serializer.handler" of service "%s" must have at least two attributes: "type" and "format".', $id));
  47. }
  48. $directions = [GraphNavigatorInterface::DIRECTION_DESERIALIZATION, GraphNavigatorInterface::DIRECTION_SERIALIZATION];
  49. if (isset($attrs['direction'])) {
  50. if (!defined($directionConstant = 'JMS\Serializer\GraphNavigatorInterface::DIRECTION_' . strtoupper($attrs['direction']))) {
  51. throw new \RuntimeException(sprintf('The direction "%s" of tag "jms_serializer.handler" of service "%s" does not exist.', $attrs['direction'], $id));
  52. }
  53. $directions = [constant($directionConstant)];
  54. }
  55. foreach ($directions as $direction) {
  56. $method = $attrs['method'] ?? HandlerRegistry::getDefaultMethod($direction, $attrs['type'], $attrs['format']);
  57. $priority = isset($attrs['priority']) ? intval($attrs['priority']) : 0;
  58. $handlers[] = [$direction, $attrs['type'], $attrs['format'], $priority, new Reference($id), $method];
  59. }
  60. }
  61. }
  62. foreach ($container->findTaggedServiceIds('jms_serializer.subscribing_handler') as $id => $tags) {
  63. $def = $container->getDefinition($id);
  64. $class = $def->getClass();
  65. $ref = new \ReflectionClass($class);
  66. if (!$ref->implementsInterface('JMS\Serializer\Handler\SubscribingHandlerInterface')) {
  67. throw new \RuntimeException(sprintf('The service "%s" must implement the SubscribingHandlerInterface.', $id));
  68. }
  69. foreach (call_user_func([$class, 'getSubscribingMethods']) as $methodData) {
  70. if (!isset($methodData['format'], $methodData['type'])) {
  71. throw new \RuntimeException(sprintf('Each method returned from getSubscribingMethods of service "%s" must have a "type", and "format" attribute.', $id));
  72. }
  73. $directions = [GraphNavigatorInterface::DIRECTION_DESERIALIZATION, GraphNavigatorInterface::DIRECTION_SERIALIZATION];
  74. if (isset($methodData['direction'])) {
  75. $directions = [$methodData['direction']];
  76. }
  77. foreach ($directions as $direction) {
  78. $priority = isset($methodData['priority']) ? intval($methodData['priority']) : 0;
  79. $method = $methodData['method'] ?? HandlerRegistry::getDefaultMethod($direction, $methodData['type'], $methodData['format']);
  80. $handlers[] = [$direction, $methodData['type'], $methodData['format'], $priority, new Reference($id), $method];
  81. }
  82. }
  83. }
  84. return $this->sortAndFlattenHandlersList($handlers);
  85. }
  86. private function sortAndFlattenHandlersList(array $allHandlers)
  87. {
  88. $sorter = static function ($a, $b) {
  89. return $b[3] === $a[3] ? 0 : ($b[3] > $a[3] ? 1 : -1);
  90. };
  91. self::stable_uasort($allHandlers, $sorter);
  92. $handlers = [];
  93. foreach ($allHandlers as $handler) {
  94. [$direction, $type, $format, $priority, $service, $method] = $handler;
  95. $handlers[$direction][$type][$format] = [$service, $method];
  96. }
  97. return $handlers;
  98. }
  99. /**
  100. * Performs stable sorting. Copied from http://php.net/manual/en/function.uasort.php#121283
  101. *
  102. * @param array $array
  103. * @param callable $value_compare_func
  104. *
  105. * @return bool
  106. */
  107. private static function stable_uasort(array &$array, callable $value_compare_func)
  108. {
  109. $index = 0;
  110. foreach ($array as &$item) {
  111. $item = [$index++, $item];
  112. }
  113. $result = uasort($array, static function ($a, $b) use ($value_compare_func) {
  114. $result = call_user_func($value_compare_func, $a[1], $b[1]);
  115. return 0 === $result ? $a[0] - $b[0] : $result;
  116. });
  117. foreach ($array as &$item) {
  118. $item = $item[1];
  119. }
  120. return $result;
  121. }
  122. }