Skip to content

Commit

Permalink
Add priorities feature to handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
adiq committed Mar 24, 2018
1 parent 5acbf09 commit de716d6
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 13 deletions.
70 changes: 58 additions & 12 deletions DependencyInjection/Compiler/CustomHandlersPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ public function process(ContainerBuilder $container)
{
$handlers = array();
$handlerServices = array();
foreach ($container->findTaggedServiceIds('jms_serializer.handler') as $id => $tags) {
foreach ($tags as $attrs) {
foreach ($this->findAndSortTaggedServices('jms_serializer.handler', $container) as $reference) {
$id = (string)$reference;
$definition = $container->getDefinition($id);
foreach ($definition->getTags() as $serviceTags) {
$attrs = $serviceTags[0];
if (!isset($attrs['type'], $attrs['format'])) {
throw new \RuntimeException(sprintf('Each tag named "jms_serializer.handler" of service "%s" must have at least two attributes: "type" and "format".', $id));
}
Expand All @@ -32,18 +35,20 @@ public function process(ContainerBuilder $container)

foreach ($directions as $direction) {
$method = isset($attrs['method']) ? $attrs['method'] : HandlerRegistry::getDefaultMethod($direction, $attrs['type'], $attrs['format']);
if (class_exists(ServiceLocatorTagPass::class) || $container->getDefinition($id)->isPublic()) {
$handlerServices[$id] = new Reference($id);
if (class_exists(ServiceLocatorTagPass::class) || $definition->isPublic()) {
$handlerServices[$id] = $reference;
$handlers[$direction][$attrs['type']][$attrs['format']] = array($id, $method);
} else {
$handlers[$direction][$attrs['type']][$attrs['format']] = array(new Reference($id), $method);
$handlers[$direction][$attrs['type']][$attrs['format']] = array($reference, $method);
}
}
}
}

foreach ($container->findTaggedServiceIds('jms_serializer.subscribing_handler') as $id => $tags) {
$class = $container->getDefinition($id)->getClass();
foreach ($this->findAndSortTaggedServices('jms_serializer.subscribing_handler', $container) as $reference) {
$id = (string)$reference;
$definition = $container->getDefinition($id);
$class = $definition->getClass();
$ref = new \ReflectionClass($class);
if (!$ref->implementsInterface('JMS\Serializer\Handler\SubscribingHandlerInterface')) {
throw new \RuntimeException(sprintf('The service "%s" must implement the SubscribingHandlerInterface.', $id));
Expand All @@ -61,22 +66,63 @@ public function process(ContainerBuilder $container)

foreach ($directions as $direction) {
$method = isset($methodData['method']) ? $methodData['method'] : HandlerRegistry::getDefaultMethod($direction, $methodData['type'], $methodData['format']);
if (class_exists(ServiceLocatorTagPass::class) || $container->getDefinition($id)->isPublic()) {
$handlerServices[$id] = new Reference($id);
if (class_exists(ServiceLocatorTagPass::class) || $definition->isPublic()) {
$handlerServices[$id] = $reference;
$handlers[$direction][$methodData['type']][$methodData['format']] = array($id, $method);
} else {
$handlers[$direction][$methodData['type']][$methodData['format']] = array(new Reference($id), $method);
$handlers[$direction][$methodData['type']][$methodData['format']] = array($reference, $method);
}
}
}
}

$container->findDefinition('jms_serializer.handler_registry')
->addArgument($handlers);
$container->findDefinition('jms_serializer.handler_registry')->addArgument($handlers);

if (class_exists(ServiceLocatorTagPass::class)) {
$serviceLocator = ServiceLocatorTagPass::register($container, $handlerServices);
$container->findDefinition('jms_serializer.handler_registry')->replaceArgument(0, $serviceLocator);
}
}

/**
* Finds all services with the given tag name and order them by their priority.
*
* The order of additions must be respected for services having the same priority,
* and knowing that the \SplPriorityQueue class does not respect the FIFO method,
* we should not use that class.
*
* Original author is Iltar van der Berg <[email protected]>
*
* @see https://bugs.php.net/bug.php?id=53710
* @see https://bugs.php.net/bug.php?id=60926
*
* @param string $tagName
* @param ContainerBuilder $container
*
* @return Reference[]
*/
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
{
$services = array();

foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
$definition = $container->getDefinition($serviceId);

if (!isset($attributes[0]['priority'])) {
$class = $definition->getClass();
$priority = strpos($class, 'JMS\Serializer') === 0 ? 100 : 0;
} else {
$priority = $attributes[0]['priority'];
}

$services[$priority][] = new Reference($serviceId);
}

if ($services) {
krsort($services);
$services = call_user_func_array('array_merge', $services);
}

return $services;
}
}
115 changes: 115 additions & 0 deletions Tests/DependencyInjection/CustomHandlerPassTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,72 @@ public function testHandlerMustHaveTypeAndFormat()
$pass->process($container);
}

public function testHandlerMustPrioritizeUserDefined()
{
$container = $this->getContainer();

$def = new Definition('JMS\Serializer\Foo');
$def->addTag('jms_serializer.handler', [
'type' => 'DateTime',
'format' => 'json',
]);
$container->setDefinition('my_service', $def);

$userDef = new Definition('Bar');
$userDef->addTag('jms_serializer.handler', [
'type' => 'DateTime',
'format' => 'json',
]);
$container->setDefinition('my_custom_service', $userDef);

$pass = new CustomHandlersPass();
$pass->process($container);

$args = $container->getDefinition('jms_serializer.handler_registry')->getArguments();

$this->assertSame([
2 => ['DateTime' => ['json' => ['my_custom_service', 'deserializeDateTimeFromjson']]],
1 => ['DateTime' => ['json' => ['my_custom_service', 'serializeDateTimeTojson']]]
], $args[1]);
}

public function testHandlerMustRespectPriorities()
{
$container = $this->getContainer();

$def = new Definition('JMS\Serializer\Foo');
$def->addTag('jms_serializer.handler', [
'type' => 'DateTime',
'format' => 'json',
]);
$container->setDefinition('my_service', $def);

$userDef = new Definition('Bar');
$userDef->addTag('jms_serializer.handler', [
'type' => 'DateTime',
'format' => 'json',
]);
$container->setDefinition('my_custom_service', $userDef);

$userExplicitDef = new Definition('Baz');
$userExplicitDef->addTag('jms_serializer.handler', [
'type' => 'DateTime',
'format' => 'json',
'priority' => -100
]);
$container->setDefinition('my_custom_explicit_service', $userExplicitDef);

$pass = new CustomHandlersPass();
$pass->process($container);

$args = $container->getDefinition('jms_serializer.handler_registry')->getArguments();

$this->assertSame([
2 => ['DateTime' => ['json' => ['my_custom_explicit_service', 'deserializeDateTimeFromjson']]],
1 => ['DateTime' => ['json' => ['my_custom_explicit_service', 'serializeDateTimeTojson']]]
], $args[1]);
}

public function testSubscribingHandler()
{
$container = $this->getContainer();
Expand Down Expand Up @@ -210,4 +276,53 @@ public function testSubscribingHandlerInterface()
$pass = new CustomHandlersPass();
$pass->process($container);
}

public function testSubscribingHandlerMustPrioritizeUserDefined()
{
$container = $this->getContainer();

$def = new Definition('JMS\SerializerBundle\Tests\DependencyInjection\Fixture\SubscribingHandler');
$def->addTag('jms_serializer.subscribing_handler');
$container->setDefinition('my_service', $def);

$userDef = new Definition('UserDefined\Foo\UserDefinedSubscribingHandler');
$userDef->addTag('jms_serializer.subscribing_handler');
$container->setDefinition('my_custom_service', $userDef);

$pass = new CustomHandlersPass();
$pass->process($container);

$args = $container->getDefinition('jms_serializer.handler_registry')->getArguments();

$this->assertSame([
1 => ['DateTime' => ['json' => ['my_custom_service', 'onDateTime']]]
], $args[1]);
}

public function testSubscribingHandlerMustRespectPriorities()
{
$container = $this->getContainer();

$def = new Definition('JMS\SerializerBundle\Tests\DependencyInjection\Fixture\SubscribingHandler');
$def->addTag('jms_serializer.subscribing_handler');
$container->setDefinition('my_service', $def);

$userDef = new Definition('UserDefined\Foo\UserDefinedSubscribingHandler');
$userDef->addTag('jms_serializer.subscribing_handler');
$container->setDefinition('my_custom_service', $userDef);

$userExplicitDef = new Definition('UserDefined\Foo\UserDefinedSubscribingHandler');
$userExplicitDef->addTag('jms_serializer.subscribing_handler', ['priority' => -100]);
$container->setDefinition('my_custom_explicit_service', $userExplicitDef);

$pass = new CustomHandlersPass();
$pass->process($container);

$args = $container->getDefinition('jms_serializer.handler_registry')->getArguments();

$this->assertSame([
1 => ['DateTime' => ['json' => ['my_custom_explicit_service', 'onDateTime']]]
], $args[1]);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
namespace UserDefined\Foo;

use JMS\Serializer\GraphNavigator;
use JMS\Serializer\Handler\SubscribingHandlerInterface;

class UserDefinedSubscribingHandler implements SubscribingHandlerInterface
{
public static function getSubscribingMethods()
{
return array(
array(
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => 'DateTime',
'method' => 'onDateTime',
),
);
}
}

5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@
]
},
"autoload-dev": {
"psr-4": { "JMS\\SerializerBundle\\Tests\\": "Tests" }
"psr-4": {
"JMS\\SerializerBundle\\Tests\\": "Tests",
"UserDefined\\Foo\\": "Tests/DependencyInjection/Fixture/UserDefined"
}
},
"extra": {
"branch-alias": {
Expand Down

0 comments on commit de716d6

Please sign in to comment.