Merge remote-tracking branch 'origin/4.3'
* origin/4.3: deprecate calling createChildContext without the format parameter [EventDispatcher] Fix interface name used in error messages [FrameworkBundle] Add cache configuration for PropertyInfo Update dependencies in the main component Drop useless executable bit [Doctrine][PropertyInfo] Detect if the ID is writeable Add transport in subscriber's phpdoc [Validator] Autovalidation: skip readonly props [DI] default to service id - *not* FQCN - when building tagged locators [Cache] Log a more readable error message when saving into cache fails Update WorkflowEvents.php [Messenger] On failure retry, make message appear received from original sender [Messenger] remove send_and_handle option which can be achieved with SyncTransport Fixing tests - passing pdo is not wrapped for some reason in dbal Changing how RoutableMessageBus fallback bus works [Serializer] Fix BC break: DEPTH_KEY_PATTERN must be public [FrameworkBundle] Fixed issue when a parameter container a '%' Fix the interface incompatibility of EventDispatchers [TwigBundle] fixed Mailer integration in Twig [Form] Add intl/choice_translation_locale option to TimezoneType
This commit is contained in:
commit
519ba3cddb
|
@ -18,7 +18,6 @@
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"ext-xml": "*",
|
"ext-xml": "*",
|
||||||
"doctrine/collections": "~1.0",
|
|
||||||
"doctrine/event-manager": "~1.0",
|
"doctrine/event-manager": "~1.0",
|
||||||
"doctrine/persistence": "~1.0",
|
"doctrine/persistence": "~1.0",
|
||||||
"fig/link-util": "^1.0",
|
"fig/link-util": "^1.0",
|
||||||
|
@ -27,7 +26,6 @@
|
||||||
"psr/container": "^1.0",
|
"psr/container": "^1.0",
|
||||||
"psr/link": "^1.0",
|
"psr/link": "^1.0",
|
||||||
"psr/log": "~1.0",
|
"psr/log": "~1.0",
|
||||||
"psr/simple-cache": "^1.0",
|
|
||||||
"symfony/contracts": "^1.1",
|
"symfony/contracts": "^1.1",
|
||||||
"symfony/polyfill-ctype": "~1.8",
|
"symfony/polyfill-ctype": "~1.8",
|
||||||
"symfony/polyfill-intl-icu": "~1.0",
|
"symfony/polyfill-intl-icu": "~1.0",
|
||||||
|
@ -38,6 +36,7 @@
|
||||||
},
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
"symfony/asset": "self.version",
|
"symfony/asset": "self.version",
|
||||||
|
"symfony/amazon-mailer": "self.version",
|
||||||
"symfony/browser-kit": "self.version",
|
"symfony/browser-kit": "self.version",
|
||||||
"symfony/cache": "self.version",
|
"symfony/cache": "self.version",
|
||||||
"symfony/config": "self.version",
|
"symfony/config": "self.version",
|
||||||
|
@ -55,6 +54,7 @@
|
||||||
"symfony/finder": "self.version",
|
"symfony/finder": "self.version",
|
||||||
"symfony/form": "self.version",
|
"symfony/form": "self.version",
|
||||||
"symfony/framework-bundle": "self.version",
|
"symfony/framework-bundle": "self.version",
|
||||||
|
"symfony/google-mailer": "self.version",
|
||||||
"symfony/http-client": "self.version",
|
"symfony/http-client": "self.version",
|
||||||
"symfony/http-foundation": "self.version",
|
"symfony/http-foundation": "self.version",
|
||||||
"symfony/http-kernel": "self.version",
|
"symfony/http-kernel": "self.version",
|
||||||
|
@ -62,10 +62,14 @@
|
||||||
"symfony/intl": "self.version",
|
"symfony/intl": "self.version",
|
||||||
"symfony/ldap": "self.version",
|
"symfony/ldap": "self.version",
|
||||||
"symfony/lock": "self.version",
|
"symfony/lock": "self.version",
|
||||||
|
"symfony/mailchimp-mailer": "self.version",
|
||||||
|
"symfony/mailer": "self.version",
|
||||||
|
"symfony/mailgun-mailer": "self.version",
|
||||||
"symfony/messenger": "self.version",
|
"symfony/messenger": "self.version",
|
||||||
"symfony/mime": "self.version",
|
"symfony/mime": "self.version",
|
||||||
"symfony/monolog-bridge": "self.version",
|
"symfony/monolog-bridge": "self.version",
|
||||||
"symfony/options-resolver": "self.version",
|
"symfony/options-resolver": "self.version",
|
||||||
|
"symfony/postmark-mailer": "self.version",
|
||||||
"symfony/process": "self.version",
|
"symfony/process": "self.version",
|
||||||
"symfony/property-access": "self.version",
|
"symfony/property-access": "self.version",
|
||||||
"symfony/property-info": "self.version",
|
"symfony/property-info": "self.version",
|
||||||
|
@ -77,6 +81,7 @@
|
||||||
"symfony/security-guard": "self.version",
|
"symfony/security-guard": "self.version",
|
||||||
"symfony/security-http": "self.version",
|
"symfony/security-http": "self.version",
|
||||||
"symfony/security-bundle": "self.version",
|
"symfony/security-bundle": "self.version",
|
||||||
|
"symfony/sendgrid-mailer": "self.version",
|
||||||
"symfony/serializer": "self.version",
|
"symfony/serializer": "self.version",
|
||||||
"symfony/stopwatch": "self.version",
|
"symfony/stopwatch": "self.version",
|
||||||
"symfony/templating": "self.version",
|
"symfony/templating": "self.version",
|
||||||
|
@ -96,6 +101,7 @@
|
||||||
"cache/integration-tests": "dev-master",
|
"cache/integration-tests": "dev-master",
|
||||||
"doctrine/annotations": "~1.0",
|
"doctrine/annotations": "~1.0",
|
||||||
"doctrine/cache": "~1.6",
|
"doctrine/cache": "~1.6",
|
||||||
|
"doctrine/collections": "~1.0",
|
||||||
"doctrine/data-fixtures": "1.0.*",
|
"doctrine/data-fixtures": "1.0.*",
|
||||||
"doctrine/dbal": "~2.4",
|
"doctrine/dbal": "~2.4",
|
||||||
"doctrine/orm": "~2.4,>=2.4.5",
|
"doctrine/orm": "~2.4,>=2.4.5",
|
||||||
|
@ -107,6 +113,7 @@
|
||||||
"ocramius/proxy-manager": "~0.4|~1.0|~2.0",
|
"ocramius/proxy-manager": "~0.4|~1.0|~2.0",
|
||||||
"predis/predis": "~1.1",
|
"predis/predis": "~1.1",
|
||||||
"psr/http-client": "^1.0",
|
"psr/http-client": "^1.0",
|
||||||
|
"psr/simple-cache": "^1.0",
|
||||||
"egulias/email-validator": "~1.2,>=1.2.8|~2.0",
|
"egulias/email-validator": "~1.2,>=1.2.8|~2.0",
|
||||||
"symfony/phpunit-bridge": "~3.4|~4.0",
|
"symfony/phpunit-bridge": "~3.4|~4.0",
|
||||||
"symfony/security-acl": "~2.8|~3.0",
|
"symfony/security-acl": "~2.8|~3.0",
|
||||||
|
|
|
@ -15,8 +15,10 @@ use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory;
|
||||||
use Doctrine\Common\Persistence\Mapping\MappingException;
|
use Doctrine\Common\Persistence\Mapping\MappingException;
|
||||||
use Doctrine\DBAL\Types\Type as DBALType;
|
use Doctrine\DBAL\Types\Type as DBALType;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||||
use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
|
use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
|
||||||
|
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\Type;
|
use Symfony\Component\PropertyInfo\Type;
|
||||||
|
@ -26,7 +28,7 @@ use Symfony\Component\PropertyInfo\Type;
|
||||||
*
|
*
|
||||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
*/
|
*/
|
||||||
class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface
|
class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
|
||||||
{
|
{
|
||||||
private $entityManager;
|
private $entityManager;
|
||||||
private $classMetadataFactory;
|
private $classMetadataFactory;
|
||||||
|
@ -51,12 +53,8 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE
|
||||||
*/
|
*/
|
||||||
public function getProperties($class, array $context = [])
|
public function getProperties($class, array $context = [])
|
||||||
{
|
{
|
||||||
try {
|
if (null === $metadata = $this->getMetadata($class)) {
|
||||||
$metadata = $this->entityManager ? $this->entityManager->getClassMetadata($class) : $this->classMetadataFactory->getMetadataFor($class);
|
return null;
|
||||||
} catch (MappingException $exception) {
|
|
||||||
return;
|
|
||||||
} catch (OrmMappingException $exception) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
|
$properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
|
||||||
|
@ -77,12 +75,8 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE
|
||||||
*/
|
*/
|
||||||
public function getTypes($class, $property, array $context = [])
|
public function getTypes($class, $property, array $context = [])
|
||||||
{
|
{
|
||||||
try {
|
if (null === $metadata = $this->getMetadata($class)) {
|
||||||
$metadata = $this->entityManager ? $this->entityManager->getClassMetadata($class) : $this->classMetadataFactory->getMetadataFor($class);
|
return null;
|
||||||
} catch (MappingException $exception) {
|
|
||||||
return;
|
|
||||||
} catch (OrmMappingException $exception) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($metadata->hasAssociation($property)) {
|
if ($metadata->hasAssociation($property)) {
|
||||||
|
@ -176,6 +170,39 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isReadable($class, $property, array $context = [])
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isWritable($class, $property, array $context = [])
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
null === ($metadata = $this->getMetadata($class))
|
||||||
|
|| ClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType
|
||||||
|
|| !\in_array($property, $metadata->getIdentifierFieldNames(), true)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMetadata(string $class): ?ClassMetadata
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $this->entityManager ? $this->entityManager->getClassMetadata($class) : $this->classMetadataFactory->getMetadataFor($class);
|
||||||
|
} catch (MappingException | OrmMappingException $exception) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether an association is nullable.
|
* Determines whether an association is nullable.
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,6 +16,7 @@ use Doctrine\ORM\EntityManager;
|
||||||
use Doctrine\ORM\Tools\Setup;
|
use Doctrine\ORM\Tools\Setup;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
|
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
|
||||||
|
use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineGeneratedValue;
|
||||||
use Symfony\Component\PropertyInfo\Type;
|
use Symfony\Component\PropertyInfo\Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -223,4 +224,13 @@ class DoctrineExtractorTest extends TestCase
|
||||||
{
|
{
|
||||||
$this->assertNull($this->createExtractor($legacy)->getTypes('Not\Exist', 'baz'));
|
$this->assertNull($this->createExtractor($legacy)->getTypes('Not\Exist', 'baz'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGeneratedValueNotWritable()
|
||||||
|
{
|
||||||
|
$extractor = $this->createExtractor();
|
||||||
|
$this->assertFalse($extractor->isWritable(DoctrineGeneratedValue::class, 'id'));
|
||||||
|
$this->assertNull($extractor->isReadable(DoctrineGeneratedValue::class, 'id'));
|
||||||
|
$this->assertNull($extractor->isWritable(DoctrineGeneratedValue::class, 'foo'));
|
||||||
|
$this->assertNull($extractor->isReadable(DoctrineGeneratedValue::class, 'foo'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?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\Bridge\Doctrine\Tests\PropertyInfo\Fixtures;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping\Column;
|
||||||
|
use Doctrine\ORM\Mapping\Entity;
|
||||||
|
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||||
|
use Doctrine\ORM\Mapping\Id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class DoctrineGeneratedValue
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Id
|
||||||
|
* @GeneratedValue(strategy="AUTO")
|
||||||
|
* @Column(type="integer")
|
||||||
|
*/
|
||||||
|
public $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Column
|
||||||
|
*/
|
||||||
|
public $foo;
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ CHANGELOG
|
||||||
`framework.messenger.default_serializer`, which holds the string service
|
`framework.messenger.default_serializer`, which holds the string service
|
||||||
id and `framework.messenger.symfony_serializer`, which configures the
|
id and `framework.messenger.symfony_serializer`, which configures the
|
||||||
options if you're using Symfony's serializer.
|
options if you're using Symfony's serializer.
|
||||||
|
* [BC Break] Removed the `framework.messenger.routing.send_and_handle` configuration.
|
||||||
|
Instead of setting it to true, configure a `SyncTransport` and route messages to it.
|
||||||
* Added information about deprecated aliases in `debug:autowiring`
|
* Added information about deprecated aliases in `debug:autowiring`
|
||||||
* Added php ini session options `sid_length` and `sid_bits_per_character`
|
* Added php ini session options `sid_length` and `sid_bits_per_character`
|
||||||
to the `session` section of the configuration
|
to the `session` section of the configuration
|
||||||
|
|
|
@ -224,15 +224,12 @@ EOF
|
||||||
if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
|
if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
|
||||||
$buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel));
|
$buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel));
|
||||||
$container = $buildContainer();
|
$container = $buildContainer();
|
||||||
|
$container->getCompilerPassConfig()->setRemovingPasses([]);
|
||||||
|
$container->compile();
|
||||||
} else {
|
} else {
|
||||||
(new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
|
(new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
|
||||||
$container->setParameter('container.build_hash', $hash = ContainerBuilder::hash(__METHOD__));
|
|
||||||
$container->setParameter('container.build_id', hash('crc32', $hash.time()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$container->getCompilerPassConfig()->setRemovingPasses([]);
|
|
||||||
$container->compile();
|
|
||||||
|
|
||||||
return $this->containerBuilder = $container;
|
return $this->containerBuilder = $container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -322,21 +322,41 @@ abstract class Descriptor implements DescriptorInterface
|
||||||
|
|
||||||
private function getContainerEnvVars(ContainerBuilder $container): array
|
private function getContainerEnvVars(ContainerBuilder $container): array
|
||||||
{
|
{
|
||||||
|
if (!$container->hasParameter('debug.container.dump')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_file($container->getParameter('debug.container.dump'))) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = file_get_contents($container->getParameter('debug.container.dump'));
|
||||||
|
preg_match_all('{%env\(((?:\w++:)*+\w++)\)%}', $file, $envVars);
|
||||||
|
$envVars = array_unique($envVars[1]);
|
||||||
|
|
||||||
|
$bag = $container->getParameterBag();
|
||||||
|
$getDefaultParameter = function (string $name) {
|
||||||
|
return parent::get($name);
|
||||||
|
};
|
||||||
|
$getDefaultParameter = $getDefaultParameter->bindTo($bag, \get_class($bag));
|
||||||
|
|
||||||
$getEnvReflection = new \ReflectionMethod($container, 'getEnv');
|
$getEnvReflection = new \ReflectionMethod($container, 'getEnv');
|
||||||
$getEnvReflection->setAccessible(true);
|
$getEnvReflection->setAccessible(true);
|
||||||
|
|
||||||
$envs = [];
|
$envs = [];
|
||||||
foreach (array_keys($container->getEnvCounters()) as $env) {
|
|
||||||
|
foreach ($envVars as $env) {
|
||||||
$processor = 'string';
|
$processor = 'string';
|
||||||
if (false !== $i = strrpos($name = $env, ':')) {
|
if (false !== $i = strrpos($name = $env, ':')) {
|
||||||
$name = substr($env, $i + 1);
|
$name = substr($env, $i + 1);
|
||||||
$processor = substr($env, 0, $i);
|
$processor = substr($env, 0, $i);
|
||||||
}
|
}
|
||||||
$defaultValue = ($hasDefault = $container->hasParameter("env($name)")) ? $container->getParameter("env($name)") : null;
|
$defaultValue = ($hasDefault = $container->hasParameter("env($name)")) ? $getDefaultParameter("env($name)") : null;
|
||||||
if (false === ($runtimeValue = $_ENV[$name] ?? $_SERVER[$name] ?? getenv($name))) {
|
if (false === ($runtimeValue = $_ENV[$name] ?? $_SERVER[$name] ?? getenv($name))) {
|
||||||
$runtimeValue = null;
|
$runtimeValue = null;
|
||||||
}
|
}
|
||||||
$processedValue = ($hasRuntime = null !== $runtimeValue) || $hasDefault ? $getEnvReflection->invoke($container, $env) : null;
|
$processedValue = ($hasRuntime = null !== $runtimeValue) || $hasDefault ? $getEnvReflection->invoke($container, $env) : null;
|
||||||
$envs[$name.$processor] = [
|
$envs["$name$processor"] = [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'processor' => $processor,
|
'processor' => $processor,
|
||||||
'default_available' => $hasDefault,
|
'default_available' => $hasDefault,
|
||||||
|
|
|
@ -1125,7 +1125,6 @@ class Configuration implements ConfigurationInterface
|
||||||
if (!\is_int($k)) {
|
if (!\is_int($k)) {
|
||||||
$newConfig[$k] = [
|
$newConfig[$k] = [
|
||||||
'senders' => $v['senders'] ?? (\is_array($v) ? array_values($v) : [$v]),
|
'senders' => $v['senders'] ?? (\is_array($v) ? array_values($v) : [$v]),
|
||||||
'send_and_handle' => $v['send_and_handle'] ?? false,
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$newConfig[$v['message-class']]['senders'] = array_map(
|
$newConfig[$v['message-class']]['senders'] = array_map(
|
||||||
|
@ -1134,7 +1133,6 @@ class Configuration implements ConfigurationInterface
|
||||||
},
|
},
|
||||||
array_values($v['sender'])
|
array_values($v['sender'])
|
||||||
);
|
);
|
||||||
$newConfig[$v['message-class']]['send-and-handle'] = $v['send-and-handle'] ?? false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1147,7 +1145,6 @@ class Configuration implements ConfigurationInterface
|
||||||
->requiresAtLeastOneElement()
|
->requiresAtLeastOneElement()
|
||||||
->prototype('scalar')->end()
|
->prototype('scalar')->end()
|
||||||
->end()
|
->end()
|
||||||
->booleanNode('send_and_handle')->defaultFalse()->end()
|
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
|
|
|
@ -99,7 +99,6 @@ use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
|
||||||
use Symfony\Component\Serializer\Encoder\DecoderInterface;
|
use Symfony\Component\Serializer\Encoder\DecoderInterface;
|
||||||
use Symfony\Component\Serializer\Encoder\EncoderInterface;
|
use Symfony\Component\Serializer\Encoder\EncoderInterface;
|
||||||
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
|
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
|
||||||
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer;
|
use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer;
|
||||||
use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
|
use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
|
||||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||||
|
@ -1505,18 +1504,8 @@ class FrameworkExtension extends Extension
|
||||||
$chainLoader->replaceArgument(0, $serializerLoaders);
|
$chainLoader->replaceArgument(0, $serializerLoaders);
|
||||||
$container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders);
|
$container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders);
|
||||||
|
|
||||||
if (!$container->getParameter('kernel.debug')) {
|
if ($container->getParameter('kernel.debug')) {
|
||||||
$cacheMetadataFactory = new Definition(
|
$container->removeDefinition('serializer.mapping.cache_class_metadata_factory');
|
||||||
CacheClassMetadataFactory::class,
|
|
||||||
[
|
|
||||||
new Reference('serializer.mapping.cache_class_metadata_factory.inner'),
|
|
||||||
new Reference('serializer.mapping.cache.symfony'),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
$cacheMetadataFactory->setPublic(false);
|
|
||||||
$cacheMetadataFactory->setDecoratedService('serializer.mapping.class_metadata_factory');
|
|
||||||
|
|
||||||
$container->setDefinition('serializer.mapping.cache_class_metadata_factory', $cacheMetadataFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($config['name_converter']) && $config['name_converter']) {
|
if (isset($config['name_converter']) && $config['name_converter']) {
|
||||||
|
@ -1551,6 +1540,10 @@ class FrameworkExtension extends Extension
|
||||||
$definition->addTag('property_info.description_extractor', ['priority' => -1000]);
|
$definition->addTag('property_info.description_extractor', ['priority' => -1000]);
|
||||||
$definition->addTag('property_info.type_extractor', ['priority' => -1001]);
|
$definition->addTag('property_info.type_extractor', ['priority' => -1001]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($container->getParameter('kernel.debug')) {
|
||||||
|
$container->removeDefinition('property_info.cache');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function registerLockConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
|
private function registerLockConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
|
||||||
|
@ -1659,6 +1652,7 @@ class FrameworkExtension extends Extension
|
||||||
'before' => [
|
'before' => [
|
||||||
['id' => 'add_bus_name_stamp_middleware'],
|
['id' => 'add_bus_name_stamp_middleware'],
|
||||||
['id' => 'dispatch_after_current_bus'],
|
['id' => 'dispatch_after_current_bus'],
|
||||||
|
['id' => 'failed_message_processing_middleware'],
|
||||||
],
|
],
|
||||||
'after' => [
|
'after' => [
|
||||||
['id' => 'send_message'],
|
['id' => 'send_message'],
|
||||||
|
@ -1744,7 +1738,6 @@ class FrameworkExtension extends Extension
|
||||||
}
|
}
|
||||||
|
|
||||||
$messageToSendersMapping = [];
|
$messageToSendersMapping = [];
|
||||||
$messagesToSendAndHandle = [];
|
|
||||||
foreach ($config['routing'] as $message => $messageConfiguration) {
|
foreach ($config['routing'] as $message => $messageConfiguration) {
|
||||||
if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) {
|
if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) {
|
||||||
throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message));
|
throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message));
|
||||||
|
@ -1758,7 +1751,6 @@ class FrameworkExtension extends Extension
|
||||||
}
|
}
|
||||||
|
|
||||||
$messageToSendersMapping[$message] = $messageConfiguration['senders'];
|
$messageToSendersMapping[$message] = $messageConfiguration['senders'];
|
||||||
$messagesToSendAndHandle[$message] = $messageConfiguration['send_and_handle'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$senderReferences = [];
|
$senderReferences = [];
|
||||||
|
@ -1769,7 +1761,6 @@ class FrameworkExtension extends Extension
|
||||||
$container->getDefinition('messenger.senders_locator')
|
$container->getDefinition('messenger.senders_locator')
|
||||||
->replaceArgument(0, $messageToSendersMapping)
|
->replaceArgument(0, $messageToSendersMapping)
|
||||||
->replaceArgument(1, ServiceLocatorTagPass::register($container, $senderReferences))
|
->replaceArgument(1, ServiceLocatorTagPass::register($container, $senderReferences))
|
||||||
->replaceArgument(2, $messagesToSendAndHandle)
|
|
||||||
;
|
;
|
||||||
|
|
||||||
$container->getDefinition('messenger.retry_strategy_locator')
|
$container->getDefinition('messenger.retry_strategy_locator')
|
||||||
|
|
|
@ -36,6 +36,10 @@
|
||||||
<tag name="cache.pool" />
|
<tag name="cache.pool" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="cache.property_info" parent="cache.system" public="false">
|
||||||
|
<tag name="cache.pool" />
|
||||||
|
</service>
|
||||||
|
|
||||||
<service id="cache.messenger.restart_workers_signal" parent="cache.app" public="false">
|
<service id="cache.messenger.restart_workers_signal" parent="cache.app" public="false">
|
||||||
<tag name="cache.pool" />
|
<tag name="cache.pool" />
|
||||||
</service>
|
</service>
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
<service id="messenger.senders_locator" class="Symfony\Component\Messenger\Transport\Sender\SendersLocator">
|
<service id="messenger.senders_locator" class="Symfony\Component\Messenger\Transport\Sender\SendersLocator">
|
||||||
<argument type="collection" /> <!-- Per message senders map -->
|
<argument type="collection" /> <!-- Per message senders map -->
|
||||||
<argument /> <!-- senders locator -->
|
<argument /> <!-- senders locator -->
|
||||||
<argument type="collection" /> <!-- Messages to send and handle -->
|
|
||||||
</service>
|
</service>
|
||||||
<service id="messenger.middleware.send_message" class="Symfony\Component\Messenger\Middleware\SendMessageMiddleware">
|
<service id="messenger.middleware.send_message" class="Symfony\Component\Messenger\Middleware\SendMessageMiddleware">
|
||||||
<tag name="monolog.logger" channel="messenger" />
|
<tag name="monolog.logger" channel="messenger" />
|
||||||
|
@ -48,6 +47,8 @@
|
||||||
<argument type="service" id="validator" />
|
<argument type="service" id="validator" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="messenger.middleware.failed_message_processing_middleware" class="Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware" />
|
||||||
|
|
||||||
<service id="messenger.middleware.traceable" class="Symfony\Component\Messenger\Middleware\TraceableMiddleware" abstract="true">
|
<service id="messenger.middleware.traceable" class="Symfony\Component\Messenger\Middleware\TraceableMiddleware" abstract="true">
|
||||||
<argument type="service" id="debug.stopwatch" />
|
<argument type="service" id="debug.stopwatch" />
|
||||||
</service>
|
</service>
|
||||||
|
@ -106,6 +107,7 @@
|
||||||
<!-- routable message bus -->
|
<!-- routable message bus -->
|
||||||
<service id="messenger.routable_message_bus" class="Symfony\Component\Messenger\RoutableMessageBus">
|
<service id="messenger.routable_message_bus" class="Symfony\Component\Messenger\RoutableMessageBus">
|
||||||
<argument /> <!-- Message bus locator -->
|
<argument /> <!-- Message bus locator -->
|
||||||
|
<argument type="service" id="messenger.default_bus" />
|
||||||
</service>
|
</service>
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
|
|
@ -21,6 +21,11 @@
|
||||||
<service id="Symfony\Component\PropertyInfo\PropertyListExtractorInterface" alias="property_info" />
|
<service id="Symfony\Component\PropertyInfo\PropertyListExtractorInterface" alias="property_info" />
|
||||||
<service id="Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface" alias="property_info" />
|
<service id="Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface" alias="property_info" />
|
||||||
|
|
||||||
|
<service id="property_info.cache" decorates="property_info" class="Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor">
|
||||||
|
<argument type="service" id="property_info.cache.inner" />
|
||||||
|
<argument type="service" id="cache.property_info" />
|
||||||
|
</service>
|
||||||
|
|
||||||
<!-- Extractor -->
|
<!-- Extractor -->
|
||||||
<service id="property_info.reflection_extractor" class="Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor">
|
<service id="property_info.reflection_extractor" class="Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor">
|
||||||
<tag name="property_info.list_extractor" priority="-1000" />
|
<tag name="property_info.list_extractor" priority="-1000" />
|
||||||
|
|
|
@ -434,7 +434,6 @@
|
||||||
<xsd:element name="sender" type="messenger_routing_sender" />
|
<xsd:element name="sender" type="messenger_routing_sender" />
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
<xsd:attribute name="message-class" type="xsd:string" use="required"/>
|
<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>
|
||||||
|
|
||||||
<xsd:complexType name="messenger_routing_sender">
|
<xsd:complexType name="messenger_routing_sender">
|
||||||
|
|
|
@ -104,6 +104,11 @@
|
||||||
<argument type="service" id="cache.serializer" />
|
<argument type="service" id="cache.serializer" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="serializer.mapping.cache_class_metadata_factory" decorates="serializer.mapping.class_metadata_factory" class="Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory">
|
||||||
|
<argument type="service" id="serializer.mapping.cache_class_metadata_factory.inner" />
|
||||||
|
<argument type="service" id="serializer.mapping.cache.symfony" />
|
||||||
|
</service>
|
||||||
|
|
||||||
<!-- Encoders -->
|
<!-- Encoders -->
|
||||||
<service id="serializer.encoder.xml" class="Symfony\Component\Serializer\Encoder\XmlEncoder">
|
<service id="serializer.encoder.xml" class="Symfony\Component\Serializer\Encoder\XmlEncoder">
|
||||||
<tag name="serializer.encoder" />
|
<tag name="serializer.encoder" />
|
||||||
|
|
|
@ -71,6 +71,7 @@
|
||||||
<service id="validator.property_info_loader" class="Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader">
|
<service id="validator.property_info_loader" class="Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader">
|
||||||
<argument type="service" id="property_info" />
|
<argument type="service" id="property_info" />
|
||||||
<argument type="service" id="property_info" />
|
<argument type="service" id="property_info" />
|
||||||
|
<argument type="service" id="property_info" />
|
||||||
|
|
||||||
<tag name="validator.auto_mapper" />
|
<tag name="validator.auto_mapper" />
|
||||||
</service>
|
</service>
|
||||||
|
|
|
@ -10,7 +10,6 @@ $container->loadFromExtension('framework', [
|
||||||
'Symfony\Component\Messenger\Tests\Fixtures\DummyMessage' => ['amqp', 'audit'],
|
'Symfony\Component\Messenger\Tests\Fixtures\DummyMessage' => ['amqp', 'audit'],
|
||||||
'Symfony\Component\Messenger\Tests\Fixtures\SecondMessage' => [
|
'Symfony\Component\Messenger\Tests\Fixtures\SecondMessage' => [
|
||||||
'senders' => ['amqp', 'audit'],
|
'senders' => ['amqp', 'audit'],
|
||||||
'send_and_handle' => true,
|
|
||||||
],
|
],
|
||||||
'*' => 'amqp',
|
'*' => 'amqp',
|
||||||
],
|
],
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<framework:sender service="amqp" />
|
<framework:sender service="amqp" />
|
||||||
<framework:sender service="audit" />
|
<framework:sender service="audit" />
|
||||||
</framework:routing>
|
</framework:routing>
|
||||||
<framework:routing message-class="Symfony\Component\Messenger\Tests\Fixtures\SecondMessage" send-and-handle="true">
|
<framework:routing message-class="Symfony\Component\Messenger\Tests\Fixtures\SecondMessage">
|
||||||
<framework:sender service="amqp" />
|
<framework:sender service="amqp" />
|
||||||
<framework:sender service="audit" />
|
<framework:sender service="audit" />
|
||||||
</framework:routing>
|
</framework:routing>
|
||||||
|
|
|
@ -7,7 +7,6 @@ framework:
|
||||||
'Symfony\Component\Messenger\Tests\Fixtures\DummyMessage': [amqp, audit]
|
'Symfony\Component\Messenger\Tests\Fixtures\DummyMessage': [amqp, audit]
|
||||||
'Symfony\Component\Messenger\Tests\Fixtures\SecondMessage':
|
'Symfony\Component\Messenger\Tests\Fixtures\SecondMessage':
|
||||||
senders: [amqp, audit]
|
senders: [amqp, audit]
|
||||||
send_and_handle: true
|
|
||||||
'*': amqp
|
'*': amqp
|
||||||
transports:
|
transports:
|
||||||
amqp: 'amqp://localhost/%2f/messages'
|
amqp: 'amqp://localhost/%2f/messages'
|
||||||
|
|
|
@ -40,7 +40,6 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\HttpClient\ScopingHttpClient;
|
use Symfony\Component\HttpClient\ScopingHttpClient;
|
||||||
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
|
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
|
||||||
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
|
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
|
||||||
use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage;
|
|
||||||
use Symfony\Component\Messenger\Transport\TransportFactory;
|
use Symfony\Component\Messenger\Transport\TransportFactory;
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||||
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
|
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
|
||||||
|
@ -715,13 +714,6 @@ abstract class FrameworkExtensionTest extends TestCase
|
||||||
$container = $this->createContainerFromFile('messenger_routing');
|
$container = $this->createContainerFromFile('messenger_routing');
|
||||||
$senderLocatorDefinition = $container->getDefinition('messenger.senders_locator');
|
$senderLocatorDefinition = $container->getDefinition('messenger.senders_locator');
|
||||||
|
|
||||||
$messageToSendAndHandleMapping = [
|
|
||||||
DummyMessage::class => false,
|
|
||||||
SecondMessage::class => true,
|
|
||||||
'*' => false,
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertSame($messageToSendAndHandleMapping, $senderLocatorDefinition->getArgument(2));
|
|
||||||
$sendersMapping = $senderLocatorDefinition->getArgument(0);
|
$sendersMapping = $senderLocatorDefinition->getArgument(0);
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
'amqp',
|
'amqp',
|
||||||
|
@ -753,6 +745,7 @@ abstract class FrameworkExtensionTest extends TestCase
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
['id' => 'add_bus_name_stamp_middleware', 'arguments' => ['messenger.bus.commands']],
|
['id' => 'add_bus_name_stamp_middleware', 'arguments' => ['messenger.bus.commands']],
|
||||||
['id' => 'dispatch_after_current_bus'],
|
['id' => 'dispatch_after_current_bus'],
|
||||||
|
['id' => 'failed_message_processing_middleware'],
|
||||||
['id' => 'send_message'],
|
['id' => 'send_message'],
|
||||||
['id' => 'handle_message'],
|
['id' => 'handle_message'],
|
||||||
], $container->getParameter('messenger.bus.commands.middleware'));
|
], $container->getParameter('messenger.bus.commands.middleware'));
|
||||||
|
@ -761,6 +754,7 @@ abstract class FrameworkExtensionTest extends TestCase
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
['id' => 'add_bus_name_stamp_middleware', 'arguments' => ['messenger.bus.events']],
|
['id' => 'add_bus_name_stamp_middleware', 'arguments' => ['messenger.bus.events']],
|
||||||
['id' => 'dispatch_after_current_bus'],
|
['id' => 'dispatch_after_current_bus'],
|
||||||
|
['id' => 'failed_message_processing_middleware'],
|
||||||
['id' => 'with_factory', 'arguments' => ['foo', true, ['bar' => 'baz']]],
|
['id' => 'with_factory', 'arguments' => ['foo', true, ['bar' => 'baz']]],
|
||||||
['id' => 'send_message'],
|
['id' => 'send_message'],
|
||||||
['id' => 'handle_message'],
|
['id' => 'handle_message'],
|
||||||
|
@ -1363,6 +1357,22 @@ abstract class FrameworkExtensionTest extends TestCase
|
||||||
$this->assertTrue($container->has('property_info'));
|
$this->assertTrue($container->has('property_info'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testPropertyInfoCacheActivated()
|
||||||
|
{
|
||||||
|
$container = $this->createContainerFromFile('property_info');
|
||||||
|
|
||||||
|
$this->assertTrue($container->hasDefinition('property_info.cache'));
|
||||||
|
|
||||||
|
$cache = $container->getDefinition('property_info.cache')->getArgument(1);
|
||||||
|
$this->assertEquals(new Reference('cache.property_info'), $cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPropertyInfoCacheDisabled()
|
||||||
|
{
|
||||||
|
$container = $this->createContainerFromFile('property_info', ['kernel.debug' => true, 'kernel.container_class' => __CLASS__]);
|
||||||
|
$this->assertFalse($container->hasDefinition('property_info.cache'));
|
||||||
|
}
|
||||||
|
|
||||||
public function testEventDispatcherService()
|
public function testEventDispatcherService()
|
||||||
{
|
{
|
||||||
$container = $this->createContainer(['kernel.charset' => 'UTF-8', 'kernel.secret' => 'secret']);
|
$container = $this->createContainer(['kernel.charset' => 'UTF-8', 'kernel.secret' => 'secret']);
|
||||||
|
|
|
@ -83,11 +83,13 @@ class ContainerDebugCommandTest extends WebTestCase
|
||||||
public function testDescribeEnvVars()
|
public function testDescribeEnvVars()
|
||||||
{
|
{
|
||||||
putenv('REAL=value');
|
putenv('REAL=value');
|
||||||
static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml']);
|
static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml', 'debug' => true]);
|
||||||
|
|
||||||
$application = new Application(static::$kernel);
|
$application = new Application(static::$kernel);
|
||||||
$application->setAutoExit(false);
|
$application->setAutoExit(false);
|
||||||
|
|
||||||
|
@unlink(static::$container->getParameter('debug.container.dump'));
|
||||||
|
|
||||||
$tester = new ApplicationTester($application);
|
$tester = new ApplicationTester($application);
|
||||||
$tester->run(['command' => 'debug:container', '--env-vars' => true], ['decorated' => false]);
|
$tester->run(['command' => 'debug:container', '--env-vars' => true], ['decorated' => false]);
|
||||||
|
|
||||||
|
@ -96,13 +98,13 @@ class ContainerDebugCommandTest extends WebTestCase
|
||||||
Symfony Container Environment Variables
|
Symfony Container Environment Variables
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
--------- ----------------- ------------
|
--------- ----------------- ------------%w
|
||||||
Name Default value Real value
|
Name Default value Real value%w
|
||||||
--------- ----------------- ------------
|
--------- ----------------- ------------%w
|
||||||
JSON "[1, "2.5", 3]" n/a
|
JSON "[1, "2.5", 3]" n/a%w
|
||||||
REAL n/a "value"
|
REAL n/a "value"%w
|
||||||
UNKNOWN n/a n/a
|
UNKNOWN n/a n/a%w
|
||||||
--------- ----------------- ------------
|
--------- ----------------- ------------%w
|
||||||
|
|
||||||
// Note real values might be different between web and CLI.%w
|
// Note real values might be different between web and CLI.%w
|
||||||
|
|
||||||
|
@ -118,35 +120,17 @@ TXT
|
||||||
|
|
||||||
public function testDescribeEnvVar()
|
public function testDescribeEnvVar()
|
||||||
{
|
{
|
||||||
static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml']);
|
static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml', 'debug' => true]);
|
||||||
|
|
||||||
$application = new Application(static::$kernel);
|
$application = new Application(static::$kernel);
|
||||||
$application->setAutoExit(false);
|
$application->setAutoExit(false);
|
||||||
|
|
||||||
|
@unlink(static::$container->getParameter('debug.container.dump'));
|
||||||
|
|
||||||
$tester = new ApplicationTester($application);
|
$tester = new ApplicationTester($application);
|
||||||
$tester->run(['command' => 'debug:container', '--env-var' => 'js'], ['decorated' => false]);
|
$tester->run(['command' => 'debug:container', '--env-var' => 'js'], ['decorated' => false]);
|
||||||
|
|
||||||
$this->assertContains(<<<'TXT'
|
$this->assertContains(file_get_contents(__DIR__.'/Fixtures/describe_env_vars.txt'), $tester->getDisplay(true));
|
||||||
%env(float:key:2:json:JSON)%
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
----------------- -----------------
|
|
||||||
Default value "[1, "2.5", 3]"
|
|
||||||
Real value n/a
|
|
||||||
Processed value 3.0
|
|
||||||
----------------- -----------------
|
|
||||||
|
|
||||||
%env(int:key:2:json:JSON)%
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
----------------- -----------------
|
|
||||||
Default value "[1, "2.5", 3]"
|
|
||||||
Real value n/a
|
|
||||||
Processed value 3
|
|
||||||
----------------- -----------------
|
|
||||||
|
|
||||||
TXT
|
|
||||||
, $tester->getDisplay(true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provideIgnoreBackslashWhenFindingService()
|
public function provideIgnoreBackslashWhenFindingService()
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
%env(float:key:2:json:JSON)%
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
----------------- -----------------
|
||||||
|
Default value "[1, "2.5", 3]"
|
||||||
|
Real value n/a
|
||||||
|
Processed value 3.0
|
||||||
|
----------------- -----------------
|
||||||
|
|
||||||
|
%env(int:key:2:json:JSON)%
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
----------------- -----------------
|
||||||
|
Default value "[1, "2.5", 3]"
|
||||||
|
Real value n/a
|
||||||
|
Processed value 3
|
||||||
|
----------------- -----------------
|
|
@ -6,8 +6,9 @@
|
||||||
|
|
||||||
<services>
|
<services>
|
||||||
<service id="twig.mailer.message_listener" class="Symfony\Component\Mailer\EventListener\MessageListener">
|
<service id="twig.mailer.message_listener" class="Symfony\Component\Mailer\EventListener\MessageListener">
|
||||||
<argument />
|
<argument>null</argument>
|
||||||
<argument type="service" id="twig.mime_body_renderer" />
|
<argument type="service" id="twig.mime_body_renderer" />
|
||||||
|
<tag name="kernel.event_subscriber"/>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="twig.mime_body_renderer" class="Symfony\Bridge\Twig\Mime\BodyRenderer">
|
<service id="twig.mime_body_renderer" class="Symfony\Bridge\Twig\Mime\BodyRenderer">
|
||||||
|
|
|
@ -164,7 +164,8 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
|
||||||
$ok = false;
|
$ok = false;
|
||||||
$v = $values[$id];
|
$v = $values[$id];
|
||||||
$type = \is_object($v) ? \get_class($v) : \gettype($v);
|
$type = \is_object($v) ? \get_class($v) : \gettype($v);
|
||||||
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
|
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
|
||||||
|
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($values as $id => $v) {
|
foreach ($values as $id => $v) {
|
||||||
|
@ -186,7 +187,8 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
|
||||||
}
|
}
|
||||||
$ok = false;
|
$ok = false;
|
||||||
$type = \is_object($v) ? \get_class($v) : \gettype($v);
|
$type = \is_object($v) ? \get_class($v) : \gettype($v);
|
||||||
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
|
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
|
||||||
|
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -178,7 +178,8 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA
|
||||||
$ok = false;
|
$ok = false;
|
||||||
$v = $values[$id];
|
$v = $values[$id];
|
||||||
$type = \is_object($v) ? \get_class($v) : \gettype($v);
|
$type = \is_object($v) ? \get_class($v) : \gettype($v);
|
||||||
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
|
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
|
||||||
|
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($values as $id => $v) {
|
foreach ($values as $id => $v) {
|
||||||
|
@ -201,7 +202,8 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA
|
||||||
}
|
}
|
||||||
$ok = false;
|
$ok = false;
|
||||||
$type = \is_object($v) ? \get_class($v) : \gettype($v);
|
$type = \is_object($v) ? \get_class($v) : \gettype($v);
|
||||||
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
|
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
|
||||||
|
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,8 @@ trait ArrayTrait
|
||||||
$serialized = serialize($value);
|
$serialized = serialize($value);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$type = \is_object($value) ? \get_class($value) : \gettype($value);
|
$type = \is_object($value) ? \get_class($value) : \gettype($value);
|
||||||
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => $key, 'type' => $type, 'exception' => $e]);
|
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
|
||||||
|
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,22 +21,26 @@ class TaggedIteratorArgument extends IteratorArgument
|
||||||
private $tag;
|
private $tag;
|
||||||
private $indexAttribute;
|
private $indexAttribute;
|
||||||
private $defaultIndexMethod;
|
private $defaultIndexMethod;
|
||||||
private $useFqcnAsFallback = false;
|
private $needsIndexes = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $tag The name of the tag identifying the target services
|
* @param string $tag The name of the tag identifying the target services
|
||||||
* @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection
|
* @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection
|
||||||
* @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute
|
* @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute
|
||||||
* @param bool $useFqcnAsFallback Whether the FQCN of the service should be used as index when neither the attribute nor the method are defined
|
* @param bool $needsIndexes Whether indexes are required and should be generated when computing the map
|
||||||
*/
|
*/
|
||||||
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $useFqcnAsFallback = false)
|
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false)
|
||||||
{
|
{
|
||||||
parent::__construct([]);
|
parent::__construct([]);
|
||||||
|
|
||||||
|
if (null === $indexAttribute && $needsIndexes) {
|
||||||
|
$indexAttribute = preg_match('/[^.]++$/', $tag, $m) ? $m[0] : $tag;
|
||||||
|
}
|
||||||
|
|
||||||
$this->tag = $tag;
|
$this->tag = $tag;
|
||||||
$this->indexAttribute = $indexAttribute;
|
$this->indexAttribute = $indexAttribute;
|
||||||
$this->defaultIndexMethod = $defaultIndexMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute ?? ''))).'Name');
|
$this->defaultIndexMethod = $defaultIndexMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute ?? ''))).'Name');
|
||||||
$this->useFqcnAsFallback = $useFqcnAsFallback;
|
$this->needsIndexes = $needsIndexes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTag()
|
public function getTag()
|
||||||
|
@ -54,8 +58,8 @@ class TaggedIteratorArgument extends IteratorArgument
|
||||||
return $this->defaultIndexMethod;
|
return $this->defaultIndexMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function useFqcnAsFallback(): bool
|
public function needsIndexes(): bool
|
||||||
{
|
{
|
||||||
return $this->useFqcnAsFallback;
|
return $this->needsIndexes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,12 +41,12 @@ trait PriorityTaggedServiceTrait
|
||||||
*/
|
*/
|
||||||
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
|
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
$indexAttribute = $defaultIndexMethod = $useFqcnAsFallback = null;
|
$indexAttribute = $defaultIndexMethod = $needsIndexes = null;
|
||||||
|
|
||||||
if ($tagName instanceof TaggedIteratorArgument) {
|
if ($tagName instanceof TaggedIteratorArgument) {
|
||||||
$indexAttribute = $tagName->getIndexAttribute();
|
$indexAttribute = $tagName->getIndexAttribute();
|
||||||
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
|
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
|
||||||
$useFqcnAsFallback = $tagName->useFqcnAsFallback();
|
$needsIndexes = $tagName->needsIndexes();
|
||||||
$tagName = $tagName->getTag();
|
$tagName = $tagName->getTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ trait PriorityTaggedServiceTrait
|
||||||
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
|
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
|
||||||
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
|
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
|
||||||
|
|
||||||
if (null === $indexAttribute && !$useFqcnAsFallback) {
|
if (null === $indexAttribute && !$needsIndexes) {
|
||||||
$services[$priority][] = new Reference($serviceId);
|
$services[$priority][] = new Reference($serviceId);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
@ -77,8 +77,8 @@ trait PriorityTaggedServiceTrait
|
||||||
$class = $r->name;
|
$class = $r->name;
|
||||||
|
|
||||||
if (!$r->hasMethod($defaultIndexMethod)) {
|
if (!$r->hasMethod($defaultIndexMethod)) {
|
||||||
if ($useFqcnAsFallback) {
|
if ($needsIndexes) {
|
||||||
$services[$priority][$class] = new TypedReference($serviceId, $class);
|
$services[$priority][$serviceId] = new TypedReference($serviceId, $class);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,22 +292,22 @@ class IntegrationTest extends TestCase
|
||||||
public function testTaggedServiceLocatorWithIndexAttribute()
|
public function testTaggedServiceLocatorWithIndexAttribute()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
$container->register(BarTagClass::class)
|
$container->register('bar_tag', BarTagClass::class)
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
->addTag('foo_bar', ['foo' => 'bar'])
|
->addTag('foo_bar', ['foo' => 'bar'])
|
||||||
;
|
;
|
||||||
$container->register(FooTagClass::class)
|
$container->register('foo_tag', FooTagClass::class)
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
->addTag('foo_bar')
|
->addTag('foo_bar')
|
||||||
;
|
;
|
||||||
$container->register(FooBarTaggedClass::class)
|
$container->register('foo_bar_tagged', FooBarTaggedClass::class)
|
||||||
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo')))
|
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo')))
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
;
|
;
|
||||||
|
|
||||||
$container->compile();
|
$container->compile();
|
||||||
|
|
||||||
$s = $container->get(FooBarTaggedClass::class);
|
$s = $container->get('foo_bar_tagged');
|
||||||
|
|
||||||
/** @var ServiceLocator $serviceLocator */
|
/** @var ServiceLocator $serviceLocator */
|
||||||
$serviceLocator = $s->getParam();
|
$serviceLocator = $s->getParam();
|
||||||
|
@ -317,28 +317,28 @@ class IntegrationTest extends TestCase
|
||||||
'bar' => $serviceLocator->get('bar'),
|
'bar' => $serviceLocator->get('bar'),
|
||||||
'foo_tag_class' => $serviceLocator->get('foo_tag_class'),
|
'foo_tag_class' => $serviceLocator->get('foo_tag_class'),
|
||||||
];
|
];
|
||||||
$this->assertSame(['bar' => $container->get(BarTagClass::class), 'foo_tag_class' => $container->get(FooTagClass::class)], $same);
|
$this->assertSame(['bar' => $container->get('bar_tag'), 'foo_tag_class' => $container->get('foo_tag')], $same);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod()
|
public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
$container->register(BarTagClass::class)
|
$container->register('bar_tag', BarTagClass::class)
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
->addTag('foo_bar')
|
->addTag('foo_bar')
|
||||||
;
|
;
|
||||||
$container->register(FooTagClass::class)
|
$container->register('foo_tag', FooTagClass::class)
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
->addTag('foo_bar', ['foo' => 'foo'])
|
->addTag('foo_bar', ['foo' => 'foo'])
|
||||||
;
|
;
|
||||||
$container->register(FooBarTaggedClass::class)
|
$container->register('foo_bar_tagged', FooBarTaggedClass::class)
|
||||||
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar')))
|
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar')))
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
;
|
;
|
||||||
|
|
||||||
$container->compile();
|
$container->compile();
|
||||||
|
|
||||||
$s = $container->get(FooBarTaggedClass::class);
|
$s = $container->get('foo_bar_tagged');
|
||||||
|
|
||||||
/** @var ServiceLocator $serviceLocator */
|
/** @var ServiceLocator $serviceLocator */
|
||||||
$serviceLocator = $s->getParam();
|
$serviceLocator = $s->getParam();
|
||||||
|
@ -348,33 +348,59 @@ class IntegrationTest extends TestCase
|
||||||
'bar_tab_class_with_defaultmethod' => $serviceLocator->get('bar_tab_class_with_defaultmethod'),
|
'bar_tab_class_with_defaultmethod' => $serviceLocator->get('bar_tab_class_with_defaultmethod'),
|
||||||
'foo' => $serviceLocator->get('foo'),
|
'foo' => $serviceLocator->get('foo'),
|
||||||
];
|
];
|
||||||
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $same);
|
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get('bar_tag'), 'foo' => $container->get('foo_tag')], $same);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTaggedServiceLocatorWithFqcnFallback()
|
public function testTaggedServiceLocatorWithFallback()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
$container->register(BarTagClass::class)
|
$container->register('bar_tag', BarTagClass::class)
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
->addTag('foo_bar')
|
->addTag('foo_bar')
|
||||||
;
|
;
|
||||||
$container->register(FooBarTaggedClass::class)
|
$container->register('foo_bar_tagged', FooBarTaggedClass::class)
|
||||||
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', null, null, true)))
|
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', null, null, true)))
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
;
|
;
|
||||||
|
|
||||||
$container->compile();
|
$container->compile();
|
||||||
|
|
||||||
$s = $container->get(FooBarTaggedClass::class);
|
$s = $container->get('foo_bar_tagged');
|
||||||
|
|
||||||
/** @var ServiceLocator $serviceLocator */
|
/** @var ServiceLocator $serviceLocator */
|
||||||
$serviceLocator = $s->getParam();
|
$serviceLocator = $s->getParam();
|
||||||
$this->assertTrue($s->getParam() instanceof ServiceLocator, sprintf('Wrong instance, should be an instance of ServiceLocator, %s given', \is_object($serviceLocator) ? \get_class($serviceLocator) : \gettype($serviceLocator)));
|
$this->assertTrue($s->getParam() instanceof ServiceLocator, sprintf('Wrong instance, should be an instance of ServiceLocator, %s given', \is_object($serviceLocator) ? \get_class($serviceLocator) : \gettype($serviceLocator)));
|
||||||
|
|
||||||
$same = [
|
$expected = [
|
||||||
BarTagClass::class => $serviceLocator->get(BarTagClass::class),
|
'bar_tag' => $container->get('bar_tag'),
|
||||||
];
|
];
|
||||||
$this->assertSame([BarTagClass::class => $container->get(BarTagClass::class)], $same);
|
$this->assertSame($expected, ['bar_tag' => $serviceLocator->get('bar_tag')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTaggedServiceLocatorWithDefaultIndex()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register('bar_tag', BarTagClass::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('app.foo_bar', ['foo_bar' => 'baz'])
|
||||||
|
;
|
||||||
|
$container->register('foo_bar_tagged', FooBarTaggedClass::class)
|
||||||
|
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('app.foo_bar', null, null, true)))
|
||||||
|
->setPublic(true)
|
||||||
|
;
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$s = $container->get('foo_bar_tagged');
|
||||||
|
|
||||||
|
/** @var ServiceLocator $serviceLocator */
|
||||||
|
$serviceLocator = $s->getParam();
|
||||||
|
$this->assertTrue($s->getParam() instanceof ServiceLocator, sprintf('Wrong instance, should be an instance of ServiceLocator, %s given', \is_object($serviceLocator) ? \get_class($serviceLocator) : \gettype($serviceLocator)));
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'baz' => $container->get('bar_tag'),
|
||||||
|
];
|
||||||
|
$this->assertSame($expected, ['baz' => $serviceLocator->get('baz')]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace Symfony\Component\EventDispatcher;
|
||||||
|
|
||||||
use Psr\EventDispatcher\StoppableEventInterface;
|
use Psr\EventDispatcher\StoppableEventInterface;
|
||||||
use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
|
use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
|
||||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch().
|
* An helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch().
|
||||||
|
@ -26,7 +26,7 @@ final class LegacyEventDispatcherProxy implements EventDispatcherInterface
|
||||||
{
|
{
|
||||||
private $dispatcher;
|
private $dispatcher;
|
||||||
|
|
||||||
public static function decorate(?EventDispatcherInterface $dispatcher): ?EventDispatcherInterface
|
public static function decorate(?ContractsEventDispatcherInterface $dispatcher): ?ContractsEventDispatcherInterface
|
||||||
{
|
{
|
||||||
if (null === $dispatcher) {
|
if (null === $dispatcher) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -58,13 +58,13 @@ final class LegacyEventDispatcherProxy implements EventDispatcherInterface
|
||||||
if (\is_object($event)) {
|
if (\is_object($event)) {
|
||||||
$eventName = $eventName ?? \get_class($event);
|
$eventName = $eventName ?? \get_class($event);
|
||||||
} else {
|
} else {
|
||||||
@trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', EventDispatcherInterface::class), E_USER_DEPRECATED);
|
@trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', ContractsEventDispatcherInterface::class), E_USER_DEPRECATED);
|
||||||
$swap = $event;
|
$swap = $event;
|
||||||
$event = $eventName ?? new Event();
|
$event = $eventName ?? new Event();
|
||||||
$eventName = $swap;
|
$eventName = $swap;
|
||||||
|
|
||||||
if (!$event instanceof Event) {
|
if (!$event instanceof Event) {
|
||||||
throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of %s, %s given.', EventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event)));
|
throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of %s, %s given.', ContractsEventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,12 @@ namespace Symfony\Component\Form\Extension\Core\Type;
|
||||||
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
|
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
|
||||||
|
use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader;
|
||||||
use Symfony\Component\Form\Exception\LogicException;
|
use Symfony\Component\Form\Exception\LogicException;
|
||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeZoneToStringTransformer;
|
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeZoneToStringTransformer;
|
||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\IntlTimeZoneToStringTransformer;
|
use Symfony\Component\Form\Extension\Core\DataTransformer\IntlTimeZoneToStringTransformer;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Intl\Timezones;
|
||||||
use Symfony\Component\OptionsResolver\Options;
|
use Symfony\Component\OptionsResolver\Options;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
@ -40,19 +42,41 @@ class TimezoneType extends AbstractType
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
|
'intl' => false,
|
||||||
'choice_loader' => function (Options $options) {
|
'choice_loader' => function (Options $options) {
|
||||||
$regions = $options->offsetGet('regions', false);
|
|
||||||
$input = $options['input'];
|
$input = $options['input'];
|
||||||
|
|
||||||
|
if ($options['intl']) {
|
||||||
|
$choiceTranslationLocale = $options['choice_translation_locale'];
|
||||||
|
|
||||||
|
return new IntlCallbackChoiceLoader(function () use ($input, $choiceTranslationLocale) {
|
||||||
|
return self::getIntlTimezones($input, $choiceTranslationLocale);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$regions = $options->offsetGet('regions', false);
|
||||||
|
|
||||||
return new CallbackChoiceLoader(function () use ($regions, $input) {
|
return new CallbackChoiceLoader(function () use ($regions, $input) {
|
||||||
return self::getTimezones($regions, $input);
|
return self::getPhpTimezones($regions, $input);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
'choice_translation_domain' => false,
|
'choice_translation_domain' => false,
|
||||||
|
'choice_translation_locale' => null,
|
||||||
'input' => 'string',
|
'input' => 'string',
|
||||||
'regions' => \DateTimeZone::ALL,
|
'regions' => \DateTimeZone::ALL,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$resolver->setAllowedTypes('intl', ['bool']);
|
||||||
|
|
||||||
|
$resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']);
|
||||||
|
$resolver->setNormalizer('choice_translation_locale', function (Options $options, $value) {
|
||||||
|
if (null !== $value && !$options['intl']) {
|
||||||
|
throw new LogicException('The "choice_translation_locale" option can only be used if the "intl" option is set to true.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
});
|
||||||
|
|
||||||
$resolver->setAllowedValues('input', ['string', 'datetimezone', 'intltimezone']);
|
$resolver->setAllowedValues('input', ['string', 'datetimezone', 'intltimezone']);
|
||||||
$resolver->setNormalizer('input', function (Options $options, $value) {
|
$resolver->setNormalizer('input', function (Options $options, $value) {
|
||||||
if ('intltimezone' === $value && !class_exists(\IntlTimeZone::class)) {
|
if ('intltimezone' === $value && !class_exists(\IntlTimeZone::class)) {
|
||||||
|
@ -64,6 +88,13 @@ class TimezoneType extends AbstractType
|
||||||
|
|
||||||
$resolver->setAllowedTypes('regions', 'int');
|
$resolver->setAllowedTypes('regions', 'int');
|
||||||
$resolver->setDeprecated('regions', 'The option "%name%" is deprecated since Symfony 4.2.');
|
$resolver->setDeprecated('regions', 'The option "%name%" is deprecated since Symfony 4.2.');
|
||||||
|
$resolver->setNormalizer('regions', function (Options $options, $value) {
|
||||||
|
if ($options['intl'] && \DateTimeZone::ALL !== (\DateTimeZone::ALL & $value)) {
|
||||||
|
throw new LogicException('The "regions" option can only be used if the "intl" option is set to false.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,10 +113,7 @@ class TimezoneType extends AbstractType
|
||||||
return 'timezone';
|
return 'timezone';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static function getPhpTimezones(int $regions, string $input): array
|
||||||
* Returns a normalized array of timezone choices.
|
|
||||||
*/
|
|
||||||
private static function getTimezones(int $regions, string $input): array
|
|
||||||
{
|
{
|
||||||
$timezones = [];
|
$timezones = [];
|
||||||
|
|
||||||
|
@ -99,4 +127,19 @@ class TimezoneType extends AbstractType
|
||||||
|
|
||||||
return $timezones;
|
return $timezones;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function getIntlTimezones(string $input, string $locale = null): array
|
||||||
|
{
|
||||||
|
$timezones = array_flip(Timezones::getNames($locale));
|
||||||
|
|
||||||
|
if ('intltimezone' === $input) {
|
||||||
|
foreach ($timezones as $name => $timezone) {
|
||||||
|
if ('Etc/Unknown' === \IntlTimeZone::createTimeZone($timezone)->getID()) {
|
||||||
|
unset($timezones[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $timezones;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||||
|
|
||||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||||
|
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||||
|
|
||||||
class TimezoneTypeTest extends BaseTypeTest
|
class TimezoneTypeTest extends BaseTypeTest
|
||||||
{
|
{
|
||||||
|
@ -83,6 +84,17 @@ class TimezoneTypeTest extends BaseTypeTest
|
||||||
$this->assertContains(new ChoiceView('Europe/Amsterdam', 'Europe/Amsterdam', 'Europe / Amsterdam'), $choices, '', false, false);
|
$this->assertContains(new ChoiceView('Europe/Amsterdam', 'Europe/Amsterdam', 'Europe / Amsterdam'), $choices, '', false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
* @expectedDeprecation The option "regions" is deprecated since Symfony 4.2.
|
||||||
|
* @expectedException \Symfony\Component\Form\Exception\LogicException
|
||||||
|
* @expectedExceptionMessage The "regions" option can only be used if the "intl" option is set to false.
|
||||||
|
*/
|
||||||
|
public function testFilterByRegionsWithIntl()
|
||||||
|
{
|
||||||
|
$this->factory->create(static::TESTED_TYPE, null, ['regions' => \DateTimeZone::EUROPE, 'intl' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @requires extension intl
|
* @requires extension intl
|
||||||
*/
|
*/
|
||||||
|
@ -116,4 +128,54 @@ class TimezoneTypeTest extends BaseTypeTest
|
||||||
$this->assertNull($form->getData());
|
$this->assertNull($form->getData());
|
||||||
$this->assertNotContains('Europe/Saratov', $form->getConfig()->getAttribute('choice_list')->getValues());
|
$this->assertNotContains('Europe/Saratov', $form->getConfig()->getAttribute('choice_list')->getValues());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires extension intl
|
||||||
|
*/
|
||||||
|
public function testIntlTimeZoneInputWithBcAndIntl()
|
||||||
|
{
|
||||||
|
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'intltimezone', 'intl' => true]);
|
||||||
|
$form->submit('Europe/Saratov');
|
||||||
|
|
||||||
|
$this->assertNull($form->getData());
|
||||||
|
$this->assertNotContains('Europe/Saratov', $form->getConfig()->getAttribute('choice_list')->getValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTimezonesAreSelectableWithIntl()
|
||||||
|
{
|
||||||
|
IntlTestHelper::requireIntl($this, false);
|
||||||
|
|
||||||
|
$choices = $this->factory->create(static::TESTED_TYPE, null, ['intl' => true])
|
||||||
|
->createView()->vars['choices'];
|
||||||
|
|
||||||
|
$this->assertContains(new ChoiceView('Europe/Amsterdam', 'Europe/Amsterdam', 'Central European Time (Amsterdam)'), $choices, '', false, false);
|
||||||
|
$this->assertContains(new ChoiceView('Etc/UTC', 'Etc/UTC', 'Coordinated Universal Time'), $choices, '', false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires extension intl
|
||||||
|
*/
|
||||||
|
public function testChoiceTranslationLocaleOptionWithIntl()
|
||||||
|
{
|
||||||
|
$choices = $this->factory
|
||||||
|
->create(static::TESTED_TYPE, null, [
|
||||||
|
'intl' => true,
|
||||||
|
'choice_translation_locale' => 'uk',
|
||||||
|
])
|
||||||
|
->createView()->vars['choices'];
|
||||||
|
|
||||||
|
$this->assertContains(new ChoiceView('Europe/Amsterdam', 'Europe/Amsterdam', 'за центральноєвропейським часом (Амстердам)'), $choices, '', false, false);
|
||||||
|
$this->assertContains(new ChoiceView('Etc/UTC', 'Etc/UTC', 'за всесвітнім координованим часом'), $choices, '', false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Form\Exception\LogicException
|
||||||
|
* @expectedExceptionMessage The "choice_translation_locale" option can only be used if the "intl" option is set to true.
|
||||||
|
*/
|
||||||
|
public function testChoiceTranslationLocaleOptionWithoutIntl()
|
||||||
|
{
|
||||||
|
$this->factory->create(static::TESTED_TYPE, null, [
|
||||||
|
'choice_translation_locale' => 'uk',
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ CHANGELOG
|
||||||
|
|
||||||
* [BC BREAK] `SendersLocatorInterface` has an additional method:
|
* [BC BREAK] `SendersLocatorInterface` has an additional method:
|
||||||
`getSenderByAlias()`.
|
`getSenderByAlias()`.
|
||||||
|
* Removed argument `?bool &$handle = false` from `SendersLocatorInterface::getSenders`
|
||||||
* A new `ListableReceiverInterface` was added, which a receiver
|
* A new `ListableReceiverInterface` was added, which a receiver
|
||||||
can implement (when applicable) to enable listing and fetching
|
can implement (when applicable) to enable listing and fetching
|
||||||
individual messages by id (used in the new "Failed Messages" commands).
|
individual messages by id (used in the new "Failed Messages" commands).
|
||||||
|
|
|
@ -15,6 +15,7 @@ use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Helper\Dumper;
|
use Symfony\Component\Console\Helper\Dumper;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Messenger\Envelope;
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
|
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
|
||||||
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
|
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
|
||||||
|
@ -59,8 +60,10 @@ abstract class AbstractFailedMessagesCommand extends Command
|
||||||
{
|
{
|
||||||
$io->title('Failed Message Details');
|
$io->title('Failed Message Details');
|
||||||
|
|
||||||
/** @var SentToFailureTransportStamp $sentToFailureTransportStamp */
|
/** @var SentToFailureTransportStamp|null $sentToFailureTransportStamp */
|
||||||
$sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
|
$sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
|
||||||
|
/** @var RedeliveryStamp|null $lastRedeliveryStamp */
|
||||||
|
$lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
||||||
|
|
||||||
$rows = [
|
$rows = [
|
||||||
['Class', \get_class($envelope->getMessage())],
|
['Class', \get_class($envelope->getMessage())],
|
||||||
|
@ -70,25 +73,34 @@ abstract class AbstractFailedMessagesCommand extends Command
|
||||||
$rows[] = ['Message Id', $id];
|
$rows[] = ['Message Id', $id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$flattenException = null === $lastRedeliveryStamp ? null : $lastRedeliveryStamp->getFlattenException();
|
||||||
if (null === $sentToFailureTransportStamp) {
|
if (null === $sentToFailureTransportStamp) {
|
||||||
$io->warning('Message does not appear to have been sent to this transport after failing');
|
$io->warning('Message does not appear to have been sent to this transport after failing');
|
||||||
} else {
|
} else {
|
||||||
$rows = array_merge($rows, [
|
$rows = array_merge($rows, [
|
||||||
['Failed at', $sentToFailureTransportStamp->getSentAt()->format('Y-m-d H:i:s')],
|
['Failed at', null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')],
|
||||||
['Error', $sentToFailureTransportStamp->getExceptionMessage()],
|
['Error', null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getExceptionMessage()],
|
||||||
['Error Class', $sentToFailureTransportStamp->getFlattenException() ? $sentToFailureTransportStamp->getFlattenException()->getClass() : '(unknown)'],
|
['Error Class', null === $flattenException ? '(unknown)' : $flattenException->getClass()],
|
||||||
['Transport', $sentToFailureTransportStamp->getOriginalReceiverName()],
|
['Transport', $sentToFailureTransportStamp->getOriginalReceiverName()],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$io->table([], $rows);
|
$io->table([], $rows);
|
||||||
|
|
||||||
|
/** @var RedeliveryStamp[] $redeliveryStamps */
|
||||||
|
$redeliveryStamps = $envelope->all(RedeliveryStamp::class);
|
||||||
|
$io->writeln(' Message history:');
|
||||||
|
foreach ($redeliveryStamps as $redeliveryStamp) {
|
||||||
|
$io->writeln(sprintf(' * Message failed and redelivered to the <info>%s</info> transport at <info>%s</info>', $redeliveryStamp->getSenderClassOrAlias(), $redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')));
|
||||||
|
}
|
||||||
|
$io->newLine();
|
||||||
|
|
||||||
if ($io->isVeryVerbose()) {
|
if ($io->isVeryVerbose()) {
|
||||||
$io->title('Message:');
|
$io->title('Message:');
|
||||||
$dump = new Dumper($io);
|
$dump = new Dumper($io);
|
||||||
$io->writeln($dump($envelope->getMessage()));
|
$io->writeln($dump($envelope->getMessage()));
|
||||||
$io->title('Exception:');
|
$io->title('Exception:');
|
||||||
$io->writeln($sentToFailureTransportStamp->getFlattenException()->getTraceAsString());
|
$io->writeln(null === $flattenException ? '(no data)' : $flattenException->getTraceAsString());
|
||||||
} else {
|
} else {
|
||||||
$io->writeln(' Re-run command with <info>-vv</info> to see more message & error details.');
|
$io->writeln(' Re-run command with <info>-vv</info> to see more message & error details.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
// avoid success message if nothing was processed
|
// avoid success message if nothing was processed
|
||||||
if (1 < $count) {
|
if (1 <= $count) {
|
||||||
$io->success('All failed messages have been handled or removed!');
|
$io->success('All failed messages have been handled or removed!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||||
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
|
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,14 +83,14 @@ EOF
|
||||||
|
|
||||||
$rows = [];
|
$rows = [];
|
||||||
foreach ($envelopes as $envelope) {
|
foreach ($envelopes as $envelope) {
|
||||||
/** @var SentToFailureTransportStamp $sentToFailureTransportStamp */
|
/** @var RedeliveryStamp|null $lastRedeliveryStamp */
|
||||||
$sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
|
$lastRedeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
||||||
|
|
||||||
$rows[] = [
|
$rows[] = [
|
||||||
$this->getMessageId($envelope),
|
$this->getMessageId($envelope),
|
||||||
\get_class($envelope->getMessage()),
|
\get_class($envelope->getMessage()),
|
||||||
null === $sentToFailureTransportStamp ? '' : $sentToFailureTransportStamp->getSentAt()->format('Y-m-d H:i:s'),
|
null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s'),
|
||||||
null === $sentToFailureTransportStamp ? '' : $sentToFailureTransportStamp->getExceptionMessage(),
|
null === $lastRedeliveryStamp ? '' : $lastRedeliveryStamp->getExceptionMessage(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,9 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||||
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
|
use Symfony\Component\Messenger\Stamp\DelayStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
|
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\SentStamp;
|
|
||||||
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
|
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
|
||||||
|
|
||||||
|
@ -51,11 +51,8 @@ class SendFailedMessageToFailureTransportListener implements EventSubscriberInte
|
||||||
$envelope = $event->getEnvelope();
|
$envelope = $event->getEnvelope();
|
||||||
|
|
||||||
// avoid re-sending to the failed sender
|
// avoid re-sending to the failed sender
|
||||||
foreach ($envelope->all(SentStamp::class) as $sentStamp) {
|
if (null !== $envelope->last(SentToFailureTransportStamp::class)) {
|
||||||
/** @var SentStamp $sentStamp */
|
return;
|
||||||
if ($sentStamp->getSenderAlias() === $this->failureSenderAlias) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the received stamp so it's redelivered
|
// remove the received stamp so it's redelivered
|
||||||
|
@ -67,8 +64,9 @@ class SendFailedMessageToFailureTransportListener implements EventSubscriberInte
|
||||||
$flattenedException = \class_exists(FlattenException::class) ? FlattenException::createFromThrowable($throwable) : null;
|
$flattenedException = \class_exists(FlattenException::class) ? FlattenException::createFromThrowable($throwable) : null;
|
||||||
$envelope = $envelope->withoutAll(ReceivedStamp::class)
|
$envelope = $envelope->withoutAll(ReceivedStamp::class)
|
||||||
->withoutAll(TransportMessageIdStamp::class)
|
->withoutAll(TransportMessageIdStamp::class)
|
||||||
->with(new SentToFailureTransportStamp($throwable->getMessage(), $event->getReceiverName(), $flattenedException))
|
->with(new SentToFailureTransportStamp($event->getReceiverName()))
|
||||||
->with(new RedeliveryStamp(0, $this->failureSenderAlias));
|
->with(new DelayStamp(0))
|
||||||
|
->with(new RedeliveryStamp(0, $this->failureSenderAlias, $throwable->getMessage(), $flattenedException));
|
||||||
|
|
||||||
if (null !== $this->logger) {
|
if (null !== $this->logger) {
|
||||||
$this->logger->info('Rejected message {class} will be sent to the failure transport {transport}.', [
|
$this->logger->info('Rejected message {class} will be sent to the failure transport {transport}.', [
|
||||||
|
|
|
@ -32,13 +32,14 @@ interface MessageSubscriberInterface extends MessageHandlerInterface
|
||||||
* yield FirstMessage::class => ['priority' => 0];
|
* yield FirstMessage::class => ['priority' => 0];
|
||||||
* yield SecondMessage::class => ['priority => -10];
|
* yield SecondMessage::class => ['priority => -10];
|
||||||
*
|
*
|
||||||
* It can also specify a method, a priority and/or a bus per message:
|
* It can also specify a method, a priority, a bus and/or a transport per message:
|
||||||
*
|
*
|
||||||
* yield FirstMessage::class => ['method' => 'firstMessageMethod'];
|
* yield FirstMessage::class => ['method' => 'firstMessageMethod'];
|
||||||
* yield SecondMessage::class => [
|
* yield SecondMessage::class => [
|
||||||
* 'method' => 'secondMessageMethod',
|
* 'method' => 'secondMessageMethod',
|
||||||
* 'priority' => 20,
|
* 'priority' => 20,
|
||||||
* 'bus' => 'my_bus_name',
|
* 'bus' => 'my_bus_name',
|
||||||
|
* 'from_transport' => 'your_transport_name',
|
||||||
* ];
|
* ];
|
||||||
*
|
*
|
||||||
* The benefit of using `yield` instead of returning an array is that you can `yield` multiple times the
|
* The benefit of using `yield` instead of returning an array is that you can `yield` multiple times the
|
||||||
|
|
|
@ -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\Middleware;
|
||||||
|
|
||||||
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
|
||||||
|
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Ryan Weaver <ryan@symfonycasts.com>
|
||||||
|
*
|
||||||
|
* @experimental in 4.3
|
||||||
|
*/
|
||||||
|
class FailedMessageProcessingMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
public function handle(Envelope $envelope, StackInterface $stack): Envelope
|
||||||
|
{
|
||||||
|
// look for "received" messages decorated with the SentToFailureTransportStamp
|
||||||
|
/** @var SentToFailureTransportStamp|null $sentToFailureStamp */
|
||||||
|
$sentToFailureStamp = $envelope->last(SentToFailureTransportStamp::class);
|
||||||
|
if (null !== $sentToFailureStamp && null !== $envelope->last(ReceivedStamp::class)) {
|
||||||
|
// mark the message as "received" from the original transport
|
||||||
|
// this guarantees the same behavior as when originally received
|
||||||
|
$envelope = $envelope->with(new ReceivedStamp($sentToFailureStamp->getOriginalReceiverName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stack->next()->handle($envelope, $stack);
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,7 +52,6 @@ class SendMessageMiddleware implements MiddlewareInterface
|
||||||
'class' => \get_class($envelope->getMessage()),
|
'class' => \get_class($envelope->getMessage()),
|
||||||
];
|
];
|
||||||
|
|
||||||
$handle = false;
|
|
||||||
$sender = null;
|
$sender = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -65,7 +64,7 @@ class SendMessageMiddleware implements MiddlewareInterface
|
||||||
|
|
||||||
// dispatch event unless this is a redelivery
|
// dispatch event unless this is a redelivery
|
||||||
$shouldDispatchEvent = null === $redeliveryStamp;
|
$shouldDispatchEvent = null === $redeliveryStamp;
|
||||||
foreach ($this->getSenders($envelope, $handle, $redeliveryStamp) as $alias => $sender) {
|
foreach ($this->getSenders($envelope, $redeliveryStamp) as $alias => $sender) {
|
||||||
if (null !== $this->eventDispatcher && $shouldDispatchEvent) {
|
if (null !== $this->eventDispatcher && $shouldDispatchEvent) {
|
||||||
$event = new SendMessageToTransportsEvent($envelope);
|
$event = new SendMessageToTransportsEvent($envelope);
|
||||||
$this->eventDispatcher->dispatch($event);
|
$this->eventDispatcher->dispatch($event);
|
||||||
|
@ -76,14 +75,9 @@ class SendMessageMiddleware implements MiddlewareInterface
|
||||||
$this->logger->info('Sending message "{class}" with "{sender}"', $context + ['sender' => \get_class($sender)]);
|
$this->logger->info('Sending message "{class}" with "{sender}"', $context + ['sender' => \get_class($sender)]);
|
||||||
$envelope = $sender->send($envelope->with(new SentStamp(\get_class($sender), \is_string($alias) ? $alias : null)));
|
$envelope = $sender->send($envelope->with(new SentStamp(\get_class($sender), \is_string($alias) ? $alias : null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// on a redelivery, only send back to queue: never call local handlers
|
|
||||||
if (null !== $redeliveryStamp) {
|
|
||||||
$handle = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $sender || $handle) {
|
if (null === $sender) {
|
||||||
return $stack->next()->handle($envelope, $stack);
|
return $stack->next()->handle($envelope, $stack);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
@ -100,7 +94,7 @@ class SendMessageMiddleware implements MiddlewareInterface
|
||||||
/**
|
/**
|
||||||
* * @return iterable|SenderInterface[]
|
* * @return iterable|SenderInterface[]
|
||||||
*/
|
*/
|
||||||
private function getSenders(Envelope $envelope, &$handle, ?RedeliveryStamp $redeliveryStamp): iterable
|
private function getSenders(Envelope $envelope, ?RedeliveryStamp $redeliveryStamp): iterable
|
||||||
{
|
{
|
||||||
if (null !== $redeliveryStamp) {
|
if (null !== $redeliveryStamp) {
|
||||||
return [
|
return [
|
||||||
|
@ -108,6 +102,6 @@ class SendMessageMiddleware implements MiddlewareInterface
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->sendersLocator->getSenders($envelope, $handle);
|
return $this->sendersLocator->getSenders($envelope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,13 +28,15 @@ use Symfony\Component\Messenger\Stamp\BusNameStamp;
|
||||||
class RoutableMessageBus implements MessageBusInterface
|
class RoutableMessageBus implements MessageBusInterface
|
||||||
{
|
{
|
||||||
private $busLocator;
|
private $busLocator;
|
||||||
|
private $fallbackBus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ContainerInterface $busLocator A locator full of MessageBusInterface objects
|
* @param ContainerInterface $busLocator A locator full of MessageBusInterface objects
|
||||||
*/
|
*/
|
||||||
public function __construct(ContainerInterface $busLocator)
|
public function __construct(ContainerInterface $busLocator, MessageBusInterface $fallbackBus = null)
|
||||||
{
|
{
|
||||||
$this->busLocator = $busLocator;
|
$this->busLocator = $busLocator;
|
||||||
|
$this->fallbackBus = $fallbackBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dispatch($envelope, array $stamps = []): Envelope
|
public function dispatch($envelope, array $stamps = []): Envelope
|
||||||
|
@ -43,14 +45,28 @@ class RoutableMessageBus implements MessageBusInterface
|
||||||
throw new InvalidArgumentException('Messages passed to RoutableMessageBus::dispatch() must be inside an Envelope');
|
throw new InvalidArgumentException('Messages passed to RoutableMessageBus::dispatch() must be inside an Envelope');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var BusNameStamp $busNameStamp */
|
return $this->getMessageBus($envelope)->dispatch($envelope, $stamps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMessageBus(Envelope $envelope): MessageBusInterface
|
||||||
|
{
|
||||||
|
/** @var BusNameStamp|null $busNameStamp */
|
||||||
$busNameStamp = $envelope->last(BusNameStamp::class);
|
$busNameStamp = $envelope->last(BusNameStamp::class);
|
||||||
$busName = null !== $busNameStamp ? $busNameStamp->getBusName() : MessageBusInterface::class;
|
|
||||||
|
if (null === $busNameStamp) {
|
||||||
|
if (null === $this->fallbackBus) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Envelope is missing a BusNameStamp and no fallback message bus is configured on RoutableMessageBus.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fallbackBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
$busName = $busNameStamp->getBusName();
|
||||||
|
|
||||||
if (!$this->busLocator->has($busName)) {
|
if (!$this->busLocator->has($busName)) {
|
||||||
throw new InvalidArgumentException(sprintf('Bus named "%s" does not exist.', $busName));
|
throw new InvalidArgumentException(sprintf('Bus named "%s" does not exist.', $busName));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->busLocator->get($busName)->dispatch($envelope, $stamps);
|
return $this->busLocator->get($busName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Messenger\Stamp;
|
namespace Symfony\Component\Messenger\Stamp;
|
||||||
|
|
||||||
|
use Symfony\Component\Debug\Exception\FlattenException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stamp applied when a messages needs to be redelivered.
|
* Stamp applied when a messages needs to be redelivered.
|
||||||
*
|
*
|
||||||
|
@ -20,14 +22,20 @@ class RedeliveryStamp implements StampInterface
|
||||||
{
|
{
|
||||||
private $retryCount;
|
private $retryCount;
|
||||||
private $senderClassOrAlias;
|
private $senderClassOrAlias;
|
||||||
|
private $redeliveredAt;
|
||||||
|
private $exceptionMessage;
|
||||||
|
private $flattenException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $senderClassOrAlias Alias from SendersLocator or just the class name
|
* @param string $senderClassOrAlias Alias from SendersLocator or just the class name
|
||||||
*/
|
*/
|
||||||
public function __construct(int $retryCount, string $senderClassOrAlias)
|
public function __construct(int $retryCount, string $senderClassOrAlias, string $exceptionMessage = null, FlattenException $flattenException = null)
|
||||||
{
|
{
|
||||||
$this->retryCount = $retryCount;
|
$this->retryCount = $retryCount;
|
||||||
$this->senderClassOrAlias = $senderClassOrAlias;
|
$this->senderClassOrAlias = $senderClassOrAlias;
|
||||||
|
$this->exceptionMessage = $exceptionMessage;
|
||||||
|
$this->flattenException = $flattenException;
|
||||||
|
$this->redeliveredAt = new \DateTimeImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRetryCount(): int
|
public function getRetryCount(): int
|
||||||
|
@ -36,7 +44,7 @@ class RedeliveryStamp implements StampInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Needed for this class to serialize through Symfony's serializer.
|
* The target sender this should be redelivered to.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -44,4 +52,19 @@ class RedeliveryStamp implements StampInterface
|
||||||
{
|
{
|
||||||
return $this->senderClassOrAlias;
|
return $this->senderClassOrAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getExceptionMessage(): ?string
|
||||||
|
{
|
||||||
|
return $this->exceptionMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFlattenException(): ?FlattenException
|
||||||
|
{
|
||||||
|
return $this->flattenException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRedeliveredAt(): \DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->redeliveredAt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Messenger\Stamp;
|
namespace Symfony\Component\Messenger\Stamp;
|
||||||
|
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stamp applied when a message is sent to the failure transport.
|
* Stamp applied when a message is sent to the failure transport.
|
||||||
*
|
*
|
||||||
|
@ -22,36 +20,15 @@ use Symfony\Component\Debug\Exception\FlattenException;
|
||||||
*/
|
*/
|
||||||
class SentToFailureTransportStamp implements StampInterface
|
class SentToFailureTransportStamp implements StampInterface
|
||||||
{
|
{
|
||||||
private $exceptionMessage;
|
|
||||||
private $originalReceiverName;
|
private $originalReceiverName;
|
||||||
private $flattenException;
|
|
||||||
private $sentAt;
|
|
||||||
|
|
||||||
public function __construct(string $exceptionMessage, string $originalReceiverName, FlattenException $flattenException = null)
|
public function __construct(string $originalReceiverName)
|
||||||
{
|
{
|
||||||
$this->exceptionMessage = $exceptionMessage;
|
|
||||||
$this->originalReceiverName = $originalReceiverName;
|
$this->originalReceiverName = $originalReceiverName;
|
||||||
$this->flattenException = $flattenException;
|
|
||||||
$this->sentAt = new \DateTimeImmutable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExceptionMessage(): string
|
|
||||||
{
|
|
||||||
return $this->exceptionMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOriginalReceiverName(): string
|
public function getOriginalReceiverName(): string
|
||||||
{
|
{
|
||||||
return $this->originalReceiverName;
|
return $this->originalReceiverName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFlattenException(): ?FlattenException
|
|
||||||
{
|
|
||||||
return $this->flattenException;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSentAt(): \DateTimeInterface
|
|
||||||
{
|
|
||||||
return $this->sentAt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Console\Tester\CommandTester;
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
use Symfony\Component\Messenger\Command\FailedMessagesShowCommand;
|
use Symfony\Component\Messenger\Command\FailedMessagesShowCommand;
|
||||||
use Symfony\Component\Messenger\Envelope;
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
|
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
|
||||||
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
|
use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
|
||||||
|
@ -26,10 +27,12 @@ class FailedMessagesShowCommandTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testBasicRun()
|
public function testBasicRun()
|
||||||
{
|
{
|
||||||
$sentToFailureStamp = new SentToFailureTransportStamp('Things are bad!', 'async');
|
$sentToFailureStamp = new SentToFailureTransportStamp('async');
|
||||||
|
$redeliveryStamp = new RedeliveryStamp(0, 'failure_receiver', 'Things are bad!');
|
||||||
$envelope = new Envelope(new \stdClass(), [
|
$envelope = new Envelope(new \stdClass(), [
|
||||||
new TransportMessageIdStamp(15),
|
new TransportMessageIdStamp(15),
|
||||||
$sentToFailureStamp,
|
$sentToFailureStamp,
|
||||||
|
$redeliveryStamp,
|
||||||
]);
|
]);
|
||||||
$receiver = $this->createMock(ListableReceiverInterface::class);
|
$receiver = $this->createMock(ListableReceiverInterface::class);
|
||||||
$receiver->expects($this->once())->method('find')->with(15)->willReturn($envelope);
|
$receiver->expects($this->once())->method('find')->with(15)->willReturn($envelope);
|
||||||
|
@ -48,10 +51,11 @@ class FailedMessagesShowCommandTest extends TestCase
|
||||||
Message Id 15
|
Message Id 15
|
||||||
Failed at %s
|
Failed at %s
|
||||||
Error Things are bad!
|
Error Things are bad!
|
||||||
Error Class (unknown)
|
Error Class (unknown)
|
||||||
|
Transport async
|
||||||
EOF
|
EOF
|
||||||
,
|
,
|
||||||
$sentToFailureStamp->getSentAt()->format('Y-m-d H:i:s')),
|
$redeliveryStamp->getRedeliveredAt()->format('Y-m-d H:i:s')),
|
||||||
$tester->getDisplay(true));
|
$tester->getDisplay(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
|
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\SentStamp;
|
|
||||||
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
||||||
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
|
use Symfony\Component\Messenger\Stamp\TransportMessageIdStamp;
|
||||||
|
|
||||||
|
@ -35,13 +34,13 @@ class SendFailedMessageToFailureTransportListenerTest extends TestCase
|
||||||
/** @var SentToFailureTransportStamp $sentToFailureTransportStamp */
|
/** @var SentToFailureTransportStamp $sentToFailureTransportStamp */
|
||||||
$sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
|
$sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
|
||||||
$this->assertNotNull($sentToFailureTransportStamp);
|
$this->assertNotNull($sentToFailureTransportStamp);
|
||||||
$this->assertSame('no!', $sentToFailureTransportStamp->getExceptionMessage());
|
|
||||||
$this->assertSame('my_receiver', $sentToFailureTransportStamp->getOriginalReceiverName());
|
$this->assertSame('my_receiver', $sentToFailureTransportStamp->getOriginalReceiverName());
|
||||||
$this->assertSame('no!', $sentToFailureTransportStamp->getFlattenException()->getMessage());
|
|
||||||
|
|
||||||
/** @var RedeliveryStamp $redeliveryStamp */
|
/** @var RedeliveryStamp $redeliveryStamp */
|
||||||
$redeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
$redeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
||||||
$this->assertSame('failure_sender', $redeliveryStamp->getSenderClassOrAlias());
|
$this->assertSame('failure_sender', $redeliveryStamp->getSenderClassOrAlias());
|
||||||
|
$this->assertSame('no!', $redeliveryStamp->getExceptionMessage());
|
||||||
|
$this->assertSame('no!', $redeliveryStamp->getFlattenException()->getMessage());
|
||||||
|
|
||||||
$this->assertNull($envelope->last(ReceivedStamp::class));
|
$this->assertNull($envelope->last(ReceivedStamp::class));
|
||||||
$this->assertNull($envelope->last(TransportMessageIdStamp::class));
|
$this->assertNull($envelope->last(TransportMessageIdStamp::class));
|
||||||
|
@ -65,11 +64,11 @@ class SendFailedMessageToFailureTransportListenerTest extends TestCase
|
||||||
$bus = $this->createMock(MessageBusInterface::class);
|
$bus = $this->createMock(MessageBusInterface::class);
|
||||||
$bus->expects($this->once())->method('dispatch')->with($this->callback(function ($envelope) {
|
$bus->expects($this->once())->method('dispatch')->with($this->callback(function ($envelope) {
|
||||||
/** @var Envelope $envelope */
|
/** @var Envelope $envelope */
|
||||||
/** @var SentToFailureTransportStamp $sentToFailureTransportStamp */
|
/** @var RedeliveryStamp $redeliveryStamp */
|
||||||
$sentToFailureTransportStamp = $envelope->last(SentToFailureTransportStamp::class);
|
$redeliveryStamp = $envelope->last(RedeliveryStamp::class);
|
||||||
$this->assertNotNull($sentToFailureTransportStamp);
|
$this->assertNotNull($redeliveryStamp);
|
||||||
$this->assertSame('I am inside!', $sentToFailureTransportStamp->getExceptionMessage());
|
$this->assertSame('I am inside!', $redeliveryStamp->getExceptionMessage());
|
||||||
$this->assertSame('Exception', $sentToFailureTransportStamp->getFlattenException()->getClass());
|
$this->assertSame('Exception', $redeliveryStamp->getFlattenException()->getClass());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}))->willReturn(new Envelope(new \stdClass()));
|
}))->willReturn(new Envelope(new \stdClass()));
|
||||||
|
@ -112,7 +111,7 @@ class SendFailedMessageToFailureTransportListenerTest extends TestCase
|
||||||
);
|
);
|
||||||
|
|
||||||
$envelope = new Envelope(new \stdClass(), [
|
$envelope = new Envelope(new \stdClass(), [
|
||||||
new SentStamp('MySender', 'failure_sender'),
|
new SentToFailureTransportStamp('my_receiver'),
|
||||||
]);
|
]);
|
||||||
$event = new WorkerMessageFailedEvent($envelope, 'my_receiver', new \Exception(''), false);
|
$event = new WorkerMessageFailedEvent($envelope, 'my_receiver', new \Exception(''), false);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Container;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
|
use Symfony\Component\Messenger\Envelope;
|
||||||
|
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||||
|
use Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener;
|
||||||
|
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||||
|
use Symfony\Component\Messenger\Handler\HandlerDescriptor;
|
||||||
|
use Symfony\Component\Messenger\Handler\HandlersLocator;
|
||||||
|
use Symfony\Component\Messenger\MessageBus;
|
||||||
|
use Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware;
|
||||||
|
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
|
||||||
|
use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
|
||||||
|
use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy;
|
||||||
|
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||||
|
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
||||||
|
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
|
||||||
|
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
|
||||||
|
use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
|
||||||
|
use Symfony\Component\Messenger\Transport\Sender\SendersLocator;
|
||||||
|
use Symfony\Component\Messenger\Worker;
|
||||||
|
|
||||||
|
class FailureIntegrationTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testRequeMechanism()
|
||||||
|
{
|
||||||
|
$transport1 = new DummyFailureTestSenderAndReceiver();
|
||||||
|
$transport2 = new DummyFailureTestSenderAndReceiver();
|
||||||
|
$failureTransport = new DummyFailureTestSenderAndReceiver();
|
||||||
|
$transports = [
|
||||||
|
'transport1' => $transport1,
|
||||||
|
'transport2' => $transport2,
|
||||||
|
'the_failure_transport' => $failureTransport,
|
||||||
|
];
|
||||||
|
|
||||||
|
$locator = $this->createMock(ContainerInterface::class);
|
||||||
|
$locator->expects($this->any())
|
||||||
|
->method('has')
|
||||||
|
->willReturn(true);
|
||||||
|
$locator->expects($this->any())
|
||||||
|
->method('get')
|
||||||
|
->willReturnCallback(function ($transportName) use ($transports) {
|
||||||
|
return $transports[$transportName];
|
||||||
|
});
|
||||||
|
$senderLocator = new SendersLocator(
|
||||||
|
[DummyMessage::class => ['transport1', 'transport2']],
|
||||||
|
$locator
|
||||||
|
);
|
||||||
|
|
||||||
|
// using to so we can lazily get the bus later and avoid circular problem
|
||||||
|
$transport1HandlerThatFails = new DummyTestHandler(true);
|
||||||
|
$allTransportHandlerThatWorks = new DummyTestHandler(false);
|
||||||
|
$transport2HandlerThatWorks = new DummyTestHandler(false);
|
||||||
|
$container = new Container();
|
||||||
|
$handlerLocator = new HandlersLocator([
|
||||||
|
DummyMessage::class => [
|
||||||
|
new HandlerDescriptor($transport1HandlerThatFails, [
|
||||||
|
'from_transport' => 'transport1',
|
||||||
|
'alias' => 'handler_that_fails',
|
||||||
|
]),
|
||||||
|
new HandlerDescriptor($allTransportHandlerThatWorks, [
|
||||||
|
'alias' => 'handler_that_works1',
|
||||||
|
]),
|
||||||
|
new HandlerDescriptor($transport2HandlerThatWorks, [
|
||||||
|
'from_transport' => 'transport2',
|
||||||
|
'alias' => 'handler_that_works2',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$dispatcher = new EventDispatcher();
|
||||||
|
$bus = new MessageBus([
|
||||||
|
new FailedMessageProcessingMiddleware(),
|
||||||
|
new SendMessageMiddleware($senderLocator),
|
||||||
|
new HandleMessageMiddleware($handlerLocator),
|
||||||
|
]);
|
||||||
|
$container->set('bus', $bus);
|
||||||
|
$dispatcher->addSubscriber(new SendFailedMessageToFailureTransportListener($bus, 'the_failure_transport'));
|
||||||
|
|
||||||
|
$runWorker = function (string $transportName, int $maxRetries) use ($transports, $bus, $dispatcher): ?\Throwable {
|
||||||
|
$throwable = null;
|
||||||
|
$failedListener = function (WorkerMessageFailedEvent $event) use (&$throwable) {
|
||||||
|
$throwable = $event->getThrowable();
|
||||||
|
};
|
||||||
|
$dispatcher->addListener(WorkerMessageFailedEvent::class, $failedListener);
|
||||||
|
|
||||||
|
$worker = new Worker([$transportName => $transports[$transportName]], $bus, [$transportName => new MultiplierRetryStrategy($maxRetries)], $dispatcher);
|
||||||
|
|
||||||
|
$worker->run([], function (?Envelope $envelope) use ($worker) {
|
||||||
|
// handle one envelope, then stop
|
||||||
|
if (null !== $envelope) {
|
||||||
|
$worker->stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$dispatcher->removeListener(WorkerMessageFailedEvent::class, $failedListener);
|
||||||
|
|
||||||
|
return $throwable;
|
||||||
|
};
|
||||||
|
|
||||||
|
// send the message
|
||||||
|
$envelope = new Envelope(new DummyMessage('API'));
|
||||||
|
$bus->dispatch($envelope);
|
||||||
|
|
||||||
|
// message has been sent
|
||||||
|
$this->assertCount(1, $transport1->getMessagesWaitingToBeReceived());
|
||||||
|
$this->assertCount(1, $transport2->getMessagesWaitingToBeReceived());
|
||||||
|
$this->assertCount(0, $failureTransport->getMessagesWaitingToBeReceived());
|
||||||
|
|
||||||
|
// receive the message - one handler will fail and the message
|
||||||
|
// will be sent back to transport1 to be retried
|
||||||
|
/*
|
||||||
|
* Receive the message from "transport1"
|
||||||
|
*/
|
||||||
|
$throwable = $runWorker('transport1', 1);
|
||||||
|
// make sure this is failing for the reason we think
|
||||||
|
$this->assertInstanceOf(HandlerFailedException::class, $throwable);
|
||||||
|
// handler for transport1 and all transports were called
|
||||||
|
$this->assertSame(1, $transport1HandlerThatFails->getTimesCalled());
|
||||||
|
$this->assertSame(1, $allTransportHandlerThatWorks->getTimesCalled());
|
||||||
|
$this->assertSame(0, $transport2HandlerThatWorks->getTimesCalled());
|
||||||
|
// one handler failed and the message is retried (resent to transport1)
|
||||||
|
$this->assertCount(1, $transport1->getMessagesWaitingToBeReceived());
|
||||||
|
$this->assertEmpty($failureTransport->getMessagesWaitingToBeReceived());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Receive the message for a (final) retry
|
||||||
|
*/
|
||||||
|
$runWorker('transport1', 1);
|
||||||
|
// only the "failed" handler is called a 2nd time
|
||||||
|
$this->assertSame(2, $transport1HandlerThatFails->getTimesCalled());
|
||||||
|
$this->assertSame(1, $allTransportHandlerThatWorks->getTimesCalled());
|
||||||
|
// handling fails again, message is sent to failure transport
|
||||||
|
$this->assertCount(0, $transport1->getMessagesWaitingToBeReceived());
|
||||||
|
$this->assertCount(1, $failureTransport->getMessagesWaitingToBeReceived());
|
||||||
|
/** @var Envelope $failedEnvelope */
|
||||||
|
$failedEnvelope = $failureTransport->getMessagesWaitingToBeReceived()[0];
|
||||||
|
/** @var SentToFailureTransportStamp $sentToFailureStamp */
|
||||||
|
$sentToFailureStamp = $failedEnvelope->last(SentToFailureTransportStamp::class);
|
||||||
|
$this->assertNotNull($sentToFailureStamp);
|
||||||
|
/** @var RedeliveryStamp $redeliveryStamp */
|
||||||
|
$redeliveryStamp = $failedEnvelope->last(RedeliveryStamp::class);
|
||||||
|
$this->assertNotNull($redeliveryStamp);
|
||||||
|
$this->assertSame('Failure from call 2', $redeliveryStamp->getExceptionMessage());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Failed message is handled, fails, and sent for a retry
|
||||||
|
*/
|
||||||
|
$throwable = $runWorker('the_failure_transport', 1);
|
||||||
|
// make sure this is failing for the reason we think
|
||||||
|
$this->assertInstanceOf(HandlerFailedException::class, $throwable);
|
||||||
|
// only the "failed" handler is called a 3rd time
|
||||||
|
$this->assertSame(3, $transport1HandlerThatFails->getTimesCalled());
|
||||||
|
$this->assertSame(1, $allTransportHandlerThatWorks->getTimesCalled());
|
||||||
|
// handling fails again, message is retried
|
||||||
|
$this->assertCount(1, $failureTransport->getMessagesWaitingToBeReceived());
|
||||||
|
// transport2 still only holds the original message
|
||||||
|
// a new message was never mistakenly delivered to it
|
||||||
|
$this->assertCount(1, $transport2->getMessagesWaitingToBeReceived());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Message is retried on failure transport then discarded
|
||||||
|
*/
|
||||||
|
$runWorker('the_failure_transport', 1);
|
||||||
|
// only the "failed" handler is called a 4th time
|
||||||
|
$this->assertSame(4, $transport1HandlerThatFails->getTimesCalled());
|
||||||
|
$this->assertSame(1, $allTransportHandlerThatWorks->getTimesCalled());
|
||||||
|
// handling fails again, message is discarded
|
||||||
|
$this->assertCount(0, $failureTransport->getMessagesWaitingToBeReceived());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execute handlers on transport2
|
||||||
|
*/
|
||||||
|
$runWorker('transport2', 1);
|
||||||
|
// transport1 handler is not called again
|
||||||
|
$this->assertSame(4, $transport1HandlerThatFails->getTimesCalled());
|
||||||
|
// all transport handler is now called again
|
||||||
|
$this->assertSame(2, $allTransportHandlerThatWorks->getTimesCalled());
|
||||||
|
// transport1 handler called for the first time
|
||||||
|
$this->assertSame(1, $transport2HandlerThatWorks->getTimesCalled());
|
||||||
|
// all transport should be empty
|
||||||
|
$this->assertEmpty($transport1->getMessagesWaitingToBeReceived());
|
||||||
|
$this->assertEmpty($transport2->getMessagesWaitingToBeReceived());
|
||||||
|
$this->assertEmpty($failureTransport->getMessagesWaitingToBeReceived());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dispatch the original message again
|
||||||
|
*/
|
||||||
|
$bus->dispatch($envelope);
|
||||||
|
// handle the message, but with no retries
|
||||||
|
$runWorker('transport1', 0);
|
||||||
|
// now make the handler work!
|
||||||
|
$transport1HandlerThatFails->setShouldThrow(false);
|
||||||
|
$runWorker('the_failure_transport', 1);
|
||||||
|
// the failure transport is empty because it worked
|
||||||
|
$this->assertEmpty($failureTransport->getMessagesWaitingToBeReceived());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyFailureTestSenderAndReceiver implements ReceiverInterface, SenderInterface
|
||||||
|
{
|
||||||
|
private $messagesWaiting = [];
|
||||||
|
|
||||||
|
public function get(): iterable
|
||||||
|
{
|
||||||
|
$message = array_shift($this->messagesWaiting);
|
||||||
|
|
||||||
|
if (null === $message) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$message];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ack(Envelope $envelope): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reject(Envelope $envelope): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(Envelope $envelope): Envelope
|
||||||
|
{
|
||||||
|
$this->messagesWaiting[] = $envelope;
|
||||||
|
|
||||||
|
return $envelope;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Envelope[]
|
||||||
|
*/
|
||||||
|
public function getMessagesWaitingToBeReceived(): array
|
||||||
|
{
|
||||||
|
return $this->messagesWaiting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyTestHandler
|
||||||
|
{
|
||||||
|
private $timesCalled = 0;
|
||||||
|
private $shouldThrow;
|
||||||
|
|
||||||
|
public function __construct(bool $shouldThrow)
|
||||||
|
{
|
||||||
|
$this->shouldThrow = $shouldThrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke()
|
||||||
|
{
|
||||||
|
++$this->timesCalled;
|
||||||
|
|
||||||
|
if ($this->shouldThrow) {
|
||||||
|
throw new \Exception('Failure from call '.$this->timesCalled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTimesCalled(): int
|
||||||
|
{
|
||||||
|
return $this->timesCalled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setShouldThrow(bool $shouldThrow)
|
||||||
|
{
|
||||||
|
$this->shouldThrow = $shouldThrow;
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,11 +92,7 @@ class SendMessageMiddlewareTest extends MiddlewareTestCase
|
||||||
|
|
||||||
$sendersLocator = $this->createSendersLocator(
|
$sendersLocator = $this->createSendersLocator(
|
||||||
[DummyMessage::class => ['foo', 'bar']],
|
[DummyMessage::class => ['foo', 'bar']],
|
||||||
['foo' => $sender, 'bar' => $sender2],
|
['foo' => $sender, 'bar' => $sender2]
|
||||||
[
|
|
||||||
// normally, this class sends and handles (but not on retry)
|
|
||||||
DummyMessage::class => true,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$middleware = new SendMessageMiddleware($sendersLocator);
|
$middleware = new SendMessageMiddleware($sendersLocator);
|
||||||
|
@ -126,68 +122,46 @@ class SendMessageMiddlewareTest extends MiddlewareTestCase
|
||||||
$middleware->handle($envelope, $this->getStackMock(false));
|
$middleware->handle($envelope, $this->getStackMock(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItAlsoCallsTheNextMiddlewareBasedOnTheMessageClass()
|
public function testItSendsTheMessageBasedOnTheMessageParentClass()
|
||||||
{
|
|
||||||
$message = new DummyMessage('Hey');
|
|
||||||
$envelope = new Envelope($message);
|
|
||||||
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
|
|
||||||
|
|
||||||
$sendersLocator = $this->createSendersLocator(['*' => ['foo_sender']], ['foo_sender' => $sender], [
|
|
||||||
DummyMessage::class => true,
|
|
||||||
]);
|
|
||||||
$middleware = new SendMessageMiddleware($sendersLocator);
|
|
||||||
|
|
||||||
$sender->expects($this->once())->method('send')->with($envelope->with(new SentStamp(\get_class($sender), 'foo_sender')))->willReturn($envelope);
|
|
||||||
|
|
||||||
$middleware->handle($envelope, $this->getStackMock());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItAlsoCallsTheNextMiddlewareBasedOnTheMessageParentClass()
|
|
||||||
{
|
{
|
||||||
$message = new ChildDummyMessage('Hey');
|
$message = new ChildDummyMessage('Hey');
|
||||||
$envelope = new Envelope($message);
|
$envelope = new Envelope($message);
|
||||||
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
|
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
|
||||||
|
|
||||||
$sendersLocator = $this->createSendersLocator(['*' => ['foo_sender']], ['foo_sender' => $sender], [
|
$sendersLocator = $this->createSendersLocator([DummyMessage::class => ['foo_sender']], ['foo_sender' => $sender]);
|
||||||
DummyMessage::class => true,
|
|
||||||
]);
|
|
||||||
$middleware = new SendMessageMiddleware($sendersLocator);
|
$middleware = new SendMessageMiddleware($sendersLocator);
|
||||||
|
|
||||||
$sender->expects($this->once())->method('send')->with($envelope->with(new SentStamp(\get_class($sender), 'foo_sender')))->willReturn($envelope);
|
$sender->expects($this->once())->method('send')->with($envelope->with(new SentStamp(\get_class($sender), 'foo_sender')))->willReturn($envelope);
|
||||||
|
|
||||||
$middleware->handle($envelope, $this->getStackMock());
|
$middleware->handle($envelope, $this->getStackMock(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItAlsoCallsTheNextMiddlewareBasedOnTheMessageInterface()
|
public function testItSendsTheMessageBasedOnTheMessageInterface()
|
||||||
{
|
{
|
||||||
$message = new DummyMessage('Hey');
|
$message = new DummyMessage('Hey');
|
||||||
$envelope = new Envelope($message);
|
$envelope = new Envelope($message);
|
||||||
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
|
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
|
||||||
|
|
||||||
$sendersLocator = $this->createSendersLocator(['*' => ['foo_sender']], ['foo_sender' => $sender], [
|
$sendersLocator = $this->createSendersLocator([DummyMessageInterface::class => ['foo_sender']], ['foo_sender' => $sender]);
|
||||||
DummyMessageInterface::class => true,
|
|
||||||
]);
|
|
||||||
$middleware = new SendMessageMiddleware($sendersLocator);
|
$middleware = new SendMessageMiddleware($sendersLocator);
|
||||||
|
|
||||||
$sender->expects($this->once())->method('send')->with($envelope->with(new SentStamp(\get_class($sender), 'foo_sender')))->willReturn($envelope);
|
$sender->expects($this->once())->method('send')->with($envelope->with(new SentStamp(\get_class($sender), 'foo_sender')))->willReturn($envelope);
|
||||||
|
|
||||||
$middleware->handle($envelope, $this->getStackMock());
|
$middleware->handle($envelope, $this->getStackMock(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItAlsoCallsTheNextMiddlewareBasedOnWildcard()
|
public function testItSendsTheMessageBasedOnWildcard()
|
||||||
{
|
{
|
||||||
$message = new DummyMessage('Hey');
|
$message = new DummyMessage('Hey');
|
||||||
$envelope = new Envelope($message);
|
$envelope = new Envelope($message);
|
||||||
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
|
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
|
||||||
|
|
||||||
$sendersLocator = $this->createSendersLocator(['*' => ['foo_sender']], ['foo_sender' => $sender], [
|
$sendersLocator = $this->createSendersLocator(['*' => ['foo_sender']], ['foo_sender' => $sender]);
|
||||||
'*' => true,
|
|
||||||
]);
|
|
||||||
$middleware = new SendMessageMiddleware($sendersLocator);
|
$middleware = new SendMessageMiddleware($sendersLocator);
|
||||||
|
|
||||||
$sender->expects($this->once())->method('send')->with($envelope->with(new SentStamp(\get_class($sender), 'foo_sender')))->willReturn($envelope);
|
$sender->expects($this->once())->method('send')->with($envelope->with(new SentStamp(\get_class($sender), 'foo_sender')))->willReturn($envelope);
|
||||||
|
|
||||||
$middleware->handle($envelope, $this->getStackMock());
|
$middleware->handle($envelope, $this->getStackMock(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItCallsTheNextMiddlewareWhenNoSenderForThisMessage()
|
public function testItCallsTheNextMiddlewareWhenNoSenderForThisMessage()
|
||||||
|
@ -267,7 +241,7 @@ class SendMessageMiddlewareTest extends MiddlewareTestCase
|
||||||
$middleware->handle($envelope, $this->getStackMock(false));
|
$middleware->handle($envelope, $this->getStackMock(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createSendersLocator(array $sendersMap, array $senders, array $sendAndHandle = [])
|
private function createSendersLocator(array $sendersMap, array $senders)
|
||||||
{
|
{
|
||||||
$container = $this->createMock(ContainerInterface::class);
|
$container = $this->createMock(ContainerInterface::class);
|
||||||
$container->expects($this->any())
|
$container->expects($this->any())
|
||||||
|
@ -281,6 +255,6 @@ class SendMessageMiddlewareTest extends MiddlewareTestCase
|
||||||
return $senders[$id];
|
return $senders[$id];
|
||||||
});
|
});
|
||||||
|
|
||||||
return new SendersLocator($sendersMap, $container, $sendAndHandle);
|
return new SendersLocator($sendersMap, $container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,8 @@ class RetryIntegrationTest extends TestCase
|
||||||
$senderLocator->method('get')->with('sender_alias')->willReturn($senderAndReceiver);
|
$senderLocator->method('get')->with('sender_alias')->willReturn($senderAndReceiver);
|
||||||
$senderLocator = new SendersLocator([DummyMessage::class => ['sender_alias']], $senderLocator);
|
$senderLocator = new SendersLocator([DummyMessage::class => ['sender_alias']], $senderLocator);
|
||||||
|
|
||||||
$handler = new DummyMessageHandlerFailingFirstTimes(0, 'A');
|
$handler = new DummyMessageHandlerFailingFirstTimes(0);
|
||||||
$throwingHandler = new DummyMessageHandlerFailingFirstTimes(1, 'B');
|
$throwingHandler = new DummyMessageHandlerFailingFirstTimes(1);
|
||||||
$handlerLocator = new HandlersLocator([
|
$handlerLocator = new HandlersLocator([
|
||||||
DummyMessage::class => [
|
DummyMessage::class => [
|
||||||
new HandlerDescriptor($handler, ['alias' => 'first']),
|
new HandlerDescriptor($handler, ['alias' => 'first']),
|
||||||
|
|
|
@ -50,41 +50,34 @@ class RoutableMessageBusTest extends TestCase
|
||||||
->willReturn($envelope);
|
->willReturn($envelope);
|
||||||
|
|
||||||
$container = $this->createMock(ContainerInterface::class);
|
$container = $this->createMock(ContainerInterface::class);
|
||||||
$container->expects($this->once())->method('has')->with(MessageBusInterface::class)
|
|
||||||
->willReturn(true);
|
|
||||||
$container->expects($this->once())->method('get')->with(MessageBusInterface::class)
|
|
||||||
->willReturn($defaultBus);
|
|
||||||
|
|
||||||
$routableBus = new RoutableMessageBus($container);
|
$routableBus = new RoutableMessageBus($container, $defaultBus);
|
||||||
|
|
||||||
$this->assertSame($envelope, $routableBus->dispatch($envelope, [$stamp]));
|
$this->assertSame($envelope, $routableBus->dispatch($envelope, [$stamp]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItExceptionOnDefaultBusNotFound()
|
|
||||||
{
|
|
||||||
$this->expectException(InvalidArgumentException::class);
|
|
||||||
$this->expectExceptionMessage(sprintf('Bus named "%s" does not exist.', MessageBusInterface::class));
|
|
||||||
|
|
||||||
$envelope = new Envelope(new \stdClass());
|
|
||||||
|
|
||||||
$container = $this->createMock(ContainerInterface::class);
|
|
||||||
$container->expects($this->once())->method('has')->with(MessageBusInterface::class)
|
|
||||||
->willReturn(false);
|
|
||||||
|
|
||||||
$routableBus = new RoutableMessageBus($container);
|
|
||||||
$routableBus->dispatch($envelope);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItExceptionOnBusNotFound()
|
public function testItExceptionOnBusNotFound()
|
||||||
{
|
{
|
||||||
$this->expectException(InvalidArgumentException::class);
|
$this->expectException(InvalidArgumentException::class);
|
||||||
$this->expectExceptionMessage(sprintf('Bus named "%s" does not exist.', 'foo_bus'));
|
$this->expectExceptionMessage('Bus named "my_cool_bus" does not exist.');
|
||||||
|
|
||||||
$envelope = new Envelope(new \stdClass(), [new BusNameStamp('foo_bus')]);
|
$envelope = new Envelope(new \stdClass(), [
|
||||||
|
new BusNameStamp('my_cool_bus'),
|
||||||
|
]);
|
||||||
|
|
||||||
$container = $this->createMock(ContainerInterface::class);
|
$container = $this->createMock(ContainerInterface::class);
|
||||||
$container->expects($this->once())->method('has')->willReturn(false);
|
$routableBus = new RoutableMessageBus($container);
|
||||||
|
$routableBus->dispatch($envelope);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItExceptionOnDefaultBusNotFound()
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('Envelope is missing a BusNameStamp and no fallback message bus is configured on RoutableMessageBus.');
|
||||||
|
|
||||||
|
$envelope = new Envelope(new \stdClass());
|
||||||
|
|
||||||
|
$container = $this->createMock(ContainerInterface::class);
|
||||||
$routableBus = new RoutableMessageBus($container);
|
$routableBus = new RoutableMessageBus($container);
|
||||||
$routableBus->dispatch($envelope);
|
$routableBus->dispatch($envelope);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
namespace Symfony\Component\Messenger\Tests\Stamp;
|
namespace Symfony\Component\Messenger\Tests\Stamp;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Debug\Exception\FlattenException;
|
||||||
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
|
||||||
|
|
||||||
class RedeliveryStampTest extends TestCase
|
class RedeliveryStampTest extends TestCase
|
||||||
|
@ -21,5 +22,16 @@ class RedeliveryStampTest extends TestCase
|
||||||
$stamp = new RedeliveryStamp(10, 'sender_alias');
|
$stamp = new RedeliveryStamp(10, 'sender_alias');
|
||||||
$this->assertSame(10, $stamp->getRetryCount());
|
$this->assertSame(10, $stamp->getRetryCount());
|
||||||
$this->assertSame('sender_alias', $stamp->getSenderClassOrAlias());
|
$this->assertSame('sender_alias', $stamp->getSenderClassOrAlias());
|
||||||
|
$this->assertInstanceOf(\DateTimeInterface::class, $stamp->getRedeliveredAt());
|
||||||
|
$this->assertNull($stamp->getExceptionMessage());
|
||||||
|
$this->assertNull($stamp->getFlattenException());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGettersPopulated()
|
||||||
|
{
|
||||||
|
$flattenException = new FlattenException();
|
||||||
|
$stamp = new RedeliveryStamp(10, 'sender_alias', 'exception message', $flattenException);
|
||||||
|
$this->assertSame('exception message', $stamp->getExceptionMessage());
|
||||||
|
$this->assertSame($flattenException, $stamp->getFlattenException());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,22 +12,13 @@
|
||||||
namespace Symfony\Component\Messenger\Tests\Stamp;
|
namespace Symfony\Component\Messenger\Tests\Stamp;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
|
||||||
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
use Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp;
|
||||||
|
|
||||||
class SentToFailureTransportStampTest extends TestCase
|
class SentToFailureTransportStampTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testGetters()
|
public function testGetOriginalReceiverName()
|
||||||
{
|
{
|
||||||
$flattenException = new FlattenException();
|
$stamp = new SentToFailureTransportStamp('original_receiver');
|
||||||
$stamp = new SentToFailureTransportStamp(
|
|
||||||
'exception message',
|
|
||||||
'original_receiver',
|
|
||||||
$flattenException
|
|
||||||
);
|
|
||||||
$this->assertSame('exception message', $stamp->getExceptionMessage());
|
|
||||||
$this->assertSame('original_receiver', $stamp->getOriginalReceiverName());
|
$this->assertSame('original_receiver', $stamp->getOriginalReceiverName());
|
||||||
$this->assertSame($flattenException, $stamp->getFlattenException());
|
|
||||||
$this->assertInstanceOf(\DateTimeInterface::class, $stamp->getSentAt());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,11 +37,8 @@ class DoctrineIntegrationTest extends TestCase
|
||||||
*/
|
*/
|
||||||
public function createConnection()
|
public function createConnection()
|
||||||
{
|
{
|
||||||
if ($dsn = getenv('MESSENGER_DOCTRINE_DSN')) {
|
$dsn = getenv('MESSENGER_DOCTRINE_DSN') ?: 'sqlite:///'.sys_get_temp_dir().'/symfony.messenger.sqlite';
|
||||||
$this->driverConnection = DriverManager::getConnection(['url' => $dsn]);
|
$this->driverConnection = DriverManager::getConnection(['url' => $dsn]);
|
||||||
} else {
|
|
||||||
$this->driverConnection = DriverManager::getConnection(['pdo' => new \PDO('sqlite:'.sys_get_temp_dir().'/symfony.messenger.sqlite')]);
|
|
||||||
}
|
|
||||||
$this->connection = new Connection([], $this->driverConnection);
|
$this->connection = new Connection([], $this->driverConnection);
|
||||||
// call send to auto-setup the table
|
// call send to auto-setup the table
|
||||||
$this->connection->setup();
|
$this->connection->setup();
|
||||||
|
|
|
@ -30,14 +30,12 @@ class SendersLocator implements SendersLocatorInterface
|
||||||
private $sendersMap;
|
private $sendersMap;
|
||||||
private $sendersLocator;
|
private $sendersLocator;
|
||||||
private $useLegacyLookup = false;
|
private $useLegacyLookup = false;
|
||||||
private $sendAndHandle;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[][] $sendersMap An array, keyed by "type", set to an array of sender aliases
|
* @param string[][] $sendersMap An array, keyed by "type", set to an array of sender aliases
|
||||||
* @param ContainerInterface $sendersLocator Locator of senders, keyed by sender alias
|
* @param ContainerInterface $sendersLocator Locator of senders, keyed by sender alias
|
||||||
* @param bool[] $sendAndHandle
|
|
||||||
*/
|
*/
|
||||||
public function __construct(array $sendersMap, /*ContainerInterface*/ $sendersLocator = null, array $sendAndHandle = [])
|
public function __construct(array $sendersMap, /*ContainerInterface*/ $sendersLocator = null)
|
||||||
{
|
{
|
||||||
$this->sendersMap = $sendersMap;
|
$this->sendersMap = $sendersMap;
|
||||||
|
|
||||||
|
@ -45,21 +43,17 @@ class SendersLocator implements SendersLocatorInterface
|
||||||
@trigger_error(sprintf('"%s::__construct()" requires a "%s" as 2nd argument. Not doing so is deprecated since Symfony 4.3 and will be required in 5.0.', __CLASS__, ContainerInterface::class), E_USER_DEPRECATED);
|
@trigger_error(sprintf('"%s::__construct()" requires a "%s" as 2nd argument. Not doing so is deprecated since Symfony 4.3 and will be required in 5.0.', __CLASS__, ContainerInterface::class), E_USER_DEPRECATED);
|
||||||
// "%s" requires a "%s" as 2nd argument. Not doing so is deprecated since Symfony 4.3 and will be required in 5.0.'
|
// "%s" requires a "%s" as 2nd argument. Not doing so is deprecated since Symfony 4.3 and will be required in 5.0.'
|
||||||
$this->sendersLocator = new ServiceLocator([]);
|
$this->sendersLocator = new ServiceLocator([]);
|
||||||
$this->sendAndHandle = $sendersLocator;
|
|
||||||
$this->useLegacyLookup = true;
|
$this->useLegacyLookup = true;
|
||||||
} else {
|
} else {
|
||||||
$this->sendersLocator = $sendersLocator;
|
$this->sendersLocator = $sendersLocator;
|
||||||
$this->sendAndHandle = $sendAndHandle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getSenders(Envelope $envelope, ?bool &$handle = false): iterable
|
public function getSenders(Envelope $envelope): iterable
|
||||||
{
|
{
|
||||||
$handle = false;
|
|
||||||
$sender = null;
|
|
||||||
$seen = [];
|
$seen = [];
|
||||||
|
|
||||||
foreach (HandlersLocator::listTypes($envelope) as $type) {
|
foreach (HandlersLocator::listTypes($envelope) as $type) {
|
||||||
|
@ -71,8 +65,6 @@ class SendersLocator implements SendersLocatorInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$handle = $handle ?: $this->sendAndHandle[$type] ?? false;
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,11 +79,7 @@ class SendersLocator implements SendersLocatorInterface
|
||||||
yield $senderAlias => $sender;
|
yield $senderAlias => $sender;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$handle = $handle ?: $this->sendAndHandle[$type] ?? false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$handle = $handle || null === $sender;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSenderByAlias(string $alias): SenderInterface
|
public function getSenderByAlias(string $alias): SenderInterface
|
||||||
|
|
|
@ -27,12 +27,9 @@ interface SendersLocatorInterface
|
||||||
/**
|
/**
|
||||||
* Gets the senders for the given message name.
|
* Gets the senders for the given message name.
|
||||||
*
|
*
|
||||||
* @param bool|null &$handle True after calling the method when the next middleware
|
|
||||||
* should also get the message; false otherwise
|
|
||||||
*
|
|
||||||
* @return iterable|SenderInterface[] Indexed by sender alias if available
|
* @return iterable|SenderInterface[] Indexed by sender alias if available
|
||||||
*/
|
*/
|
||||||
public function getSenders(Envelope $envelope, ?bool &$handle = false): iterable;
|
public function getSenders(Envelope $envelope): iterable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a specific sender by its alias.
|
* Returns a specific sender by its alias.
|
||||||
|
|
|
@ -555,8 +555,12 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
protected function createChildContext(array $parentContext, $attribute/*, string $format = null */)
|
protected function createChildContext(array $parentContext, $attribute/*, ?string $format */)
|
||||||
{
|
{
|
||||||
|
if (\func_num_args() < 3) {
|
||||||
|
@trigger_error(sprintf('Method "%s::%s()" will have a third "?string $format" argument in version 5.0; not defining it is deprecated since Symfony 4.3.', \get_class($this), __FUNCTION__), E_USER_DEPRECATED);
|
||||||
|
$format = null;
|
||||||
|
}
|
||||||
if (isset($parentContext[self::ATTRIBUTES][$attribute])) {
|
if (isset($parentContext[self::ATTRIBUTES][$attribute])) {
|
||||||
$parentContext[self::ATTRIBUTES] = $parentContext[self::ATTRIBUTES][$attribute];
|
$parentContext[self::ATTRIBUTES] = $parentContext[self::ATTRIBUTES][$attribute];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -41,7 +41,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
||||||
/**
|
/**
|
||||||
* How to track the current depth in the context.
|
* How to track the current depth in the context.
|
||||||
*/
|
*/
|
||||||
private const DEPTH_KEY_PATTERN = 'depth_%s::%s';
|
public const DEPTH_KEY_PATTERN = 'depth_%s::%s';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* While denormalizing, we can verify that types match.
|
* While denormalizing, we can verify that types match.
|
||||||
|
@ -559,18 +559,19 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
||||||
* We must not mix up the attribute cache between parent and children.
|
* We must not mix up the attribute cache between parent and children.
|
||||||
*
|
*
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @param string|null $format
|
||||||
*/
|
*/
|
||||||
protected function createChildContext(array $parentContext, $attribute/*, string $format = null */)
|
protected function createChildContext(array $parentContext, $attribute/*, ?string $format */)
|
||||||
{
|
{
|
||||||
if (\func_num_args() >= 3) {
|
if (\func_num_args() >= 3) {
|
||||||
$format = \func_get_arg(2);
|
$format = \func_get_arg(2);
|
||||||
} else {
|
} else {
|
||||||
// will be deprecated in version 4
|
@trigger_error(sprintf('Method "%s::%s()" will have a third "?string $format" argument in version 5.0; not defining it is deprecated since Symfony 4.3.', \get_class($this), __FUNCTION__), E_USER_DEPRECATED);
|
||||||
$format = null;
|
$format = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$context = parent::createChildContext($parentContext, $attribute, $format);
|
$context = parent::createChildContext($parentContext, $attribute, $format);
|
||||||
// format is already included in the cache_key of the parent.
|
|
||||||
$context['cache_key'] = $this->getCacheKey($format, $context);
|
$context['cache_key'] = $this->getCacheKey($format, $context);
|
||||||
|
|
||||||
return $context;
|
return $context;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Validator\Mapping\Loader;
|
namespace Symfony\Component\Validator\Mapping\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\Type as PropertyInfoType;
|
use Symfony\Component\PropertyInfo\Type as PropertyInfoType;
|
||||||
|
@ -29,12 +30,14 @@ final class PropertyInfoLoader implements LoaderInterface
|
||||||
{
|
{
|
||||||
private $listExtractor;
|
private $listExtractor;
|
||||||
private $typeExtractor;
|
private $typeExtractor;
|
||||||
|
private $accessExtractor;
|
||||||
private $classValidatorRegexp;
|
private $classValidatorRegexp;
|
||||||
|
|
||||||
public function __construct(PropertyListExtractorInterface $listExtractor, PropertyTypeExtractorInterface $typeExtractor, string $classValidatorRegexp = null)
|
public function __construct(PropertyListExtractorInterface $listExtractor, PropertyTypeExtractorInterface $typeExtractor, PropertyAccessExtractorInterface $accessExtractor, string $classValidatorRegexp = null)
|
||||||
{
|
{
|
||||||
$this->listExtractor = $listExtractor;
|
$this->listExtractor = $listExtractor;
|
||||||
$this->typeExtractor = $typeExtractor;
|
$this->typeExtractor = $typeExtractor;
|
||||||
|
$this->accessExtractor = $accessExtractor;
|
||||||
$this->classValidatorRegexp = $classValidatorRegexp;
|
$this->classValidatorRegexp = $classValidatorRegexp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +56,10 @@ final class PropertyInfoLoader implements LoaderInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($properties as $property) {
|
foreach ($properties as $property) {
|
||||||
|
if (false === $this->accessExtractor->isWritable($className, $property)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$types = $this->typeExtractor->getTypes($className, $property);
|
$types = $this->typeExtractor->getTypes($className, $property);
|
||||||
if (null === $types) {
|
if (null === $types) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -46,4 +46,6 @@ class PropertyInfoLoaderEntity
|
||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
public $alreadyPartiallyMappedCollection;
|
public $alreadyPartiallyMappedCollection;
|
||||||
|
|
||||||
|
public $readOnly;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ class PropertyInfoLoaderTest extends TestCase
|
||||||
'alreadyMappedNotNull',
|
'alreadyMappedNotNull',
|
||||||
'alreadyMappedNotBlank',
|
'alreadyMappedNotBlank',
|
||||||
'alreadyPartiallyMappedCollection',
|
'alreadyPartiallyMappedCollection',
|
||||||
|
'readOnly',
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
$propertyInfoStub
|
$propertyInfoStub
|
||||||
|
@ -58,11 +59,27 @@ class PropertyInfoLoaderTest extends TestCase
|
||||||
[new Type(Type::BUILTIN_TYPE_FLOAT, true)], // The existing constraint is float
|
[new Type(Type::BUILTIN_TYPE_FLOAT, true)], // The existing constraint is float
|
||||||
[new Type(Type::BUILTIN_TYPE_STRING, true)],
|
[new Type(Type::BUILTIN_TYPE_STRING, true)],
|
||||||
[new Type(Type::BUILTIN_TYPE_STRING, true)],
|
[new Type(Type::BUILTIN_TYPE_STRING, true)],
|
||||||
[new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, null, new Type(Type::BUILTIN_TYPE_FLOAT))]
|
[new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, null, new Type(Type::BUILTIN_TYPE_FLOAT))],
|
||||||
|
[new Type(Type::BUILTIN_TYPE_STRING)]
|
||||||
|
))
|
||||||
|
;
|
||||||
|
$propertyInfoStub
|
||||||
|
->method('isWritable')
|
||||||
|
->will($this->onConsecutiveCalls(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false
|
||||||
))
|
))
|
||||||
;
|
;
|
||||||
|
|
||||||
$propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub);
|
$propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub, $propertyInfoStub);
|
||||||
|
|
||||||
$validator = Validation::createValidatorBuilder()
|
$validator = Validation::createValidatorBuilder()
|
||||||
->enableAnnotationMapping()
|
->enableAnnotationMapping()
|
||||||
|
@ -137,6 +154,9 @@ class PropertyInfoLoaderTest extends TestCase
|
||||||
$this->assertSame('string', $alreadyPartiallyMappedCollectionConstraints[0]->constraints[0]->type);
|
$this->assertSame('string', $alreadyPartiallyMappedCollectionConstraints[0]->constraints[0]->type);
|
||||||
$this->assertInstanceOf(Iban::class, $alreadyPartiallyMappedCollectionConstraints[0]->constraints[1]);
|
$this->assertInstanceOf(Iban::class, $alreadyPartiallyMappedCollectionConstraints[0]->constraints[1]);
|
||||||
$this->assertInstanceOf(NotNull::class, $alreadyPartiallyMappedCollectionConstraints[0]->constraints[2]);
|
$this->assertInstanceOf(NotNull::class, $alreadyPartiallyMappedCollectionConstraints[0]->constraints[2]);
|
||||||
|
|
||||||
|
$readOnlyMetadata = $classMetadata->getPropertyMetadata('readOnly');
|
||||||
|
$this->assertEmpty($readOnlyMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,7 +174,7 @@ class PropertyInfoLoaderTest extends TestCase
|
||||||
->willReturn([new Type(Type::BUILTIN_TYPE_STRING)])
|
->willReturn([new Type(Type::BUILTIN_TYPE_STRING)])
|
||||||
;
|
;
|
||||||
|
|
||||||
$propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub, $classValidatorRegexp);
|
$propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub, $propertyInfoStub, $classValidatorRegexp);
|
||||||
|
|
||||||
$classMetadata = new ClassMetadata(PropertyInfoLoaderEntity::class);
|
$classMetadata = new ClassMetadata(PropertyInfoLoaderEntity::class);
|
||||||
$this->assertSame($expected, $propertyInfoLoader->loadClassMetadata($classMetadata));
|
$this->assertSame($expected, $propertyInfoLoader->loadClassMetadata($classMetadata));
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
namespace Symfony\Component\Workflow;
|
namespace Symfony\Component\Workflow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To learn more about how workflow events work check the documentation
|
* To learn more about how workflow events work, check the documentation
|
||||||
* entry at {@link https://symfony.com/doc/current/workflow/usage.html#using-events}.
|
* entry at {@link https://symfony.com/doc/current/workflow/usage.html#using-events}.
|
||||||
*/
|
*/
|
||||||
final class WorkflowEvents
|
final class WorkflowEvents
|
||||||
|
@ -43,7 +43,7 @@ final class WorkflowEvents
|
||||||
const ENTERED = 'workflow.entered';
|
const ENTERED = 'workflow.entered';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Event("Symfony\Component\Workflow\Event\EnteredEvent")
|
* @Event("Symfony\Component\Workflow\Event\LeaveEvent")
|
||||||
*/
|
*/
|
||||||
const LEAVE = 'workflow.leave';
|
const LEAVE = 'workflow.leave';
|
||||||
|
|
||||||
|
|
Reference in New Issue