Merge branch '4.1'

* 4.1:
  Supress deprecation notices thrown when getting private servies from container in tests
  Uses `protected` for test functions
  [Messenger] Allow to scope handlers per bus
  do not mock the session in token storage tests
  [DependencyInjection] resolve array env vars
  Add Occitan plural rule
  Fix security/* cross-dependencies
  [Messenger] implement several senders using a ChainSender
  [Lock] Skip test if posix extension is not installed
  fix bug when imported routes are prefixed
  [DI] Allow defining bindings on ChildDefinition
  use strict compare in url validator
  Disallow illegal characters like "." in session.name
  [HttpKernel] do file_exists() check instead of silent notice
  Select alternatives on missing receiver arg or typo
  fix rounding from string
This commit is contained in:
Fabien Potencier 2018-05-21 12:10:22 +02:00
commit 1f4100d635
44 changed files with 1016 additions and 372 deletions

View File

@ -24,6 +24,7 @@
"require-dev": {
"symfony/stopwatch": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
<<<<<<< HEAD
"symfony/form": "~3.4|~4.0",
"symfony/http-kernel": "~3.4|~4.0",
"symfony/property-access": "~3.4|~4.0",
@ -33,6 +34,17 @@
"symfony/expression-language": "~3.4|~4.0",
"symfony/validator": "~3.4|~4.0",
"symfony/translation": "~3.4|~4.0",
=======
"symfony/form": "^3.3.10|~4.0",
"symfony/http-kernel": "~2.8|~3.0|~4.0",
"symfony/property-access": "~2.8|~3.0|~4.0",
"symfony/property-info": "~2.8|3.0|~4.0",
"symfony/proxy-manager-bridge": "~2.8|~3.0|~4.0",
"symfony/security": "^2.8.31|^3.3.13|~4.0",
"symfony/expression-language": "~2.8|~3.0|~4.0",
"symfony/validator": "^3.2.5|~4.0",
"symfony/translation": "~2.8|~3.0|~4.0",
>>>>>>> 3.4
"doctrine/data-fixtures": "1.0.*",
"doctrine/dbal": "~2.4",
"doctrine/orm": "^2.4.5"

View File

@ -109,6 +109,30 @@ class DeprecationErrorHandler
}
$trace = debug_backtrace(true);
// Silence deprecation warnings about private service accessed
// from the service container if done so from a Test class.
// As of Symfony 4.1, there is a new TestContainer that allows
// fetching of private services within tests, so we no longer
// need to warn about this behavior.
//
// NOTE: the event at the top of the stack $trace (index 0) should
// always be the PhpUnitBridge's DeprecationErrorHandler; the
// second event (index 1) should be the trigger_error() event;
// the third event (index 2) should be the actual source of the
// triggered deprecation notice; and the fourth event (index 3)
// represents the action that called the deprecated code. In the
// scenario that we want to suppress, the 4th event will be an
// object instance of \PHPUnit\Framework\TestCase.
if (isset($trace[3]['object'])) {
$isPrivateServiceNotice = false !== strpos($msg, ' service is private, ');
$isNoticeForContainerGetHasUsage = 'Symfony\Component\DependencyInjection\Container' === $trace[2]['class'] && in_array($trace[2]['function'], array('get', 'has'));
$noticeWasTriggeredByPhpUnitTest = $trace[3]['object'] instanceof \PHPUnit\Framework\TestCase;
if ($isPrivateServiceNotice && $isNoticeForContainerGetHasUsage && $noticeWasTriggeredByPhpUnitTest) {
return false;
}
}
$group = 'other';
$isVendor = DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $inVendors($file);

View File

@ -467,7 +467,16 @@ class Configuration implements ConfigurationInterface
->children()
->scalarNode('storage_id')->defaultValue('session.storage.native')->end()
->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end()
->scalarNode('name')->end()
->scalarNode('name')
->validate()
->ifTrue(function ($v) {
parse_str($v, $parsed);
return implode('&', array_keys($parsed)) !== (string) $v;
})
->thenInvalid('Session name %s contains illegal character(s)')
->end()
->end()
->scalarNode('cookie_lifetime')->end()
->scalarNode('cookie_path')->end()
->scalarNode('cookie_domain')->end()
@ -986,7 +995,10 @@ class Configuration implements ConfigurationInterface
$newConfig = array();
foreach ($config as $k => $v) {
if (!\is_int($k)) {
$newConfig[$k] = array('senders' => \is_array($v) ? array_values($v) : array($v));
$newConfig[$k] = array(
'senders' => $v['senders'] ?? (\is_array($v) ? array_values($v) : array($v)),
'send_and_handle' => $v['send_and_handle'] ?? false,
);
} else {
$newConfig[$v['message-class']]['senders'] = array_map(
function ($a) {
@ -994,6 +1006,7 @@ class Configuration implements ConfigurationInterface
},
array_values($v['sender'])
);
$newConfig[$v['message-class']]['send-and-handle'] = $v['send-and-handle'] ?? false;
}
}
@ -1006,6 +1019,7 @@ class Configuration implements ConfigurationInterface
->requiresAtLeastOneElement()
->prototype('scalar')->end()
->end()
->booleanNode('send_and_handle')->defaultFalse()->end()
->end()
->end()
->end()

View File

@ -63,6 +63,7 @@ use Symfony\Component\Lock\StoreInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Transport\ChainSender;
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
use Symfony\Component\Messenger\Transport\TransportInterface;
use Symfony\Component\PropertyAccess\PropertyAccessor;
@ -1482,7 +1483,7 @@ class FrameworkExtension extends Extension
}
$container->setParameter($busId.'.middleware', $middleware);
$container->setDefinition($busId, (new Definition(MessageBus::class, array(array())))->addTag('messenger.bus'));
$container->register($busId, MessageBus::class)->addArgument(array())->addTag('messenger.bus');
if ($busId === $config['default_bus']) {
$container->setAlias('message_bus', $busId);
@ -1494,16 +1495,28 @@ class FrameworkExtension extends Extension
throw new LogicException(sprintf('The default bus named "%s" is not defined. Define it or change the default bus name.', $config['default_bus']));
}
$messageToSenderIdsMapping = array();
$messageToSenderIdMapping = array();
$messageToSendAndHandleMapping = array();
foreach ($config['routing'] as $message => $messageConfiguration) {
if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) {
throw new LogicException(sprintf('Messenger routing configuration contains a mistake: message "%s" does not exist. It needs to match an existing class or interface.', $message));
}
$messageToSenderIdsMapping[$message] = $messageConfiguration['senders'];
if (1 < \count($messageConfiguration['senders'])) {
$senders = array_map(function ($sender) { return new Reference($sender); }, $messageConfiguration['senders']);
$chainSenderDefinition = new Definition(ChainSender::class, array($senders));
$chainSenderId = '.messenger.chain_sender.'.$message;
$container->setDefinition($chainSenderId, $chainSenderDefinition);
$messageToSenderIdMapping[$message] = $chainSenderId;
} else {
$messageToSenderIdMapping[$message] = $messageConfiguration['senders'][0];
}
$messageToSendAndHandleMapping[$message] = $messageConfiguration['send_and_handle'];
}
$container->getDefinition('messenger.asynchronous.routing.sender_locator')->replaceArgument(1, $messageToSenderIdsMapping);
$container->getDefinition('messenger.asynchronous.routing.sender_locator')->replaceArgument(1, $messageToSenderIdMapping);
$container->getDefinition('messenger.middleware.route_messages')->replaceArgument(1, $messageToSendAndHandleMapping);
foreach ($config['transports'] as $name => $transport) {
if (0 === strpos($transport['dsn'], 'amqp://') && !$container->hasDefinition('messenger.transport.amqp.factory')) {

View File

@ -7,18 +7,14 @@
<services>
<defaults public="false" />
<!-- Handlers -->
<service id="messenger.handler_resolver" class="Symfony\Component\Messenger\Handler\Locator\ContainerHandlerLocator">
<argument type="service" id="service_container"/>
</service>
<!-- Asynchronous -->
<service id="messenger.asynchronous.routing.sender_locator" class="Symfony\Component\Messenger\Asynchronous\Routing\SenderLocator">
<argument type="service" id="messenger.sender_locator" />
<argument /> <!-- Message to sender ID mapping -->
<argument type="collection" /> <!-- Message to sender ID mapping -->
</service>
<service id="messenger.middleware.route_messages" class="Symfony\Component\Messenger\Asynchronous\Middleware\SendMessageMiddleware">
<argument type="service" id="messenger.asynchronous.routing.sender_locator" />
<argument type="collection" /> <!-- Message to send and handle mapping -->
</service>
<!-- Message encoding/decoding -->
@ -31,7 +27,7 @@
<!-- Middleware -->
<service id="messenger.middleware.allow_no_handler" class="Symfony\Component\Messenger\Middleware\AllowNoHandlerMiddleware" abstract="true" />
<service id="messenger.middleware.call_message_handler" class="Symfony\Component\Messenger\Middleware\HandleMessageMiddleware" abstract="true">
<argument type="service" id="messenger.handler_resolver" />
<argument /> <!-- Bus handler resolver -->
</service>
<service id="messenger.middleware.validation" class="Symfony\Component\Messenger\Middleware\ValidationMiddleware" abstract="true">

View File

@ -375,6 +375,7 @@
<xsd:element name="sender" type="messenger_routing_sender" />
</xsd:choice>
<xsd:attribute name="message-class" type="xsd:string" use="required"/>
<xsd:attribute name="send-and-handle" type="xsd:boolean" default="false"/>
</xsd:complexType>
<xsd:complexType name="messenger_routing_sender">

View File

@ -46,6 +46,105 @@ class ConfigurationTest extends TestCase
$this->assertEquals(array('FrameworkBundle:Form'), $config['templating']['form']['resources']);
}
public function getTestValidSessionName()
{
return array(
array(null),
array('PHPSESSID'),
array('a&b'),
array(',_-!@#$%^*(){}:<>/?'),
);
}
/**
* @dataProvider getTestInvalidSessionName
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
public function testInvalidSessionName($sessionName)
{
$processor = new Processor();
$processor->processConfiguration(
new Configuration(true),
array(array('session' => array('name' => $sessionName)))
);
}
public function getTestInvalidSessionName()
{
return array(
array('a.b'),
array('a['),
array('a[]'),
array('a[b]'),
array('a=b'),
array('a+b'),
);
}
/**
* @dataProvider getTestValidTrustedProxiesData
*/
public function testValidTrustedProxies($trustedProxies, $processedProxies)
{
$processor = new Processor();
$configuration = new Configuration(true);
$config = $processor->processConfiguration($configuration, array(array(
'secret' => 's3cr3t',
'trusted_proxies' => $trustedProxies,
)));
$this->assertEquals($processedProxies, $config['trusted_proxies']);
}
public function getTestValidTrustedProxiesData()
{
return array(
array(array('127.0.0.1'), array('127.0.0.1')),
array(array('::1'), array('::1')),
array(array('127.0.0.1', '::1'), array('127.0.0.1', '::1')),
array(null, array()),
array(false, array()),
array(array(), array()),
array(array('10.0.0.0/8'), array('10.0.0.0/8')),
array(array('::ffff:0:0/96'), array('::ffff:0:0/96')),
array(array('0.0.0.0/0'), array('0.0.0.0/0')),
);
}
/**
* @group legacy
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
public function testInvalidTypeTrustedProxies()
{
$processor = new Processor();
$configuration = new Configuration(true);
$processor->processConfiguration($configuration, array(
array(
'secret' => 's3cr3t',
'trusted_proxies' => 'Not an IP address',
),
));
}
/**
* @group legacy
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
public function testInvalidValueTrustedProxies()
{
$processor = new Processor();
$configuration = new Configuration(true);
$processor->processConfiguration($configuration, array(
array(
'secret' => 's3cr3t',
'trusted_proxies' => array('Not an IP address'),
),
));
}
>>>>>>> 3.4
public function testAssetsCanBeEnabled()
{
$processor = new Processor();

View File

@ -3,8 +3,11 @@
$container->loadFromExtension('framework', array(
'messenger' => array(
'routing' => array(
'Symfony\Component\Messenger\Tests\Fixtures\DummyMessage' => array('amqp'),
'Symfony\Component\Messenger\Tests\Fixtures\SecondMessage' => array('amqp', 'audit', null),
'Symfony\Component\Messenger\Tests\Fixtures\DummyMessage' => array('amqp', 'audit'),
'Symfony\Component\Messenger\Tests\Fixtures\SecondMessage' => array(
'senders' => array('amqp', 'audit'),
'send_and_handle' => true,
),
'*' => 'amqp',
),
),

View File

@ -9,11 +9,11 @@
<framework:messenger>
<framework:routing message-class="Symfony\Component\Messenger\Tests\Fixtures\DummyMessage">
<framework:sender service="amqp" />
<framework:sender service="audit" />
</framework:routing>
<framework:routing message-class="Symfony\Component\Messenger\Tests\Fixtures\SecondMessage">
<framework:routing message-class="Symfony\Component\Messenger\Tests\Fixtures\SecondMessage" send-and-handle="true">
<framework:sender service="amqp" />
<framework:sender service="audit" />
<framework:sender service="null" />
</framework:routing>
<framework:routing message-class="*">
<framework:sender service="amqp" />

View File

@ -1,6 +1,8 @@
framework:
messenger:
routing:
'Symfony\Component\Messenger\Tests\Fixtures\DummyMessage': amqp
'Symfony\Component\Messenger\Tests\Fixtures\SecondMessage': [amqp, audit, ~]
'Symfony\Component\Messenger\Tests\Fixtures\DummyMessage': [amqp, audit]
'Symfony\Component\Messenger\Tests\Fixtures\SecondMessage':
senders: [amqp, audit]
send_and_handle: true
'*': amqp

View File

@ -558,14 +558,21 @@ abstract class FrameworkExtensionTest extends TestCase
{
$container = $this->createContainerFromFile('messenger_routing');
$senderLocatorDefinition = $container->getDefinition('messenger.asynchronous.routing.sender_locator');
$sendMessageMiddlewareDefinition = $container->getDefinition('messenger.middleware.route_messages');
$messageToSenderIdsMapping = array(
DummyMessage::class => array('amqp'),
SecondMessage::class => array('amqp', 'audit', null),
'*' => array('amqp'),
DummyMessage::class => '.messenger.chain_sender.'.DummyMessage::class,
SecondMessage::class => '.messenger.chain_sender.'.SecondMessage::class,
'*' => 'amqp',
);
$messageToSendAndHandleMapping = array(
DummyMessage::class => false,
SecondMessage::class => true,
'*' => false,
);
$this->assertSame($messageToSenderIdsMapping, $senderLocatorDefinition->getArgument(1));
$this->assertSame($messageToSendAndHandleMapping, $sendMessageMiddlewareDefinition->getArgument(1));
}
/**

View File

@ -121,12 +121,4 @@ class ChildDefinition extends Definition
{
throw new BadMethodCallException('A ChildDefinition cannot have instanceof conditionals set on it.');
}
/**
* @internal
*/
public function setBindings(array $bindings)
{
throw new BadMethodCallException('A ChildDefinition cannot have bindings set on it.');
}
}

View File

@ -100,7 +100,7 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass
$def->setAutowired($parentDef->isAutowired());
$def->setChanges($parentDef->getChanges());
$def->setBindings($parentDef->getBindings());
$def->setBindings($definition->getBindings() + $parentDef->getBindings());
// overwrite with values specified in the decorator
$changes = $definition->getChanges();

View File

@ -1363,6 +1363,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
$envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
$completed = false;
foreach ($envPlaceholders as $env => $placeholders) {
foreach ($placeholders as $placeholder) {
if (false !== stripos($value, $placeholder)) {
@ -1373,14 +1374,19 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
}
if ($placeholder === $value) {
$value = $resolved;
$completed = true;
} else {
if (!is_string($resolved) && !is_numeric($resolved)) {
throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type %s inside string value "%s".', $env, gettype($resolved), $value));
throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type %s inside string value "%s".', $env, gettype($resolved), $this->resolveEnvPlaceholders($value)));
}
$value = str_ireplace($placeholder, $resolved, $value);
}
$usedEnvs[$env] = $env;
$this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1;
if ($completed) {
break 2;
}
}
}
}

View File

@ -355,6 +355,27 @@ class ResolveChildDefinitionsPassTest extends TestCase
$this->assertSame(array(2, 1, 'foo' => 3), $def->getArguments());
}
public function testBindings()
{
$container = new ContainerBuilder();
$container->register('parent', 'stdClass')
->setBindings(array('a' => '1', 'b' => '2'))
;
$child = $container->setDefinition('child', new ChildDefinition('parent'))
->setBindings(array('b' => 'B', 'c' => 'C'))
;
$this->process($container);
$bindings = array();
foreach ($container->getDefinition('child')->getBindings() as $k => $v) {
$bindings[$k] = $v->getValues()[0];
}
$this->assertEquals(array('b' => 'B', 'c' => 'C', 'a' => '1'), $bindings);
}
public function testSetAutoconfiguredOnServiceIsParent()
{
$container = new ContainerBuilder();

View File

@ -665,17 +665,49 @@ class ContainerBuilderTest extends TestCase
putenv('DUMMY_ENV_VAR');
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage A string value must be composed of strings and/or numbers, but found parameter "env(ARRAY)" of type array inside string value "ABC %env(ARRAY)%".
*/
public function testCompileWithArrayResolveEnv()
{
$bag = new TestingEnvPlaceholderParameterBag();
$container = new ContainerBuilder($bag);
$container->setParameter('foo', '%env(ARRAY)%');
$container->setParameter('bar', 'ABC %env(ARRAY)%');
putenv('ARRAY={"foo":"bar"}');
$container = new ContainerBuilder();
$container->setParameter('foo', '%env(json:ARRAY)%');
$container->compile(true);
$this->assertSame(array('foo' => 'bar'), $container->getParameter('foo'));
putenv('ARRAY');
}
public function testCompileWithArrayAndAnotherResolveEnv()
{
putenv('DUMMY_ENV_VAR=abc');
putenv('ARRAY={"foo":"bar"}');
$container = new ContainerBuilder();
$container->setParameter('foo', '%env(json:ARRAY)%');
$container->setParameter('bar', '%env(DUMMY_ENV_VAR)%');
$container->compile(true);
$this->assertSame(array('foo' => 'bar'), $container->getParameter('foo'));
$this->assertSame('abc', $container->getParameter('bar'));
putenv('DUMMY_ENV_VAR');
putenv('ARRAY');
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage A string value must be composed of strings and/or numbers, but found parameter "env(json:ARRAY)" of type array inside string value "ABC %env(json:ARRAY)%".
*/
public function testCompileWithArrayInStringResolveEnv()
{
putenv('ARRAY={"foo":"bar"}');
$container = new ContainerBuilder();
$container->setParameter('foo', 'ABC %env(json:ARRAY)%');
$container->compile(true);
putenv('ARRAY');
}
/**
@ -1415,11 +1447,3 @@ class B
{
}
}
class TestingEnvPlaceholderParameterBag extends EnvPlaceholderParameterBag
{
public function get($name)
{
return 'env(array)' === strtolower($name) ? array(123) : parent::get($name);
}
}

View File

@ -78,6 +78,16 @@ class MoneyToLocalizedStringTransformerTest extends TestCase
$transformer = new MoneyToLocalizedStringTransformer(null, null, null, 100);
IntlTestHelper::requireFullIntl($this, false);
\Locale::setDefault('de_AT');
$this->assertSame(3655, (int) $transformer->reverseTransform('36,55'));
}
public function testFloatToIntConversionMismatchOnTransform()
{
$transformer = new MoneyToLocalizedStringTransformer(null, null, MoneyToLocalizedStringTransformer::ROUND_DOWN, 100);
IntlTestHelper::requireFullIntl($this, false);
\Locale::setDefault('de_AT');
$this->assertSame('10,20', $transformer->transform(1020));
}
}

View File

@ -465,7 +465,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
$fresh = $oldContainer = false;
try {
if (\is_object($this->container = include $cache->getPath())) {
if (file_exists($cache->getPath()) && \is_object($this->container = include $cache->getPath())) {
$this->container->set('kernel', $this);
$oldContainer = $this->container;
$fresh = true;
@ -528,7 +528,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
}
}
if (null === $oldContainer) {
if (null === $oldContainer && file_exists($cache->getPath())) {
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
try {
$oldContainer = include $cache->getPath();

View File

@ -710,6 +710,7 @@ class NumberFormatter
} elseif (isset(self::$customRoundingList[$roundingModeAttribute])) {
$roundingCoef = pow(10, $precision);
$value *= $roundingCoef;
$value = (float) (string) $value;
switch ($roundingModeAttribute) {
case self::ROUND_CEILING:

View File

@ -428,6 +428,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
// array(1.125, '1.13'),
array(1.127, '1.13'),
array(1.129, '1.13'),
array(1020 / 100, '10.20'),
);
}
@ -451,6 +452,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(1.125, '1.12'),
array(1.127, '1.13'),
array(1.129, '1.13'),
array(1020 / 100, '10.20'),
);
}
@ -474,6 +476,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(1.125, '1.12'),
array(1.127, '1.13'),
array(1.129, '1.13'),
array(1020 / 100, '10.20'),
);
}
@ -498,6 +501,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(-1.123, '-1.12'),
array(-1.125, '-1.12'),
array(-1.127, '-1.12'),
array(1020 / 100, '10.20'),
);
}
@ -522,6 +526,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(-1.123, '-1.13'),
array(-1.125, '-1.13'),
array(-1.127, '-1.13'),
array(1020 / 100, '10.20'),
);
}
@ -546,6 +551,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(-1.123, '-1.12'),
array(-1.125, '-1.12'),
array(-1.127, '-1.12'),
array(1020 / 100, '10.20'),
);
}
@ -570,6 +576,7 @@ abstract class AbstractNumberFormatterTest extends TestCase
array(-1.123, '-1.13'),
array(-1.125, '-1.13'),
array(-1.127, '-1.13'),
array(1020 / 100, '10.20'),
);
}

View File

@ -31,6 +31,7 @@ trait BlockingStoreTestTrait
* This test is time sensible: the $clockDelay could be adjust.
*
* @requires extension pcntl
* @requires extension posix
* @requires function pcntl_sigwaitinfo
*/
public function testBlockingLocks()

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Messenger\Asynchronous\Middleware;
use Symfony\Component\Messenger\Asynchronous\Routing\SenderLocator;
use Symfony\Component\Messenger\Asynchronous\Routing\SenderLocatorInterface;
use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage;
use Symfony\Component\Messenger\Envelope;
@ -19,14 +20,17 @@ use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
/**
* @author Samuel Roze <samuel.roze@gmail.com>
* @author Tobias Schultze <http://tobion.de>
*/
class SendMessageMiddleware implements MiddlewareInterface, EnvelopeAwareInterface
{
private $senderLocator;
private $messagesToSendAndHandleMapping;
public function __construct(SenderLocatorInterface $senderLocator)
public function __construct(SenderLocatorInterface $senderLocator, array $messagesToSendAndHandleMapping = array())
{
$this->senderLocator = $senderLocator;
$this->messagesToSendAndHandleMapping = $messagesToSendAndHandleMapping;
}
/**
@ -40,20 +44,21 @@ class SendMessageMiddleware implements MiddlewareInterface, EnvelopeAwareInterfa
return $next($message);
}
if (!empty($senders = $this->senderLocator->getSendersForMessage($envelope->getMessage()))) {
foreach ($senders as $sender) {
if (null === $sender) {
continue;
}
$sender = $this->senderLocator->getSenderForMessage($envelope->getMessage());
$sender->send($envelope);
}
if ($sender) {
$sender->send($envelope);
if (!\in_array(null, $senders, true)) {
if (!$this->mustSendAndHandle($envelope->getMessage())) {
return;
}
}
return $next($message);
}
private function mustSendAndHandle($message): bool
{
return (bool) SenderLocator::getValueFromMessageRouting($this->messagesToSendAndHandleMapping, $message);
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Messenger\Asynchronous\Routing;
use Psr\Container\ContainerInterface;
use Symfony\Component\Messenger\Transport\SenderInterface;
/**
* @author Samuel Roze <samuel.roze@gmail.com>
@ -19,42 +20,47 @@ use Psr\Container\ContainerInterface;
class SenderLocator implements SenderLocatorInterface
{
private $senderServiceLocator;
private $messageToSenderIdsMapping;
private $messageToSenderIdMapping;
public function __construct(ContainerInterface $senderServiceLocator, array $messageToSenderIdsMapping)
public function __construct(ContainerInterface $senderServiceLocator, array $messageToSenderIdMapping)
{
$this->senderServiceLocator = $senderServiceLocator;
$this->messageToSenderIdsMapping = $messageToSenderIdsMapping;
$this->messageToSenderIdMapping = $messageToSenderIdMapping;
}
/**
* {@inheritdoc}
*/
public function getSendersForMessage($message): array
public function getSenderForMessage($message): ?SenderInterface
{
$senders = array();
foreach ($this->getSenderIds($message) as $senderId) {
$senders[] = $this->senderServiceLocator->get($senderId);
}
$senderId = $this->getSenderId($message);
return $senders;
return $senderId ? $this->senderServiceLocator->get($senderId) : null;
}
private function getSenderIds($message): array
private function getSenderId($message): ?string
{
if (isset($this->messageToSenderIdsMapping[\get_class($message)])) {
return $this->messageToSenderIdsMapping[\get_class($message)];
return self::getValueFromMessageRouting($this->messageToSenderIdMapping, $message);
}
/**
* @internal
*/
public static function getValueFromMessageRouting(array $mapping, $message)
{
if (isset($mapping[\get_class($message)])) {
return $mapping[\get_class($message)];
}
if ($messageToSenderIdsMapping = array_intersect_key($this->messageToSenderIdsMapping, class_parents($message))) {
return current($messageToSenderIdsMapping);
if ($parentsMapping = array_intersect_key($mapping, class_parents($message))) {
return current($parentsMapping);
}
if ($messageToSenderIdsMapping = array_intersect_key($this->messageToSenderIdsMapping, class_implements($message))) {
return current($messageToSenderIdsMapping);
if ($interfaceMapping = array_intersect_key($mapping, class_implements($message))) {
return current($interfaceMapping);
}
if (isset($this->messageToSenderIdsMapping['*'])) {
return $this->messageToSenderIdsMapping['*'];
if (isset($mapping['*'])) {
return $mapping['*'];
}
return array();
return null;
}
}

View File

@ -15,6 +15,7 @@ use Symfony\Component\Messenger\Transport\SenderInterface;
/**
* @author Samuel Roze <samuel.roze@gmail.com>
* @author Tobias Schultze <http://tobion.de>
*
* @experimental in 4.1
*/
@ -25,7 +26,7 @@ interface SenderLocatorInterface
*
* @param object $message
*
* @return SenderInterface[]
* @return SenderInterface|null
*/
public function getSendersForMessage($message): array;
public function getSenderForMessage($message): ?SenderInterface;
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Transport\Enhancers\StopWhenMemoryUsageIsExceededReceiver;
use Symfony\Component\Messenger\Transport\Enhancers\StopWhenMessageCountIsExceededReceiver;
@ -37,14 +38,14 @@ class ConsumeMessagesCommand extends Command
private $bus;
private $receiverLocator;
private $logger;
private $defaultReceiverName;
private $receiverNames;
public function __construct(MessageBusInterface $bus, ContainerInterface $receiverLocator, LoggerInterface $logger = null, string $defaultReceiverName = null)
public function __construct(MessageBusInterface $bus, ContainerInterface $receiverLocator, LoggerInterface $logger = null, array $receiverNames = array())
{
$this->bus = $bus;
$this->receiverLocator = $receiverLocator;
$this->logger = $logger;
$this->defaultReceiverName = $defaultReceiverName;
$this->receiverNames = $receiverNames;
parent::__construct();
}
@ -54,9 +55,11 @@ class ConsumeMessagesCommand extends Command
*/
protected function configure(): void
{
$defaultReceiverName = 1 === \count($this->receiverNames) ? current($this->receiverNames) : null;
$this
->setDefinition(array(
new InputArgument('receiver', $this->defaultReceiverName ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'Name of the receiver', $this->defaultReceiverName),
new InputArgument('receiver', $defaultReceiverName ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'Name of the receiver', $defaultReceiverName),
new InputOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Limit the number of received messages'),
new InputOption('memory-limit', 'm', InputOption::VALUE_REQUIRED, 'The memory limit the worker can consume'),
new InputOption('time-limit', 't', InputOption::VALUE_REQUIRED, 'The time limit in seconds the worker can run'),
@ -83,6 +86,27 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
if (!$this->receiverNames || $this->receiverLocator->has($receiverName = $input->getArgument('receiver'))) {
return;
}
$style = new SymfonyStyle($input, $output);
if (null === $receiverName) {
$style->block('Missing receiver argument.', null, 'error', ' ', true);
$input->setArgument('receiver', $style->choice('Select one of the available receivers', $this->receiverNames));
} elseif ($alternatives = $this->findAlternatives($receiverName, $this->receiverNames)) {
$style->block(sprintf('Receiver "%s" is not defined.', $receiverName), null, 'error', ' ', true);
if ($style->confirm(sprintf('Do you want to receive from "%s" instead? ', $alternatives[0]), false)) {
$input->setArgument('receiver', $alternatives[0]);
}
}
}
/**
* {@inheritdoc}
*/
@ -134,4 +158,21 @@ EOF
return $max;
}
private function findAlternatives($name, array $collection)
{
$alternatives = array();
foreach ($collection as $item) {
$lev = levenshtein($name, $item);
if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) {
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
}
}
$threshold = 1e3;
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);
return array_keys($alternatives);
}
}

View File

@ -12,6 +12,8 @@
namespace Symfony\Component\Messenger\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
@ -31,9 +33,9 @@ class DebugCommand extends Command
public function __construct(array $mapping)
{
parent::__construct();
$this->mapping = $mapping;
parent::__construct();
}
/**
@ -42,13 +44,18 @@ class DebugCommand extends Command
protected function configure()
{
$this
->setDescription('Lists messages you can dispatch using the message bus')
->addArgument('bus', InputArgument::OPTIONAL, sprintf('The bus id (one of %s)', implode(', ', array_keys($this->mapping))), null)
->setDescription('Lists messages you can dispatch using the message buses')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays all messages that can be
dispatched using the message bus:
dispatched using the message buses:
<info>php %command.full_name%</info>
Or for a specific bus only:
<info>php %command.full_name% command_bus</info>
EOF
)
;
@ -61,21 +68,33 @@ EOF
{
$io = new SymfonyStyle($input, $output);
$io->title('Messenger');
$io->text('The following messages can be dispatched:');
$io->newLine();
$tableRows = array();
foreach ($this->mapping as $message => $handlers) {
$tableRows[] = array(sprintf('<fg=cyan>%s</fg=cyan>', $message));
foreach ($handlers as $handler) {
$tableRows[] = array(sprintf(' handled by %s', $handler));
$mapping = $this->mapping;
if ($bus = $input->getArgument('bus')) {
if (!isset($mapping[$bus])) {
throw new RuntimeException(sprintf('Bus "%s" does not exist. Known buses are %s.', $bus, implode(', ', array_keys($this->mapping))));
}
$mapping = array($bus => $mapping[$bus]);
}
if ($tableRows) {
$io->table(array(), $tableRows);
} else {
$io->text('No messages were found that have valid handlers.');
foreach ($mapping as $bus => $handlersByMessage) {
$io->section($bus);
$tableRows = array();
foreach ($handlersByMessage as $message => $handlers) {
$tableRows[] = array(sprintf('<fg=cyan>%s</fg=cyan>', $message));
foreach ($handlers as $handler) {
$tableRows[] = array(sprintf(' handled by <info>%s</>', $handler));
}
}
if ($tableRows) {
$io->text('The following messages can be dispatched:');
$io->newLine();
$io->table(array(), $tableRows);
} else {
$io->warning(sprintf('No handled message found in bus "%s".', $bus));
}
}
}
}

View File

@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Messenger\Handler\ChainHandler;
use Symfony\Component\Messenger\Handler\Locator\ContainerHandlerLocator;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
use Symfony\Component\Messenger\TraceableMessageBus;
@ -51,11 +52,13 @@ class MessengerPass implements CompilerPassInterface
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('messenger.handler_resolver')) {
if (!$container->has('message_bus')) {
return;
}
$busIds = array();
foreach ($container->findTaggedServiceIds($this->busTag) as $busId => $tags) {
$busIds[] = $busId;
if ($container->hasParameter($busMiddlewareParameter = $busId.'.middleware')) {
$this->registerBusMiddleware($container, $busId, $container->getParameter($busMiddlewareParameter));
@ -69,16 +72,20 @@ class MessengerPass implements CompilerPassInterface
$this->registerReceivers($container);
$this->registerSenders($container);
$this->registerHandlers($container);
$this->registerHandlers($container, $busIds);
}
private function registerHandlers(ContainerBuilder $container)
private function registerHandlers(ContainerBuilder $container, array $busIds)
{
$definitions = array();
$handlersByMessage = array();
$handlersByBusAndMessage = array();
foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) {
foreach ($tags as $tag) {
if (isset($tag['bus']) && !\in_array($tag['bus'], $busIds, true)) {
throw new RuntimeException(sprintf('Invalid handler service "%s": bus "%s" specified on the tag "%s" does not exist (known ones are: %s).', $serviceId, $tag['bus'], $this->handlerTag, implode(', ', $busIds)));
}
$r = $container->getReflectionClass($container->getDefinition($serviceId)->getClass());
if (isset($tag['handles'])) {
@ -88,6 +95,7 @@ class MessengerPass implements CompilerPassInterface
}
$priority = $tag['priority'] ?? 0;
$handlerBuses = (array) ($tag['bus'] ?? $busIds);
foreach ($handles as $messageClass => $method) {
if (\is_int($messageClass)) {
@ -123,38 +131,57 @@ class MessengerPass implements CompilerPassInterface
$definitions[$serviceId = '.messenger.method_on_object_wrapper.'.ContainerBuilder::hash($messageClass.':'.$messagePriority.':'.$serviceId.':'.$method)] = $wrapperDefinition;
}
$handlersByMessage[$messageClass][$messagePriority][] = new Reference($serviceId);
foreach ($handlerBuses as $handlerBus) {
$handlersByBusAndMessage[$handlerBus][$messageClass][$messagePriority][] = $serviceId;
}
}
}
}
foreach ($handlersByMessage as $message => $handlers) {
krsort($handlersByMessage[$message]);
$handlersByMessage[$message] = array_merge(...$handlersByMessage[$message]);
foreach ($handlersByBusAndMessage as $bus => $handlersByMessage) {
foreach ($handlersByMessage as $message => $handlersByPriority) {
krsort($handlersByPriority);
$handlersByBusAndMessage[$bus][$message] = array_unique(array_merge(...$handlersByPriority));
}
}
$handlersLocatorMapping = array();
foreach ($handlersByMessage as $message => $handlers) {
if (1 === \count($handlers)) {
$handlersLocatorMapping['handler.'.$message] = current($handlers);
} else {
$d = new Definition(ChainHandler::class, array($handlers));
$d->setPrivate(true);
$serviceId = hash('sha1', $message);
$definitions[$serviceId] = $d;
$handlersLocatorMapping['handler.'.$message] = new Reference($serviceId);
$handlersLocatorMappingByBus = array();
foreach ($handlersByBusAndMessage as $bus => $handlersByMessage) {
foreach ($handlersByMessage as $message => $handlersIds) {
if (1 === \count($handlersIds)) {
$handlersLocatorMappingByBus[$bus]['handler.'.$message] = new Reference(current($handlersIds));
} else {
$chainHandler = new Definition(ChainHandler::class, array(array_map(function (string $handlerId): Reference {
return new Reference($handlerId);
}, $handlersIds)));
$chainHandler->setPrivate(true);
$serviceId = '.messenger.chain_handler.'.ContainerBuilder::hash($bus.$message);
$definitions[$serviceId] = $chainHandler;
$handlersLocatorMappingByBus[$bus]['handler.'.$message] = new Reference($serviceId);
}
}
}
$container->addDefinitions($definitions);
$handlerResolver = $container->getDefinition('messenger.handler_resolver');
$handlerResolver->replaceArgument(0, ServiceLocatorTagPass::register($container, $handlersLocatorMapping));
foreach ($busIds as $bus) {
$container->register($resolverName = "$bus.messenger.handler_resolver", ContainerHandlerLocator::class)
->setArgument(0, ServiceLocatorTagPass::register($container, $handlersLocatorMappingByBus[$bus] ?? array()))
;
if ($container->has($callMessageHandlerId = "$bus.middleware.call_message_handler")) {
$container->getDefinition($callMessageHandlerId)
->replaceArgument(0, new Reference($resolverName))
;
}
}
if ($container->hasDefinition('console.command.messenger_debug')) {
$container->getDefinition('console.command.messenger_debug')
->replaceArgument(0, array_map(function (array $handlers): array {
return array_map('strval', $handlers);
}, $handlersByMessage));
$debugCommandMapping = $handlersByBusAndMessage;
foreach ($busIds as $bus) {
if (!isset($debugCommandMapping[$bus])) {
$debugCommandMapping[$bus] = array();
}
}
$container->getDefinition('console.command.messenger_debug')->replaceArgument(0, $debugCommandMapping);
}
}
@ -193,9 +220,8 @@ class MessengerPass implements CompilerPassInterface
private function registerReceivers(ContainerBuilder $container)
{
$receiverMapping = array();
$taggedReceivers = $container->findTaggedServiceIds($this->receiverTag);
foreach ($taggedReceivers as $id => $tags) {
foreach ($container->findTaggedServiceIds($this->receiverTag) as $id => $tags) {
$receiverClass = $container->findDefinition($id)->getClass();
if (!is_subclass_of($receiverClass, ReceiverInterface::class)) {
throw new RuntimeException(sprintf('Invalid receiver "%s": class "%s" must implement interface "%s".', $id, $receiverClass, ReceiverInterface::class));
@ -210,8 +236,12 @@ class MessengerPass implements CompilerPassInterface
}
}
if (1 === \count($taggedReceivers) && $container->hasDefinition('console.command.messenger_consume_messages')) {
$container->getDefinition('console.command.messenger_consume_messages')->replaceArgument(3, (string) current($receiverMapping));
if ($container->hasDefinition('console.command.messenger_consume_messages')) {
$receiverNames = array();
foreach ($receiverMapping as $name => $reference) {
$receiverNames[(string) $reference] = $name;
}
$container->getDefinition('console.command.messenger_consume_messages')->replaceArgument(3, array_values($receiverNames));
}
$container->getDefinition('messenger.receiver_locator')->replaceArgument(0, $receiverMapping);

View File

@ -16,7 +16,9 @@ use Symfony\Component\Messenger\Asynchronous\Middleware\SendMessageMiddleware;
use Symfony\Component\Messenger\Asynchronous\Routing\SenderLocatorInterface;
use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Tests\Asynchronous\Routing\ChildDummyMessage;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessageInterface;
use Symfony\Component\Messenger\Transport\SenderInterface;
class SendMessageMiddlewareTest extends TestCase
@ -27,9 +29,7 @@ class SendMessageMiddlewareTest extends TestCase
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$next = $this->createPartialMock(\stdClass::class, array('__invoke'));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator(array(
$sender,
)));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator($sender));
$sender->expects($this->once())->method('send')->with(Envelope::wrap($message));
$next->expects($this->never())->method($this->anything());
@ -43,9 +43,7 @@ class SendMessageMiddlewareTest extends TestCase
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$next = $this->createPartialMock(\stdClass::class, array('__invoke'));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator(array(
$sender,
)));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator($sender));
$sender->expects($this->once())->method('send')->with($envelope);
$next->expects($this->never())->method($this->anything());
@ -53,16 +51,63 @@ class SendMessageMiddlewareTest extends TestCase
$middleware->handle($envelope, $next);
}
public function testItAlsoCallsTheNextMiddlewareIfASenderIsNull()
public function testItAlsoCallsTheNextMiddlewareBasedOnTheMessageClass()
{
$message = new DummyMessage('Hey');
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$next = $this->createPartialMock(\stdClass::class, array('__invoke'));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator(array(
$sender,
null,
)));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator($sender), array(
DummyMessage::class => true,
));
$sender->expects($this->once())->method('send')->with(Envelope::wrap($message));
$next->expects($this->once())->method($this->anything());
$middleware->handle($message, $next);
}
public function testItAlsoCallsTheNextMiddlewareBasedOnTheMessageParentClass()
{
$message = new ChildDummyMessage('Hey');
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$next = $this->createPartialMock(\stdClass::class, array('__invoke'));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator($sender), array(
DummyMessage::class => true,
));
$sender->expects($this->once())->method('send')->with(Envelope::wrap($message));
$next->expects($this->once())->method($this->anything());
$middleware->handle($message, $next);
}
public function testItAlsoCallsTheNextMiddlewareBasedOnTheMessageInterface()
{
$message = new DummyMessage('Hey');
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$next = $this->createPartialMock(\stdClass::class, array('__invoke'));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator($sender), array(
DummyMessageInterface::class => true,
));
$sender->expects($this->once())->method('send')->with(Envelope::wrap($message));
$next->expects($this->once())->method($this->anything());
$middleware->handle($message, $next);
}
public function testItAlsoCallsTheNextMiddlewareBasedOnWildcard()
{
$message = new DummyMessage('Hey');
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$next = $this->createPartialMock(\stdClass::class, array('__invoke'));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator($sender), array(
'*' => true,
));
$sender->expects($this->once())->method('send')->with(Envelope::wrap($message));
$next->expects($this->once())->method($this->anything());
@ -75,7 +120,7 @@ class SendMessageMiddlewareTest extends TestCase
$message = new DummyMessage('Hey');
$next = $this->createPartialMock(\stdClass::class, array('__invoke'));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator(array()));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator(null));
$next->expects($this->once())->method($this->anything());
@ -89,9 +134,7 @@ class SendMessageMiddlewareTest extends TestCase
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$next = $this->createPartialMock(\stdClass::class, array('__invoke'));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator(array(
$sender,
)));
$middleware = new SendMessageMiddleware(new InMemorySenderLocator($sender));
$sender->expects($this->never())->method('send');
$next->expects($this->once())->method('__invoke')->with($envelope);
@ -102,15 +145,15 @@ class SendMessageMiddlewareTest extends TestCase
class InMemorySenderLocator implements SenderLocatorInterface
{
private $senders;
private $sender;
public function __construct(array $senders)
public function __construct(?SenderInterface $sender)
{
$this->senders = $senders;
$this->sender = $sender;
}
public function getSendersForMessage($message): array
public function getSenderForMessage($message): ?SenderInterface
{
return $this->senders;
return $this->sender;
}
}

View File

@ -28,13 +28,11 @@ class SenderLocatorTest extends TestCase
$container->set('my_amqp_sender', $sender);
$locator = new SenderLocator($container, array(
DummyMessage::class => array(
'my_amqp_sender',
),
DummyMessage::class => 'my_amqp_sender',
));
$this->assertSame(array($sender), $locator->getSendersForMessage(new DummyMessage('Hello')));
$this->assertSame(array(), $locator->getSendersForMessage(new SecondMessage()));
$this->assertSame($sender, $locator->getSenderForMessage(new DummyMessage('Hello')));
$this->assertNull($locator->getSenderForMessage(new SecondMessage()));
}
public function testItReturnsTheSenderBasedOnTheMessageParentClass()
@ -48,15 +46,12 @@ class SenderLocatorTest extends TestCase
$container->set('my_api_sender', $apiSender);
$locator = new SenderLocator($container, array(
DummyMessageInterface::class => array(
'my_api_sender',
),
DummyMessage::class => array(
'my_amqp_sender',
),
DummyMessageInterface::class => 'my_api_sender',
DummyMessage::class => 'my_amqp_sender',
));
$this->assertSame(array($sender), $locator->getSendersForMessage(new ChildDummyMessage('Hello')));
$this->assertSame(array(), $locator->getSendersForMessage(new SecondMessage()));
$this->assertSame($sender, $locator->getSenderForMessage(new ChildDummyMessage('Hello')));
$this->assertNull($locator->getSenderForMessage(new SecondMessage()));
}
public function testItReturnsTheSenderBasedOnTheMessageInterface()
@ -67,13 +62,11 @@ class SenderLocatorTest extends TestCase
$container->set('my_amqp_sender', $sender);
$locator = new SenderLocator($container, array(
DummyMessageInterface::class => array(
'my_amqp_sender',
),
DummyMessageInterface::class => 'my_amqp_sender',
));
$this->assertSame(array($sender), $locator->getSendersForMessage(new DummyMessage('Hello')));
$this->assertSame(array(), $locator->getSendersForMessage(new SecondMessage()));
$this->assertSame($sender, $locator->getSenderForMessage(new DummyMessage('Hello')));
$this->assertNull($locator->getSenderForMessage(new SecondMessage()));
}
public function testItSupportsAWildcardInsteadOfTheMessageClass()
@ -87,16 +80,12 @@ class SenderLocatorTest extends TestCase
$container->set('my_api_sender', $apiSender);
$locator = new SenderLocator($container, array(
DummyMessage::class => array(
'my_amqp_sender',
),
'*' => array(
'my_api_sender',
),
DummyMessage::class => 'my_amqp_sender',
'*' => 'my_api_sender',
));
$this->assertSame(array($sender), $locator->getSendersForMessage(new DummyMessage('Hello')));
$this->assertSame(array($apiSender), $locator->getSendersForMessage(new SecondMessage()));
$this->assertSame($sender, $locator->getSenderForMessage(new DummyMessage('Hello')));
$this->assertSame($apiSender, $locator->getSenderForMessage(new SecondMessage()));
}
}

View File

@ -20,15 +20,15 @@ class ConsumeMessagesCommandTest extends TestCase
{
public function testConfigurationWithDefaultReceiver()
{
$command = new ConsumeMessagesCommand($this->createMock(MessageBus::class), $this->createMock(ServiceLocator::class), null, 'messenger.transport.amqp');
$command = new ConsumeMessagesCommand($this->createMock(MessageBus::class), $this->createMock(ServiceLocator::class), null, array('amqp'));
$inputArgument = $command->getDefinition()->getArgument('receiver');
$this->assertFalse($inputArgument->isRequired());
$this->assertSame('messenger.transport.amqp', $inputArgument->getDefault());
$this->assertSame('amqp', $inputArgument->getDefault());
}
public function testConfigurationWithoutDefaultReceiver()
{
$command = new ConsumeMessagesCommand($this->createMock(MessageBus::class), $this->createMock(ServiceLocator::class));
$command = new ConsumeMessagesCommand($this->createMock(MessageBus::class), $this->createMock(ServiceLocator::class), null, array('amqp', 'dummy'));
$inputArgument = $command->getDefinition()->getArgument('receiver');
$this->assertTrue($inputArgument->isRequired());
$this->assertNull($inputArgument->getDefault());

View File

@ -0,0 +1,155 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Messenger\Command\DebugCommand;
use Symfony\Component\Messenger\Tests\Fixtures\DummyCommand;
use Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler;
use Symfony\Component\Messenger\Tests\Fixtures\DummyQuery;
use Symfony\Component\Messenger\Tests\Fixtures\DummyQueryHandler;
use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage;
use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler;
/**
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class DebugCommandTest extends TestCase
{
protected function setUp()
{
putenv('COLUMNS='.(119 + strlen(PHP_EOL)));
}
protected function tearDown()
{
putenv('COLUMNS=');
}
public function testOutput()
{
$command = new DebugCommand(
array(
'command_bus' => array(
DummyCommand::class => array(DummyCommandHandler::class),
MultipleBusesMessage::class => array(MultipleBusesMessageHandler::class),
),
'query_bus' => array(
DummyQuery::class => array(DummyQueryHandler::class),
MultipleBusesMessage::class => array(MultipleBusesMessageHandler::class),
),
)
);
$tester = new CommandTester($command);
$tester->execute(array(), array('decorated' => false));
$this->assertSame(<<<TXT
Messenger
=========
command_bus
-----------
The following messages can be dispatched:
---------------------------------------------------------------------------------------
Symfony\Component\Messenger\Tests\Fixtures\DummyCommand
handled by Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler
Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage
handled by Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler
---------------------------------------------------------------------------------------
query_bus
---------
The following messages can be dispatched:
---------------------------------------------------------------------------------------
Symfony\Component\Messenger\Tests\Fixtures\DummyQuery
handled by Symfony\Component\Messenger\Tests\Fixtures\DummyQueryHandler
Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage
handled by Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler
---------------------------------------------------------------------------------------
TXT
, $tester->getDisplay(true)
);
$tester->execute(array('bus' => 'query_bus'), array('decorated' => false));
$this->assertSame(<<<TXT
Messenger
=========
query_bus
---------
The following messages can be dispatched:
---------------------------------------------------------------------------------------
Symfony\Component\Messenger\Tests\Fixtures\DummyQuery
handled by Symfony\Component\Messenger\Tests\Fixtures\DummyQueryHandler
Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage
handled by Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler
---------------------------------------------------------------------------------------
TXT
, $tester->getDisplay(true)
);
}
public function testOutputWithoutMessages()
{
$command = new DebugCommand(array('command_bus' => array(), 'query_bus' => array()));
$tester = new CommandTester($command);
$tester->execute(array(), array('decorated' => false));
$this->assertSame(<<<TXT
Messenger
=========
command_bus
-----------
[WARNING] No handled message found in bus "command_bus".
query_bus
---------
[WARNING] No handled message found in bus "query_bus".
TXT
, $tester->getDisplay(true)
);
}
/**
* @expectedException \Symfony\Component\Console\Exception\RuntimeException
* @expectedExceptionMessage Bus "unknown_bus" does not exist. Known buses are command_bus, query_bus.
*/
public function testExceptionOnUnknownBusArgument()
{
$command = new DebugCommand(array('command_bus' => array(), 'query_bus' => array()));
$tester = new CommandTester($command);
$tester->execute(array('bus' => 'unknown_bus'), array('decorated' => false));
}
}

View File

@ -14,30 +14,38 @@ namespace Symfony\Component\Messenger\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Messenger\Command\ConsumeMessagesCommand;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver;
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender;
use Symfony\Component\Messenger\Handler\Locator\ContainerHandlerLocator;
use Symfony\Component\Messenger\Command\DebugCommand;
use Symfony\Component\Messenger\DataCollector\MessengerDataCollector;
use Symfony\Component\Messenger\DependencyInjection\MessengerPass;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Handler\ChainHandler;
use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Middleware\AllowNoHandlerMiddleware;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Tests\Fixtures\DummyCommand;
use Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Tests\Fixtures\DummyQuery;
use Symfony\Component\Messenger\Tests\Fixtures\DummyQueryHandler;
use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage;
use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler;
use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage;
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver;
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender;
use Symfony\Component\Messenger\Transport\ReceiverInterface;
class MessengerPassTest extends TestCase
{
public function testProcess()
{
$container = $this->getContainerBuilder();
$container = $this->getContainerBuilder($busId = 'message_bus');
$container
->register(DummyHandler::class, DummyHandler::class)
->addTag('messenger.message_handler')
@ -55,7 +63,7 @@ class MessengerPassTest extends TestCase
$this->assertFalse($container->hasDefinition('messenger.middleware.debug.logging'));
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0));
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition("$busId.messenger.handler_resolver")->getArgument(0));
$this->assertSame(ServiceLocator::class, $handlerLocatorDefinition->getClass());
$this->assertEquals(
array(
@ -71,9 +79,68 @@ class MessengerPassTest extends TestCase
);
}
public function testProcessHandlersByBus()
{
$container = $this->getContainerBuilder($commandBusId = 'command_bus');
$container->register($queryBusId = 'query_bus', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
$container->register('messenger.middleware.call_message_handler', HandleMessageMiddleware::class)
->addArgument(null)
->setAbstract(true)
;
$middlewares = array(array('id' => 'call_message_handler'));
$container->setParameter($commandBusId.'.middleware', $middlewares);
$container->setParameter($queryBusId.'.middleware', $middlewares);
$container->register(DummyCommandHandler::class)->addTag('messenger.message_handler', array('bus' => $commandBusId));
$container->register(DummyQueryHandler::class)->addTag('messenger.message_handler', array('bus' => $queryBusId));
$container->register(MultipleBusesMessageHandler::class)
->addTag('messenger.message_handler', array('bus' => $commandBusId))
->addTag('messenger.message_handler', array('bus' => $queryBusId))
;
(new ResolveClassPass())->process($container);
(new MessengerPass())->process($container);
$commandBusHandlerLocatorDefinition = $container->getDefinition($container->getDefinition("$commandBusId.messenger.handler_resolver")->getArgument(0));
$this->assertSame(ServiceLocator::class, $commandBusHandlerLocatorDefinition->getClass());
$this->assertEquals(
array(
'handler.'.DummyCommand::class => new ServiceClosureArgument(new Reference(DummyCommandHandler::class)),
'handler.'.MultipleBusesMessage::class => new ServiceClosureArgument(new Reference(MultipleBusesMessageHandler::class)),
),
$commandBusHandlerLocatorDefinition->getArgument(0)
);
$queryBusHandlerLocatorDefinition = $container->getDefinition($container->getDefinition("$queryBusId.messenger.handler_resolver")->getArgument(0));
$this->assertSame(ServiceLocator::class, $queryBusHandlerLocatorDefinition->getClass());
$this->assertEquals(
array(
'handler.'.DummyQuery::class => new ServiceClosureArgument(new Reference(DummyQueryHandler::class)),
'handler.'.MultipleBusesMessage::class => new ServiceClosureArgument(new Reference(MultipleBusesMessageHandler::class)),
),
$queryBusHandlerLocatorDefinition->getArgument(0)
);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler": bus "unknown_bus" specified on the tag "messenger.message_handler" does not exist (known ones are: command_bus).
*/
public function testProcessTagWithUnknownBus()
{
$container = $this->getContainerBuilder($commandBusId = 'command_bus');
$container->register(DummyCommandHandler::class)->addTag('messenger.message_handler', array('bus' => 'unknown_bus'));
(new ResolveClassPass())->process($container);
(new MessengerPass())->process($container);
}
public function testGetClassesFromTheHandlerSubscriberInterface()
{
$container = $this->getContainerBuilder();
$container = $this->getContainerBuilder($busId = 'message_bus');
$container
->register(HandlerWithMultipleMessages::class, HandlerWithMultipleMessages::class)
->addTag('messenger.message_handler')
@ -85,7 +152,7 @@ class MessengerPassTest extends TestCase
(new MessengerPass())->process($container);
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0));
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition("$busId.messenger.handler_resolver")->getArgument(0));
$handlerMapping = $handlerLocatorDefinition->getArgument(0);
$this->assertArrayHasKey('handler.'.DummyMessage::class, $handlerMapping);
@ -101,7 +168,7 @@ class MessengerPassTest extends TestCase
public function testGetClassesAndMethodsAndPrioritiesFromTheSubscriber()
{
$container = $this->getContainerBuilder();
$container = $this->getContainerBuilder($busId = 'message_bus');
$container
->register(HandlerMappingMethods::class, HandlerMappingMethods::class)
->addTag('messenger.message_handler')
@ -113,7 +180,7 @@ class MessengerPassTest extends TestCase
(new MessengerPass())->process($container);
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0));
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition("$busId.messenger.handler_resolver")->getArgument(0));
$handlerMapping = $handlerLocatorDefinition->getArgument(0);
$this->assertArrayHasKey('handler.'.DummyMessage::class, $handlerMapping);
@ -138,6 +205,7 @@ class MessengerPassTest extends TestCase
public function testThrowsExceptionIfTheHandlerMethodDoesNotExist()
{
$container = $this->getContainerBuilder();
$container->register('message_bus', MessageBusInterface::class)->addTag('messenger.bus');
$container
->register(HandlerMappingWithNonExistentMethod::class, HandlerMappingWithNonExistentMethod::class)
->addTag('messenger.message_handler')
@ -149,6 +217,7 @@ class MessengerPassTest extends TestCase
public function testItRegistersReceivers()
{
$container = $this->getContainerBuilder();
$container->register('message_bus', MessageBusInterface::class)->addTag('messenger.bus');
$container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver', array('alias' => 'amqp'));
(new MessengerPass())->process($container);
@ -159,6 +228,7 @@ class MessengerPassTest extends TestCase
public function testItRegistersReceiversWithoutTagName()
{
$container = $this->getContainerBuilder();
$container->register('message_bus', MessageBusInterface::class)->addTag('messenger.bus');
$container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver');
(new MessengerPass())->process($container);
@ -166,24 +236,7 @@ class MessengerPassTest extends TestCase
$this->assertEquals(array(AmqpReceiver::class => new Reference(AmqpReceiver::class)), $container->getDefinition('messenger.receiver_locator')->getArgument(0));
}
public function testItRegistersOneReceiverAndSetsTheDefaultOneOnTheCommand()
{
$container = $this->getContainerBuilder();
$container->register('console.command.messenger_consume_messages', ConsumeMessagesCommand::class)->setArguments(array(
new Reference('message_bus'),
new Reference('messenger.receiver_locator'),
null,
null,
));
$container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver', array('alias' => 'amqp'));
(new MessengerPass())->process($container);
$this->assertSame(AmqpReceiver::class, $container->getDefinition('console.command.messenger_consume_messages')->getArgument(3));
}
public function testItRegistersMultipleReceiversAndDoesNotSetTheDefaultOneOnTheCommand()
public function testItRegistersMultipleReceiversAndSetsTheReceiverNamesOnTheCommand()
{
$container = $this->getContainerBuilder();
$container->register('console.command.messenger_consume_messages', ConsumeMessagesCommand::class)->setArguments(array(
@ -198,7 +251,7 @@ class MessengerPassTest extends TestCase
(new MessengerPass())->process($container);
$this->assertNull($container->getDefinition('console.command.messenger_consume_messages')->getArgument(3));
$this->assertSame(array('amqp', 'dummy'), $container->getDefinition('console.command.messenger_consume_messages')->getArgument(3));
}
public function testItRegistersSenders()
@ -343,9 +396,8 @@ class MessengerPassTest extends TestCase
{
$dataCollector = $this->getMockBuilder(MessengerDataCollector::class)->getMock();
$container = $this->getContainerBuilder();
$container = $this->getContainerBuilder($fooBusId = 'messenger.bus.foo');
$container->register('messenger.data_collector', $dataCollector);
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->addTag('messenger.bus');
$container->setParameter('kernel.debug', true);
(new MessengerPass())->process($container);
@ -357,8 +409,7 @@ class MessengerPassTest extends TestCase
public function testRegistersMiddlewareFromServices()
{
$container = $this->getContainerBuilder();
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
$container = $this->getContainerBuilder($fooBusId = 'messenger.bus.foo');
$container->register('messenger.middleware.allow_no_handler', AllowNoHandlerMiddleware::class)->setAbstract(true);
$container->register('middleware_with_factory', UselessMiddleware::class)->addArgument('some_default')->setAbstract(true);
$container->register('middleware_with_factory_using_default', UselessMiddleware::class)->addArgument('some_default')->setAbstract(true);
@ -405,8 +456,7 @@ class MessengerPassTest extends TestCase
*/
public function testCannotRegistersAnUndefinedMiddleware()
{
$container = $this->getContainerBuilder();
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
$container = $this->getContainerBuilder($fooBusId = 'messenger.bus.foo');
$container->setParameter($middlewareParameter = $fooBusId.'.middleware', array(
array('id' => 'not_defined_middleware', 'arguments' => array()),
));
@ -420,9 +470,8 @@ class MessengerPassTest extends TestCase
*/
public function testMiddlewareFactoryDefinitionMustBeAbstract()
{
$container = $this->getContainerBuilder();
$container = $this->getContainerBuilder($fooBusId = 'messenger.bus.foo');
$container->register('not_an_abstract_definition', UselessMiddleware::class);
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus', array('name' => 'foo'));
$container->setParameter($middlewareParameter = $fooBusId.'.middleware', array(
array('id' => 'not_an_abstract_definition', 'arguments' => array('foo')),
));
@ -430,18 +479,58 @@ class MessengerPassTest extends TestCase
(new MessengerPass())->process($container);
}
private function getContainerBuilder(): ContainerBuilder
public function testItRegistersTheDebugCommand()
{
$container = $this->getContainerBuilder($commandBusId = 'command_bus');
$container->register($queryBusId = 'query_bus', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
$container->register($emptyBus = 'empty_bus', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
$container->register('messenger.middleware.call_message_handler', HandleMessageMiddleware::class)
->addArgument(null)
->setAbstract(true)
;
$container->register('console.command.messenger_debug', DebugCommand::class)->addArgument(array());
$middlewares = array(array('id' => 'call_message_handler'));
$container->setParameter($commandBusId.'.middleware', $middlewares);
$container->setParameter($queryBusId.'.middleware', $middlewares);
$container->register(DummyCommandHandler::class)->addTag('messenger.message_handler', array('bus' => $commandBusId));
$container->register(DummyQueryHandler::class)->addTag('messenger.message_handler', array('bus' => $queryBusId));
$container->register(MultipleBusesMessageHandler::class)
->addTag('messenger.message_handler', array('bus' => $commandBusId))
->addTag('messenger.message_handler', array('bus' => $queryBusId))
;
(new ResolveClassPass())->process($container);
(new MessengerPass())->process($container);
$this->assertEquals(array(
$commandBusId => array(
DummyCommand::class => array(DummyCommandHandler::class),
MultipleBusesMessage::class => array(MultipleBusesMessageHandler::class),
),
$queryBusId => array(
DummyQuery::class => array(DummyQueryHandler::class),
MultipleBusesMessage::class => array(MultipleBusesMessageHandler::class),
),
$emptyBus => array(),
), $container->getDefinition('console.command.messenger_debug')->getArgument(0));
}
private function getContainerBuilder(string $busId = 'message_bus'): ContainerBuilder
{
$container = new ContainerBuilder();
$container->setParameter('kernel.debug', true);
$container
->register('messenger.sender_locator', ServiceLocator::class)
->addArgument(new Reference('service_container'))
;
$container->register($busId, MessageBusInterface::class)->addTag('messenger.bus')->setArgument(0, array());
if ('message_bus' !== $busId) {
$container->setAlias('message_bus', $busId);
}
$container
->register('messenger.handler_resolver', ContainerHandlerLocator::class)
->register('messenger.sender_locator', ServiceLocator::class)
->addArgument(new Reference('service_container'))
;

View File

@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class DummyCommand
{
}

View File

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class DummyCommandHandler
{
public function __invoke(DummyCommand $command)
{
}
}

View File

@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class DummyQuery
{
}

View File

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class DummyQueryHandler
{
public function __invoke(DummyQuery $query)
{
}
}

View File

@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class MultipleBusesMessage
{
}

View File

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class MultipleBusesMessageHandler
{
public function __invoke(MultipleBusesMessage $message)
{
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Transport;
/**
* @author Tobias Schultze <http://tobion.de>
*/
class ChainSender implements SenderInterface
{
private $senders;
/**
* @param SenderInterface[] $senders
*/
public function __construct(iterable $senders)
{
$this->senders = $senders;
}
/**
* {@inheritdoc}
*/
public function send($message): void
{
foreach ($this->senders as $sender) {
$sender->send($message);
}
}
}

View File

@ -162,6 +162,9 @@ class RouteCollection implements \IteratorAggregate, \Countable
foreach ($this->routes as $name => $route) {
$prefixedRoutes[$prefix.$name] = $route;
if (null !== $name = $route->getDefault('_canonical_route')) {
$route->setDefault('_canonical_route', $prefix.$name);
}
}
$this->routes = $prefixedRoutes;

View File

@ -317,4 +317,17 @@ class RouteCollectionTest extends TestCase
$this->assertNull($collection->get('foo'));
$this->assertNull($collection->get('bar'));
}
public function testAddNamePrefixCanonicalRouteName()
{
$collection = new RouteCollection();
$collection->add('foo', new Route('/foo', array('_canonical_route' => 'foo')));
$collection->add('bar', new Route('/bar', array('_canonical_route' => 'bar')));
$collection->add('api_foo', new Route('/api/foo', array('_canonical_route' => 'api_foo')));
$collection->addNamePrefix('api_');
$this->assertEquals('api_foo', $collection->get('api_foo')->getDefault('_canonical_route'));
$this->assertEquals('api_bar', $collection->get('api_bar')->getDefault('_canonical_route'));
$this->assertEquals('api_api_foo', $collection->get('api_api_foo')->getDefault('_canonical_route'));
}
}

View File

@ -12,6 +12,8 @@
namespace Symfony\Component\Security\Csrf\Tests\TokenStorage;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
/**
@ -22,7 +24,7 @@ class SessionTokenStorageTest extends TestCase
const SESSION_NAMESPACE = 'foobar';
/**
* @var \PHPUnit_Framework_MockObject_MockObject
* @var Session
*/
private $session;
@ -33,118 +35,53 @@ class SessionTokenStorageTest extends TestCase
protected function setUp()
{
$this->session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')
->disableOriginalConstructor()
->getMock();
$this->session = new Session(new MockArraySessionStorage());
$this->storage = new SessionTokenStorage($this->session, self::SESSION_NAMESPACE);
}
public function testStoreTokenInClosedSession()
public function testStoreTokenInNotStartedSessionStartsTheSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('set')
->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
$this->storage->setToken('token_id', 'TOKEN');
$this->assertTrue($this->session->isStarted());
}
public function testStoreTokenInActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('set')
->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
$this->session->start();
$this->storage->setToken('token_id', 'TOKEN');
$this->assertSame('TOKEN', $this->session->get(self::SESSION_NAMESPACE.'/token_id'));
}
public function testCheckTokenInClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('RESULT'));
$this->assertSame('RESULT', $this->storage->hasToken('token_id'));
$this->assertTrue($this->storage->hasToken('token_id'));
$this->assertTrue($this->session->isStarted());
}
public function testCheckTokenInActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->start();
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('RESULT'));
$this->assertSame('RESULT', $this->storage->hasToken('token_id'));
$this->assertTrue($this->storage->hasToken('token_id'));
}
public function testGetExistingTokenFromClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(true));
$this->session->expects($this->once())
->method('get')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('RESULT'));
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
$this->assertSame('RESULT', $this->storage->getToken('token_id'));
$this->assertTrue($this->session->isStarted());
}
public function testGetExistingTokenFromActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(true));
$this->session->expects($this->once())
->method('get')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('RESULT'));
$this->session->start();
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT');
$this->assertSame('RESULT', $this->storage->getToken('token_id'));
}
@ -154,18 +91,6 @@ class SessionTokenStorageTest extends TestCase
*/
public function testGetNonExistingTokenFromClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(false));
$this->storage->getToken('token_id');
}
@ -174,85 +99,33 @@ class SessionTokenStorageTest extends TestCase
*/
public function testGetNonExistingTokenFromActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('has')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(false));
$this->session->start();
$this->storage->getToken('token_id');
}
public function testRemoveNonExistingTokenFromClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('remove')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(null));
$this->assertNull($this->storage->removeToken('token_id'));
}
public function testRemoveNonExistingTokenFromActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('remove')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue(null));
$this->session->start();
$this->assertNull($this->storage->removeToken('token_id'));
}
public function testRemoveExistingTokenFromClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(false));
$this->session->expects($this->once())
->method('start');
$this->session->expects($this->once())
->method('remove')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('TOKEN'));
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
$this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
}
public function testRemoveExistingTokenFromActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
->will($this->returnValue(true));
$this->session->expects($this->never())
->method('start');
$this->session->expects($this->once())
->method('remove')
->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('TOKEN'));
$this->session->start();
$this->session->set(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
$this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
}

View File

@ -107,6 +107,7 @@ class PluralizationRules
case 'nl':
case 'nn':
case 'no':
case 'oc':
case 'om':
case 'or':
case 'pa':

View File

@ -87,7 +87,7 @@ class UrlValidator extends ConstraintValidator
Url::CHECK_DNS_TYPE_SOA,
Url::CHECK_DNS_TYPE_SRV,
Url::CHECK_DNS_TYPE_TXT,
))) {
), true)) {
throw new InvalidOptionsException(sprintf('Invalid value for option "checkDNS" in constraint %s', get_class($constraint)), array('checkDNS'));
}