Merge branch '4.3'

* 4.3: (22 commits)
  [Messenger] Fix incorrect error when symfony/serializer is missing
  Allow WrappedListener to describe uncallable listeners.
  [HttpClient] fix handling exceptions thrown before first mock chunk
  [Filesystem] fix wrong method call casing
  [HttpClient] fix test
  [Translation] Fixed issue with new vs old TranslatorInterface in TranslationDataCollector
  Don't reference symfony/security
  [HttpClient] display proper error message on TransportException when curl is used
  [FrameworkBundle] fix named autowiring aliases for TagAwareCacheInterface
  [Cache] improve logged messages
  [FrameworkBundle] improve cs
  [Mime][HttpFoundation] Added mime type audio/x-hx-aac-adts
  bumped Symfony version to 4.3.0
  updated VERSION for 4.3.0-BETA2
  updated CHANGELOG for 4.3.0-BETA2
  [HttpClient] Only use CURLMOPT_MAX_HOST_CONNECTIONS & CURL_VERSION_HTTP2 if defined
  [Security] fixed a fatal error when upgrading from 4.2
  [HttpClient] Allow arrays as query parameters
  Throws UnrecoverableMessageHandlingException when passed invalid entity manager name for Doctrine middlewares
  [Messenger] Make redis Connection::get() non blocking by default
  ...
This commit is contained in:
Fabien Potencier 2019-05-26 11:07:14 +02:00
commit 5dba412d30
47 changed files with 368 additions and 98 deletions

View File

@ -7,6 +7,51 @@ in 4.3 minor versions.
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.3.0...v4.3.1 To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.3.0...v4.3.1
* 4.3.0-BETA2 (2019-05-22)
* bug #31569 [HttpClient] Only use CURLMOPT_MAX_HOST_CONNECTIONS & CURL_VERSION_HTTP2 if defined (GawainLynch)
* bug #31566 [Security] fixed a fatal error when upgrading from 4.2 (fabpot)
* bug #31219 [HttpClient] Allow arrays as query parameters (sleepyboy)
* bug #31482 [Messenger][DoctrineBridge] Throws UnrecoverableMessageHandlingException when passed invalid entity manager name (Koc)
* feature #31471 [Messenger] Add "non sendable" stamps (weaverryan)
* bug #31545 [Messenger] Fix redis Connection::get() should be non blocking by default (chalasr)
* bug #31537 [Workflow] use method marking store (noniagriconomie)
* bug #31551 [ProxyManager] isProxyCandidate() does not take into account interfaces (andrerom)
* bug #31542 [HttpClient] add missing argument check (nicolas-grekas)
* bug #31544 [Messenger] Fix undefined index on read timeout (chalasr)
* bug #31335 [Doctrine] Respect parent class contract in ContainerAwareEventManager (Koc)
* bug #31421 [Routing][AnnotationClassLoader] fix utf-8 encoding in default route name (przemyslaw-bogusz)
* bug #31493 [EventDispatcher] Removed "callable" type hint from WrappedListener constructor (wskorodecki)
* bug #31502 [WebProfilerBundle][Form] The form data collector return serialized data (Simperfit)
* bug #31510 Use the current working dir as default first arg in 'link' binary (lyrixx)
* bug #31524 [HttpFoundation] prevent deprecation when filesize matches error code (xabbuh)
* bug #31535 [Debug] Wrap call to require_once in a try/catch (lyrixx)
* feature #31030 [Serializer] Deprecate calling createChildContext without the format parameter (dbu)
* bug #31459 Fix the interface incompatibility of EventDispatchers (keulinho)
* bug #31463 [DI] default to service id - *not* FQCN - when building tagged locators (nicolas-grekas)
* feature #31294 [Form] Add intl/choice_translation_locale option to TimezoneType (ro0NL)
* feature #31452 [FrameworkBundle] Add cache configuration for PropertyInfo (alanpoulain)
* feature #31486 [Doctrine][PropertyInfo] Detect if the ID is writeable (dunglas)
* bug #31481 [Validator] Autovalidation: skip readonly props (dunglas)
* bug #31480 Update dependencies in the main component (DavidPrevot)
* bug #31477 [PropertyAccess] Add missing property to PropertyAccessor (vudaltsov)
* bug #31479 [Cache] fix saving unrelated keys in recursive callback calls (nicolas-grekas)
* bug #30930 [FrameworkBundle] Fixed issue when a parameter contains a '' (lyrixx)
* bug #31438 [Serializer] Fix denormalization of object with variadic constructor typed argument (ajgarlag)
* bug #31445 [Messenger] Making cache rebuild correctly when message subscribers change (weaverryan)
* bug #31442 [Validator] Fix finding translator parent definition in compiler pass (deguif)
* bug #31475 [HttpFoundation] Allow set 'None' on samesite cookie flag (markitosgv)
* feature #31454 [Messenger] remove send_and_handle which can be achieved with SyncTransport (Tobion)
* bug #31425 [Messenger] On failure retry, make message appear received from original sender (weaverryan)
* bug #31472 [Messenger] Fix routable message bus default bus (weaverryan)
* bug #31465 [Serializer] Fix BC break: DEPTH_KEY_PATTERN must be public (dunglas)
* bug #31458 [TwigBundle] Fix Mailer integration in Twig (fabpot)
* bug #31456 Remove deprecated usage of some Twig features (fabpot)
* bug #31207 [Routing] Fixed unexpected 404 NoConfigurationException (yceruto)
* bug #31261 [Console] Commands with an alias should not be recognized as ambiguous when using register (Simperfit)
* bug #31371 [DI] Removes number of elements information in debug mode (jschaedl)
* bug #31418 [FrameworkBundle] clarify the possible class/interface of the cache (xabbuh)
* 4.3.0-BETA1 (2019-05-09) * 4.3.0-BETA1 (2019-05-09)
* feature #31249 [Translator] Set sources when extracting strings from php files (Stadly) * feature #31249 [Translator] Set sources when extracting strings from php files (Stadly)

View File

@ -12,8 +12,8 @@
namespace Symfony\Bridge\Doctrine\Messenger; namespace Symfony\Bridge\Doctrine\Messenger;
use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface; use Symfony\Component\Messenger\Middleware\StackInterface;
@ -40,10 +40,10 @@ class DoctrineCloseConnectionMiddleware implements MiddlewareInterface
*/ */
public function handle(Envelope $envelope, StackInterface $stack): Envelope public function handle(Envelope $envelope, StackInterface $stack): Envelope
{ {
$entityManager = $this->managerRegistry->getManager($this->entityManagerName); try {
$entityManager = $this->managerRegistry->getManager($this->entityManagerName);
if (!$entityManager instanceof EntityManagerInterface) { } catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException(sprintf('The ObjectManager with name "%s" must be an instance of EntityManagerInterface', $this->entityManagerName)); throw new UnrecoverableMessageHandlingException($e->getMessage(), 0, $e);
} }
try { try {

View File

@ -12,8 +12,8 @@
namespace Symfony\Bridge\Doctrine\Messenger; namespace Symfony\Bridge\Doctrine\Messenger;
use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface; use Symfony\Component\Messenger\Middleware\StackInterface;
@ -40,10 +40,10 @@ class DoctrinePingConnectionMiddleware implements MiddlewareInterface
*/ */
public function handle(Envelope $envelope, StackInterface $stack): Envelope public function handle(Envelope $envelope, StackInterface $stack): Envelope
{ {
$entityManager = $this->managerRegistry->getManager($this->entityManagerName); try {
$entityManager = $this->managerRegistry->getManager($this->entityManagerName);
if (!$entityManager instanceof EntityManagerInterface) { } catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException(sprintf('The ObjectManager with name "%s" must be an instance of EntityManagerInterface', $this->entityManagerName)); throw new UnrecoverableMessageHandlingException($e->getMessage(), 0, $e);
} }
$connection = $entityManager->getConnection(); $connection = $entityManager->getConnection();

View File

@ -12,8 +12,8 @@
namespace Symfony\Bridge\Doctrine\Messenger; namespace Symfony\Bridge\Doctrine\Messenger;
use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface; use Symfony\Component\Messenger\Middleware\StackInterface;
@ -40,10 +40,10 @@ class DoctrineTransactionMiddleware implements MiddlewareInterface
*/ */
public function handle(Envelope $envelope, StackInterface $stack): Envelope public function handle(Envelope $envelope, StackInterface $stack): Envelope
{ {
$entityManager = $this->managerRegistry->getManager($this->entityManagerName); try {
$entityManager = $this->managerRegistry->getManager($this->entityManagerName);
if (!$entityManager instanceof EntityManagerInterface) { } catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException(sprintf('The ObjectManager with name "%s" must be an instance of EntityManagerInterface', $this->entityManagerName)); throw new UnrecoverableMessageHandlingException($e->getMessage(), 0, $e);
} }
$entityManager->getConnection()->beginTransaction(); $entityManager->getConnection()->beginTransaction();

View File

@ -16,6 +16,7 @@ use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Messenger\DoctrineCloseConnectionMiddleware; use Symfony\Bridge\Doctrine\Messenger\DoctrineCloseConnectionMiddleware;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase; use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase;
class DoctrineCloseConnectionMiddlewareTest extends MiddlewareTestCase class DoctrineCloseConnectionMiddlewareTest extends MiddlewareTestCase
@ -50,4 +51,19 @@ class DoctrineCloseConnectionMiddlewareTest extends MiddlewareTestCase
$this->middleware->handle(new Envelope(new \stdClass()), $this->getStackMock()); $this->middleware->handle(new Envelope(new \stdClass()), $this->getStackMock());
} }
public function testInvalidEntityManagerThrowsException()
{
$managerRegistry = $this->createMock(ManagerRegistry::class);
$managerRegistry
->method('getManager')
->with('unknown_manager')
->will($this->throwException(new \InvalidArgumentException()));
$middleware = new DoctrineCloseConnectionMiddleware($managerRegistry, 'unknown_manager');
$this->expectException(UnrecoverableMessageHandlingException::class);
$middleware->handle(new Envelope(new \stdClass()), $this->getStackMock(false));
}
} }

View File

@ -16,6 +16,7 @@ use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Messenger\DoctrinePingConnectionMiddleware; use Symfony\Bridge\Doctrine\Messenger\DoctrinePingConnectionMiddleware;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase; use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase;
class DoctrinePingConnectionMiddlewareTest extends MiddlewareTestCase class DoctrinePingConnectionMiddlewareTest extends MiddlewareTestCase
@ -71,4 +72,19 @@ class DoctrinePingConnectionMiddlewareTest extends MiddlewareTestCase
$this->middleware->handle(new Envelope(new \stdClass()), $this->getStackMock()); $this->middleware->handle(new Envelope(new \stdClass()), $this->getStackMock());
} }
public function testInvalidEntityManagerThrowsException()
{
$managerRegistry = $this->createMock(ManagerRegistry::class);
$managerRegistry
->method('getManager')
->with('unknown_manager')
->will($this->throwException(new \InvalidArgumentException()));
$middleware = new DoctrinePingConnectionMiddleware($managerRegistry, 'unknown_manager');
$this->expectException(UnrecoverableMessageHandlingException::class);
$middleware->handle(new Envelope(new \stdClass()), $this->getStackMock(false));
}
} }

View File

@ -16,6 +16,7 @@ use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware; use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase; use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase;
class DoctrineTransactionMiddlewareTest extends MiddlewareTestCase class DoctrineTransactionMiddlewareTest extends MiddlewareTestCase
@ -67,4 +68,19 @@ class DoctrineTransactionMiddlewareTest extends MiddlewareTestCase
$this->middleware->handle(new Envelope(new \stdClass()), $this->getThrowingStackMock()); $this->middleware->handle(new Envelope(new \stdClass()), $this->getThrowingStackMock());
} }
public function testInvalidEntityManagerThrowsException()
{
$managerRegistry = $this->createMock(ManagerRegistry::class);
$managerRegistry
->method('getManager')
->with('unknown_manager')
->will($this->throwException(new \InvalidArgumentException()));
$middleware = new DoctrineTransactionMiddleware($managerRegistry, 'unknown_manager');
$this->expectException(UnrecoverableMessageHandlingException::class);
$middleware->handle(new Envelope(new \stdClass()), $this->getStackMock(false));
}
} }

View File

@ -29,11 +29,11 @@
"symfony/dependency-injection": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0",
"symfony/form": "~4.3", "symfony/form": "~4.3",
"symfony/http-kernel": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0",
"symfony/messenger": "~4.2", "symfony/messenger": "~4.3",
"symfony/property-access": "~3.4|~4.0", "symfony/property-access": "~3.4|~4.0",
"symfony/property-info": "~3.4|~4.0", "symfony/property-info": "~3.4|~4.0",
"symfony/proxy-manager-bridge": "~3.4|~4.0", "symfony/proxy-manager-bridge": "~3.4|~4.0",
"symfony/security": "~3.4|~4.0", "symfony/security-core": "~3.4|~4.0",
"symfony/expression-language": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0",
"symfony/validator": "~3.4|~4.0", "symfony/validator": "~3.4|~4.0",
"symfony/translation": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0",
@ -49,7 +49,7 @@
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
"symfony/dependency-injection": "<3.4", "symfony/dependency-injection": "<3.4",
"symfony/form": "<4.3", "symfony/form": "<4.3",
"symfony/messenger": "<4.2" "symfony/messenger": "<4.3"
}, },
"suggest": { "suggest": {
"symfony/form": "", "symfony/form": "",

View File

@ -34,8 +34,9 @@
"symfony/templating": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0",
"symfony/translation": "~4.2", "symfony/translation": "~4.2",
"symfony/yaml": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0",
"symfony/security": "~3.4|~4.0",
"symfony/security-acl": "~2.8|~3.0", "symfony/security-acl": "~2.8|~3.0",
"symfony/security-csrf": "~3.4|~4.0",
"symfony/security-http": "~3.4|~4.0",
"symfony/stopwatch": "~3.4|~4.0", "symfony/stopwatch": "~3.4|~4.0",
"symfony/console": "~3.4|~4.0", "symfony/console": "~3.4|~4.0",
"symfony/var-dumper": "~3.4|~4.0", "symfony/var-dumper": "~3.4|~4.0",
@ -59,7 +60,9 @@
"symfony/templating": "For using the TwigEngine", "symfony/templating": "For using the TwigEngine",
"symfony/translation": "For using the TranslationExtension", "symfony/translation": "For using the TranslationExtension",
"symfony/yaml": "For using the YamlExtension", "symfony/yaml": "For using the YamlExtension",
"symfony/security": "For using the SecurityExtension", "symfony/security-core": "For using the SecurityExtension",
"symfony/security-csrf": "For using the CsrfExtension",
"symfony/security-http": "For using the LogoutUrlExtension",
"symfony/stopwatch": "For using the StopwatchExtension", "symfony/stopwatch": "For using the StopwatchExtension",
"symfony/var-dumper": "For using the DumpExtension", "symfony/var-dumper": "For using the DumpExtension",
"symfony/expression-language": "For using the ExpressionExtension", "symfony/expression-language": "For using the ExpressionExtension",

View File

@ -117,6 +117,7 @@ use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand; use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\Service\ResetInterface; use Symfony\Contracts\Service\ResetInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface;
@ -737,7 +738,7 @@ class FrameworkExtension extends Extension
} }
if (!class_exists(Security::class)) { if (!class_exists(Security::class)) {
throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security".'); throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security-core".');
} }
$guard = new Definition(Workflow\EventListener\GuardListener::class); $guard = new Definition(Workflow\EventListener\GuardListener::class);
@ -1819,13 +1820,9 @@ class FrameworkExtension extends Extension
$pool['adapter'] = '.'.$pool['adapter'].'.inner'; $pool['adapter'] = '.'.$pool['adapter'].'.inner';
} }
$definition = new ChildDefinition($pool['adapter']); $definition = new ChildDefinition($pool['adapter']);
if (!\in_array($name, ['cache.app', 'cache.system'], true)) {
$container->registerAliasForArgument($name, CacheInterface::class);
$container->registerAliasForArgument($name, CacheItemPoolInterface::class);
}
if ($pool['tags']) { if ($pool['tags']) {
if ($config['pools'][$pool['tags']]['tags'] ?? false) { if (true !== $pool['tags'] && ($config['pools'][$pool['tags']]['tags'] ?? false)) {
$pool['tags'] = '.'.$pool['tags'].'.inner'; $pool['tags'] = '.'.$pool['tags'].'.inner';
} }
$container->register($name, TagAwareAdapter::class) $container->register($name, TagAwareAdapter::class)
@ -1837,7 +1834,21 @@ class FrameworkExtension extends Extension
$pool['name'] = $name; $pool['name'] = $name;
$pool['public'] = false; $pool['public'] = false;
$name = '.'.$name.'.inner'; $name = '.'.$name.'.inner';
if (!\in_array($pool['name'], ['cache.app', 'cache.system'], true)) {
$container->registerAliasForArgument($pool['name'], TagAwareCacheInterface::class);
$container->registerAliasForArgument($name, CacheInterface::class, $pool['name']);
$container->registerAliasForArgument($name, CacheItemPoolInterface::class, $pool['name']);
}
} elseif (!\in_array($name, ['cache.app', 'cache.system'], true)) {
$container->register('.'.$name.'.taggable', TagAwareAdapter::class)
->addArgument(new Reference($name))
;
$container->registerAliasForArgument('.'.$name.'.taggable', TagAwareCacheInterface::class, $name);
$container->registerAliasForArgument($name, CacheInterface::class);
$container->registerAliasForArgument($name, CacheItemPoolInterface::class);
} }
$definition->setPublic($pool['public']); $definition->setPublic($pool['public']);
unset($pool['adapter'], $pool['public'], $pool['tags']); unset($pool['adapter'], $pool['public'], $pool['tags']);

View File

@ -38,7 +38,6 @@
"symfony/css-selector": "~3.4|~4.0", "symfony/css-selector": "~3.4|~4.0",
"symfony/dom-crawler": "^4.3", "symfony/dom-crawler": "^4.3",
"symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-intl-icu": "~1.0",
"symfony/security": "~3.4|~4.0",
"symfony/form": "^4.3", "symfony/form": "^4.3",
"symfony/expression-language": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0",
"symfony/http-client": "^4.3", "symfony/http-client": "^4.3",
@ -46,8 +45,8 @@
"symfony/messenger": "^4.3", "symfony/messenger": "^4.3",
"symfony/mime": "^4.3", "symfony/mime": "^4.3",
"symfony/process": "~3.4|~4.0", "symfony/process": "~3.4|~4.0",
"symfony/security-core": "~3.4|~4.0",
"symfony/security-csrf": "~3.4|~4.0", "symfony/security-csrf": "~3.4|~4.0",
"symfony/security-http": "~3.4|~4.0",
"symfony/serializer": "^4.3", "symfony/serializer": "^4.3",
"symfony/stopwatch": "~3.4|~4.0", "symfony/stopwatch": "~3.4|~4.0",
"symfony/translation": "~4.2", "symfony/translation": "~4.2",

View File

@ -253,7 +253,8 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA
} }
} catch (\Exception $e) { } catch (\Exception $e) {
} }
CacheItem::log($this->logger, 'Failed to delete key "{key}"', ['key' => $key, 'exception' => $e]); $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
$ok = false; $ok = false;
} }

View File

@ -198,7 +198,7 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
$info = $host->info('Server'); $info = $host->info('Server');
$info = isset($info['Server']) ? $info['Server'] : $info; $info = isset($info['Server']) ? $info['Server'] : $info;
if (version_compare($info['redis_version'], '3.2', '<')) { if (version_compare($info['redis_version'], '3.2', '<')) {
CacheItem::log($this->logger, 'Redis server needs to be version 3.2 or higher, your Redis server was detected as {version}', ['version' => $info['redis_version']]); CacheItem::log($this->logger, 'Redis server needs to be version 3.2 or higher, your Redis server was detected as '.$info['redis_version']);
return $this->redisServerSupportSPOP = false; return $this->redisServerSupportSPOP = false;
} }

View File

@ -56,7 +56,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
return $value; return $value;
} }
} catch (\Exception $e) { } catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]); CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
} }
return $default; return $default;
@ -90,7 +90,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
try { try {
$values = $this->doFetch($ids); $values = $this->doFetch($ids);
} catch (\Exception $e) { } catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => $keys, 'exception' => $e]); CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]);
$values = []; $values = [];
} }
$ids = array_combine($ids, $keys); $ids = array_combine($ids, $keys);
@ -129,7 +129,8 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) { foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) {
$keys[] = substr($id, \strlen($this->namespace)); $keys[] = substr($id, \strlen($this->namespace));
} }
CacheItem::log($this->logger, 'Failed to save values', ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]); $message = 'Failed to save values'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]);
return false; return false;
} }
@ -175,7 +176,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
yield $key => $value; yield $key => $value;
} }
} catch (\Exception $e) { } catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => array_values($keys), 'exception' => $e]); CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]);
} }
foreach ($keys as $key) { foreach ($keys as $key) {

View File

@ -52,7 +52,7 @@ trait AbstractAdapterTrait
$isHit = true; $isHit = true;
} }
} catch (\Exception $e) { } catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]); CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
} }
return $f($key, $value, $isHit); return $f($key, $value, $isHit);
@ -74,7 +74,7 @@ trait AbstractAdapterTrait
try { try {
$items = $this->doFetch($ids); $items = $this->doFetch($ids);
} catch (\Exception $e) { } catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => $keys, 'exception' => $e]); CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]);
$items = []; $items = [];
} }
$ids = array_combine($ids, $keys); $ids = array_combine($ids, $keys);
@ -129,7 +129,7 @@ trait AbstractAdapterTrait
yield $key => $f($key, $value, true); yield $key => $f($key, $value, true);
} }
} catch (\Exception $e) { } catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => array_values($keys), 'exception' => $e]); CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]);
} }
foreach ($keys as $key) { foreach ($keys as $key) {

View File

@ -94,7 +94,7 @@ trait AbstractTrait
try { try {
return $this->doHave($id); return $this->doHave($id);
} catch (\Exception $e) { } catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached', ['key' => $key, 'exception' => $e]); CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
return false; return false;
} }
@ -122,7 +122,7 @@ trait AbstractTrait
try { try {
return $this->doClear($this->namespace) || $cleared; return $this->doClear($this->namespace) || $cleared;
} catch (\Exception $e) { } catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to clear the cache', ['exception' => $e]); CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e]);
return false; return false;
} }
@ -166,7 +166,8 @@ trait AbstractTrait
} }
} catch (\Exception $e) { } catch (\Exception $e) {
} }
CacheItem::log($this->logger, 'Failed to delete key "{key}"', ['key' => $key, 'exception' => $e]); $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
$ok = false; $ok = false;
} }

View File

@ -128,8 +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);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); $message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]); CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e]);
return; return;
} }
@ -151,7 +151,7 @@ trait ArrayTrait
try { try {
$value = unserialize($value); $value = unserialize($value);
} catch (\Exception $e) { } catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', ['key' => $key, 'exception' => $e]); CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
$value = false; $value = false;
} }
if (false === $value) { if (false === $value) {

View File

@ -41,7 +41,7 @@ class WrappedListener
public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
{ {
$this->listener = $listener; $this->listener = $listener;
$this->optimizedListener = $listener instanceof \Closure ? $listener : \Closure::fromCallable($listener); $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null);
$this->stopwatch = $stopwatch; $this->stopwatch = $stopwatch;
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->called = false; $this->called = false;
@ -123,7 +123,7 @@ class WrappedListener
$e = $this->stopwatch->start($this->name, 'event_listener'); $e = $this->stopwatch->start($this->name, 'event_listener');
($this->optimizedListener)($event, $eventName, $dispatcher); ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
if ($e->isStarted()) { if ($e->isStarted()) {
$e->stop(); $e->stop();

View File

@ -21,7 +21,7 @@ class WrappedListenerTest extends TestCase
/** /**
* @dataProvider provideListenersToDescribe * @dataProvider provideListenersToDescribe
*/ */
public function testListenerDescription(callable $listener, $expected) public function testListenerDescription($listener, $expected)
{ {
$wrappedListener = new WrappedListener($listener, null, $this->getMockBuilder(Stopwatch::class)->getMock(), $this->getMockBuilder(EventDispatcherInterface::class)->getMock()); $wrappedListener = new WrappedListener($listener, null, $this->getMockBuilder(Stopwatch::class)->getMock(), $this->getMockBuilder(EventDispatcherInterface::class)->getMock());
@ -34,6 +34,7 @@ class WrappedListenerTest extends TestCase
[new FooListener(), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::__invoke'], [new FooListener(), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::__invoke'],
[[new FooListener(), 'listen'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'], [[new FooListener(), 'listen'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'],
[['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'listenStatic'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listenStatic'], [['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'listenStatic'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listenStatic'],
[['Symfony\Component\EventDispatcher\Tests\Debug\FooListener', 'invalidMethod'], 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::invalidMethod'],
['var_dump', 'var_dump'], ['var_dump', 'var_dump'],
[function () {}, 'closure'], [function () {}, 'closure'],
[\Closure::fromCallable([new FooListener(), 'listen']), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'], [\Closure::fromCallable([new FooListener(), 'listen']), 'Symfony\Component\EventDispatcher\Tests\Debug\FooListener::listen'],

View File

@ -572,7 +572,7 @@ class Filesystem
$targetDirInfo = new \SplFileInfo($targetDir); $targetDirInfo = new \SplFileInfo($targetDir);
foreach ($iterator as $file) { foreach ($iterator as $file) {
if ($file->getPathName() === $targetDir || $file->getRealPath() === $targetDir || 0 === strpos($file->getRealPath(), $targetDirInfo->getRealPath())) { if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || 0 === strpos($file->getRealPath(), $targetDirInfo->getRealPath())) {
continue; continue;
} }

View File

@ -70,7 +70,9 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
if (\defined('CURLPIPE_MULTIPLEX')) { if (\defined('CURLPIPE_MULTIPLEX')) {
curl_multi_setopt($this->multi->handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); curl_multi_setopt($this->multi->handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
} }
curl_multi_setopt($this->multi->handle, CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX); if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
curl_multi_setopt($this->multi->handle, CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX);
}
// Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/bug.php?id=77535 // Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/bug.php?id=77535
if (0 >= $maxPendingPushes || \PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304)) { if (0 >= $maxPendingPushes || \PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304)) {
@ -188,7 +190,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; $curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
} elseif (1.1 === (float) $options['http_version'] || 'https:' !== $scheme) { } elseif (1.1 === (float) $options['http_version'] || 'https:' !== $scheme) {
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; $curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
} elseif (CURL_VERSION_HTTP2 & curl_version()['features']) { } elseif (\defined('CURL_VERSION_HTTP2') && CURL_VERSION_HTTP2 & curl_version()['features']) {
$curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; $curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
} }

View File

@ -27,7 +27,7 @@ trait HttpExceptionTrait
$this->response = $response; $this->response = $response;
$code = $response->getInfo('http_code'); $code = $response->getInfo('http_code');
$url = $response->getInfo('url'); $url = $response->getInfo('url');
$message = sprintf('HTTP %d returned for URL "%s".', $code, $url); $message = sprintf('HTTP %d returned for "%s".', $code, $url);
$httpCodeFound = false; $httpCodeFound = false;
$isJson = false; $isJson = false;
@ -37,7 +37,7 @@ trait HttpExceptionTrait
break; break;
} }
$message = sprintf('%s returned for URL "%s".', $h, $url); $message = sprintf('%s returned for "%s".', $h, $url);
$httpCodeFound = true; $httpCodeFound = true;
} }

View File

@ -481,17 +481,20 @@ trait HttpClientTrait
} }
} }
foreach ($queryArray as $k => $v) { if ($replace) {
if (is_scalar($v)) { foreach ($queryArray as $k => $v) {
$queryArray[$k] = rawurlencode($k).'='.rawurlencode($v); if (null === $v) {
} elseif (null === $v) {
unset($queryArray[$k]);
if ($replace) {
unset($query[$k]); unset($query[$k]);
} }
} else { }
throw new InvalidArgumentException(sprintf('Unsupported value for query parameter "%s": scalar or null expected, %s given.', $k, \gettype($v))); }
$queryString = http_build_query($queryArray, '', '&', PHP_QUERY_RFC3986);
$queryArray = [];
if ($queryString) {
foreach (explode('&', $queryString) as $v) {
$queryArray[rawurldecode(explode('=', $v, 2)[0])] = $v;
} }
} }

View File

@ -245,7 +245,7 @@ final class CurlResponse implements ResponseInterface
while ($info = curl_multi_info_read($multi->handle)) { while ($info = curl_multi_info_read($multi->handle)) {
$multi->handlesActivity[(int) $info['handle']][] = null; $multi->handlesActivity[(int) $info['handle']][] = null;
$multi->handlesActivity[(int) $info['handle']][] = \in_array($info['result'], [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || (\CURLE_WRITE_ERROR === $info['result'] && 'destruct' === @curl_getinfo($info['handle'], CURLINFO_PRIVATE)) ? null : new TransportException(curl_error($info['handle'])); $multi->handlesActivity[(int) $info['handle']][] = \in_array($info['result'], [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || (\CURLE_WRITE_ERROR === $info['result'] && 'destruct' === @curl_getinfo($info['handle'], CURLINFO_PRIVATE)) ? null : new TransportException(sprintf('%s for"%s".', curl_strerror($info['result']), curl_getinfo($info['handle'], CURLINFO_EFFECTIVE_URL)));
} }
} finally { } finally {
self::$performing = false; self::$performing = false;

View File

@ -25,7 +25,9 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
*/ */
class MockResponse implements ResponseInterface class MockResponse implements ResponseInterface
{ {
use ResponseTrait; use ResponseTrait {
doDestruct as public __destruct;
}
private $body; private $body;
private $requestOptions = []; private $requestOptions = [];
@ -162,8 +164,8 @@ class MockResponse implements ResponseInterface
$offset = 0; $offset = 0;
$chunk[1]->getStatusCode(); $chunk[1]->getStatusCode();
$response->headers = $chunk[1]->getHeaders(false); $response->headers = $chunk[1]->getHeaders(false);
$multi->handlesActivity[$id][] = new FirstChunk();
self::readResponse($response, $chunk[0], $chunk[1], $offset); self::readResponse($response, $chunk[0], $chunk[1], $offset);
$multi->handlesActivity[$id][] = new FirstChunk();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$multi->handlesActivity[$id][] = null; $multi->handlesActivity[$id][] = null;
$multi->handlesActivity[$id][] = $e; $multi->handlesActivity[$id][] = $e;

View File

@ -61,14 +61,14 @@ class CurlHttpClientTest extends HttpClientTestCase
$js->getHeaders(); $js->getHeaders();
$expected = [ $expected = [
'Request: GET https://http2-push.io/', 'Request: "GET https://http2-push.io/"',
'Queueing pushed response: https://http2-push.io/css/style.css', 'Queueing pushed response: "https://http2-push.io/css/style.css"',
'Queueing pushed response: https://http2-push.io/js/http2-push.js', 'Queueing pushed response: "https://http2-push.io/js/http2-push.js"',
'Response: 200 https://http2-push.io/', 'Response: "200 https://http2-push.io/"',
'Connecting request to pushed response: GET https://http2-push.io/css/style.css', 'Connecting request to pushed response: "GET https://http2-push.io/css/style.css"',
'Connecting request to pushed response: GET https://http2-push.io/js/http2-push.js', 'Connecting request to pushed response: "GET https://http2-push.io/js/http2-push.js"',
'Response: 200 https://http2-push.io/css/style.css', 'Response: "200 https://http2-push.io/css/style.css"',
'Response: 200 https://http2-push.io/js/http2-push.js', 'Response: "200 https://http2-push.io/js/http2-push.js"',
]; ];
$this->assertSame($expected, $logger->logs); $this->assertSame($expected, $logger->logs);
} }

View File

@ -141,6 +141,10 @@ class HttpClientTraitTest extends TestCase
yield [[null, null, 'bar', '?a=1&c=c', null], 'bar?a=a&b=b', ['b' => null, 'c' => 'c', 'a' => 1]]; yield [[null, null, 'bar', '?a=1&c=c', null], 'bar?a=a&b=b', ['b' => null, 'c' => 'c', 'a' => 1]];
yield [[null, null, 'bar', '?a=b+c&b=b', null], 'bar?a=b+c', ['b' => 'b']]; yield [[null, null, 'bar', '?a=b+c&b=b', null], 'bar?a=b+c', ['b' => 'b']];
yield [[null, null, 'bar', '?a=b%2B%20c', null], 'bar?a=b+c', ['a' => 'b+ c']]; yield [[null, null, 'bar', '?a=b%2B%20c', null], 'bar?a=b+c', ['a' => 'b+ c']];
yield [[null, null, 'bar', '?a%5Bb%5D=c', null], 'bar', ['a' => ['b' => 'c']]];
yield [[null, null, 'bar', '?a%5Bb%5Bc%5D=d', null], 'bar?a[b[c]=d', []];
yield [[null, null, 'bar', '?a%5Bb%5D%5Bc%5D=dd', null], 'bar?a[b][c]=d&e[f]=g', ['a' => ['b' => ['c' => 'dd']], 'e[f]' => null]];
yield [[null, null, 'bar', '?a=b&a%5Bb%20c%5D=d&e%3Df=%E2%9C%93', null], 'bar?a=b', ['a' => ['b c' => 'd'], 'e=f' => '✓']];
} }
/** /**

View File

@ -115,7 +115,7 @@ class MockHttpClientTest extends HttpClientTestCase
case 'testResolve': case 'testResolve':
$responses[] = new MockResponse($body, ['response_headers' => $headers]); $responses[] = new MockResponse($body, ['response_headers' => $headers]);
$responses[] = new MockResponse($body, ['response_headers' => $headers]); $responses[] = new MockResponse($body, ['response_headers' => $headers]);
$responses[] = $client->request('GET', 'http://symfony.com:8057/'); $responses[] = new MockResponse((function () { throw new \Exception('Fake connection timeout'); yield ''; })(), ['response_headers' => $headers]);
break; break;
case 'testTimeoutOnStream': case 'testTimeoutOnStream':

View File

@ -645,6 +645,7 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
'audio/x-aiff' => 'aif', 'audio/x-aiff' => 'aif',
'audio/x-caf' => 'caf', 'audio/x-caf' => 'caf',
'audio/x-flac' => 'flac', 'audio/x-flac' => 'flac',
'audio/x-hx-aac-adts' => 'aac',
'audio/x-matroska' => 'mka', 'audio/x-matroska' => 'mka',
'audio/x-mpegurl' => 'm3u', 'audio/x-mpegurl' => 'm3u',
'audio/x-ms-wax' => 'wax', 'audio/x-ms-wax' => 'wax',

View File

@ -17,6 +17,22 @@ use Symfony\Component\Ldap\Entry;
class EntryManagerTest extends TestCase class EntryManagerTest extends TestCase
{ {
/**
* @expectedException \Symfony\Component\Ldap\Exception\LdapException
* @expectedExceptionMessage Entry "$$$$$$" malformed, could not parse RDN.
*/
public function testMove()
{
$connection = $this->createMock(Connection::class);
$connection
->expects($this->once())
->method('isBound')->willReturn(true);
$entry = new Entry('$$$$$$');
$entryManager = new EntryManager($connection);
$entryManager->move($entry, 'a');
}
/** /**
* @expectedException \Symfony\Component\Ldap\Exception\NotBoundException * @expectedException \Symfony\Component\Ldap\Exception\NotBoundException
* @expectedExceptionMessage Query execution is not possible without binding the connection first. * @expectedExceptionMessage Query execution is not possible without binding the connection first.

View File

@ -4,6 +4,9 @@ CHANGELOG
4.3.0 4.3.0
----- -----
* Added `NonSendableStampInterface` that a stamp can implement if
it should not be sent to a transport. Transport serializers
must now check for these stamps and not encode them.
* [BC BREAK] `SendersLocatorInterface` has an additional method: * [BC BREAK] `SendersLocatorInterface` has an additional method:
`getSenderByAlias()`. `getSenderByAlias()`.
* Removed argument `?bool &$handle = false` from `SendersLocatorInterface::getSenders` * Removed argument `?bool &$handle = false` from `SendersLocatorInterface::getSenders`

View File

@ -80,6 +80,22 @@ final class Envelope
return $cloned; return $cloned;
} }
/**
* Removes all stamps that implement the given type.
*/
public function withoutStampsOfType(string $type): self
{
$cloned = clone $this;
foreach ($cloned->stamps as $class => $stamps) {
if ($class === $type || \is_subclass_of($class, $type)) {
unset($cloned->stamps[$class]);
}
}
return $cloned;
}
public function last(string $stampFqcn): ?StampInterface public function last(string $stampFqcn): ?StampInterface
{ {
return isset($this->stamps[$stampFqcn]) ? end($this->stamps[$stampFqcn]) : null; return isset($this->stamps[$stampFqcn]) ? end($this->stamps[$stampFqcn]) : null;

View File

@ -0,0 +1,23 @@
<?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\Stamp;
/**
* A stamp that should not be included with the Envelope if sent to a transport.
*
* @author Ryan Weaver <ryan@symfonycasts.com>
*
* @experimental in 4.3
*/
interface NonSendableStampInterface extends StampInterface
{
}

View File

@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\DelayStamp; use Symfony\Component\Messenger\Stamp\DelayStamp;
use Symfony\Component\Messenger\Stamp\ReceivedStamp; use Symfony\Component\Messenger\Stamp\ReceivedStamp;
use Symfony\Component\Messenger\Stamp\StampInterface;
use Symfony\Component\Messenger\Stamp\ValidationStamp; use Symfony\Component\Messenger\Stamp\ValidationStamp;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
@ -50,6 +51,30 @@ class EnvelopeTest extends TestCase
$this->assertCount(1, $envelope->all(DelayStamp::class)); $this->assertCount(1, $envelope->all(DelayStamp::class));
} }
public function testWithoutStampsOfType()
{
$envelope = new Envelope(new DummyMessage('dummy'), [
new ReceivedStamp('transport1'),
new DelayStamp(5000),
new DummyExtendsDelayStamp(5000),
new DummyImplementsFooBarStampInterface(),
]);
$envelope2 = $envelope->withoutStampsOfType(DummyNothingImplementsMeStampInterface::class);
$this->assertEquals($envelope, $envelope2);
$envelope3 = $envelope2->withoutStampsOfType(ReceivedStamp::class);
$this->assertEmpty($envelope3->all(ReceivedStamp::class));
$envelope4 = $envelope3->withoutStampsOfType(DelayStamp::class);
$this->assertEmpty($envelope4->all(DelayStamp::class));
$this->assertEmpty($envelope4->all(DummyExtendsDelayStamp::class));
$envelope5 = $envelope4->withoutStampsOfType(DummyFooBarStampInterface::class);
$this->assertEmpty($envelope5->all(DummyImplementsFooBarStampInterface::class));
$this->assertEmpty($envelope5->all());
}
public function testLast() public function testLast()
{ {
$receivedStamp = new ReceivedStamp('transport'); $receivedStamp = new ReceivedStamp('transport');
@ -92,3 +117,16 @@ class EnvelopeTest extends TestCase
$this->assertCount(1, $envelope->all(ReceivedStamp::class)); $this->assertCount(1, $envelope->all(ReceivedStamp::class));
} }
} }
class DummyExtendsDelayStamp extends DelayStamp
{
}
interface DummyFooBarStampInterface extends StampInterface
{
}
interface DummyNothingImplementsMeStampInterface extends StampInterface
{
}
class DummyImplementsFooBarStampInterface implements DummyFooBarStampInterface
{
}

View File

@ -46,9 +46,9 @@ class ConnectionTest extends TestCase
'host' => 'localhost', 'host' => 'localhost',
'port' => 6379, 'port' => 6379,
], [ ], [
'blocking_timeout' => 30, 'serializer' => 2,
]), ]),
Connection::fromDsn('redis://localhost/queue/group1/consumer1', ['blocking_timeout' => 30]) Connection::fromDsn('redis://localhost/queue/group1/consumer1', ['serializer' => 2])
); );
} }
@ -59,9 +59,9 @@ class ConnectionTest extends TestCase
'host' => 'localhost', 'host' => 'localhost',
'port' => 6379, 'port' => 6379,
], [ ], [
'blocking_timeout' => 30, 'serializer' => 2,
]), ]),
Connection::fromDsn('redis://localhost/queue/group1/consumer1?blocking_timeout=30') Connection::fromDsn('redis://localhost/queue/group1/consumer1?serializer=2')
); );
} }
@ -134,16 +134,20 @@ class ConnectionTest extends TestCase
$redis->del('messenger-rejectthenget'); $redis->del('messenger-rejectthenget');
} }
public function testBlockingTimeout() public function testGetNonBlocking()
{ {
$redis = new \Redis(); $redis = new \Redis();
$connection = Connection::fromDsn('redis://localhost/messenger-blockingtimeout', ['blocking_timeout' => 1], $redis);
$connection = Connection::fromDsn('redis://localhost/messenger-getnonblocking', [], $redis);
try { try {
$connection->setup(); $connection->setup();
} catch (TransportException $e) { } catch (TransportException $e) {
} }
$this->assertNull($connection->get()); $this->assertNull($connection->get()); // no message, should return null immediately
$redis->del('messenger-blockingtimeout'); $connection->add('1', []);
$this->assertNotEmpty($message = $connection->get());
$connection->reject($message['id']);
$redis->del('messenger-getnonblocking');
} }
} }

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Serialization;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
@ -63,4 +64,20 @@ class PhpSerializerTest extends TestCase
'body' => 'O:13:"ReceivedSt0mp":0:{}', 'body' => 'O:13:"ReceivedSt0mp":0:{}',
]); ]);
} }
public function testEncodedSkipsNonEncodeableStamps()
{
$serializer = new PhpSerializer();
$envelope = new Envelope(new DummyMessage('Hello'), [
new DummyPhpSerializerNonSendableStamp(),
]);
$encoded = $serializer->encode($envelope);
$this->assertNotContains('DummyPhpSerializerNonSendableStamp', $encoded['body']);
}
}
class DummyPhpSerializerNonSendableStamp implements NonSendableStampInterface
{
} }

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Serialization;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
use Symfony\Component\Messenger\Stamp\SerializerStamp; use Symfony\Component\Messenger\Stamp\SerializerStamp;
use Symfony\Component\Messenger\Stamp\ValidationStamp; use Symfony\Component\Messenger\Stamp\ValidationStamp;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
@ -193,4 +194,19 @@ class SerializerTest extends TestCase
'headers' => ['type' => 'NonExistentClass'], 'headers' => ['type' => 'NonExistentClass'],
]); ]);
} }
public function testEncodedSkipsNonEncodeableStamps()
{
$serializer = new Serializer();
$envelope = new Envelope(new DummyMessage('Hello'), [
new DummySymfonySerializerNonSendableStamp(),
]);
$encoded = $serializer->encode($envelope);
$this->assertNotContains('DummySymfonySerializerNonSendableStamp', print_r($encoded['headers'], true));
}
}
class DummySymfonySerializerNonSendableStamp implements NonSendableStampInterface
{
} }

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Messenger\Transport\AmqpExt; namespace Symfony\Component\Messenger\Transport\AmqpExt;
use Symfony\Component\Messenger\Stamp\StampInterface; use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
/** /**
* Stamp applied when a message is received from Amqp. * Stamp applied when a message is received from Amqp.
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class AmqpReceivedStamp implements StampInterface class AmqpReceivedStamp implements NonSendableStampInterface
{ {
private $amqpEnvelope; private $amqpEnvelope;
private $queueName; private $queueName;

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Messenger\Transport\Doctrine; namespace Symfony\Component\Messenger\Transport\Doctrine;
use Symfony\Component\Messenger\Stamp\StampInterface; use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
/** /**
* @author Vincent Touzet <vincent.touzet@gmail.com> * @author Vincent Touzet <vincent.touzet@gmail.com>
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class DoctrineReceivedStamp implements StampInterface class DoctrineReceivedStamp implements NonSendableStampInterface
{ {
private $id; private $id;

View File

@ -31,7 +31,6 @@ class Connection
private $stream; private $stream;
private $group; private $group;
private $consumer; private $consumer;
private $blockingTimeout;
private $couldHavePendingMessages = true; private $couldHavePendingMessages = true;
public function __construct(array $configuration, array $connectionCredentials = [], array $redisOptions = [], \Redis $redis = null) public function __construct(array $configuration, array $connectionCredentials = [], array $redisOptions = [], \Redis $redis = null)
@ -42,7 +41,6 @@ class Connection
$this->stream = $configuration['stream'] ?? '' ?: 'messages'; $this->stream = $configuration['stream'] ?? '' ?: 'messages';
$this->group = $configuration['group'] ?? '' ?: 'symfony'; $this->group = $configuration['group'] ?? '' ?: 'symfony';
$this->consumer = $configuration['consumer'] ?? '' ?: 'consumer'; $this->consumer = $configuration['consumer'] ?? '' ?: 'consumer';
$this->blockingTimeout = $redisOptions['blocking_timeout'] ?? null;
} }
public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $redis = null): self public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $redis = null): self
@ -83,8 +81,7 @@ class Connection
$this->group, $this->group,
$this->consumer, $this->consumer,
[$this->stream => $messageId], [$this->stream => $messageId],
1, 1
$this->blockingTimeout
); );
} catch (\RedisException $e) { } catch (\RedisException $e) {
} }
@ -142,7 +139,7 @@ class Connection
} }
} }
public function add(string $body, array $headers) public function add(string $body, array $headers): void
{ {
$e = null; $e = null;
try { try {

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Messenger\Transport\RedisExt; namespace Symfony\Component\Messenger\Transport\RedisExt;
use Symfony\Component\Messenger\Stamp\StampInterface; use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
/** /**
* @author Alexander Schranz <alexander@sulu.io> * @author Alexander Schranz <alexander@sulu.io>
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class RedisReceivedStamp implements StampInterface class RedisReceivedStamp implements NonSendableStampInterface
{ {
private $id; private $id;

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Messenger\Transport\Serialization;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
/** /**
* @author Ryan Weaver<ryan@symfonycasts.com> * @author Ryan Weaver<ryan@symfonycasts.com>
@ -40,6 +41,8 @@ class PhpSerializer implements SerializerInterface
*/ */
public function encode(Envelope $envelope): array public function encode(Envelope $envelope): array
{ {
$envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);
$body = addslashes(serialize($envelope)); $body = addslashes(serialize($envelope));
return [ return [

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Messenger\Transport\Serialization;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\LogicException; use Symfony\Component\Messenger\Exception\LogicException;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException; use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
use Symfony\Component\Messenger\Stamp\SerializerStamp; use Symfony\Component\Messenger\Stamp\SerializerStamp;
use Symfony\Component\Messenger\Stamp\StampInterface; use Symfony\Component\Messenger\Stamp\StampInterface;
use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\JsonEncoder;
@ -47,7 +48,7 @@ class Serializer implements SerializerInterface
public static function create(): self public static function create(): self
{ {
if (!class_exists(SymfonySerializer::class)) { if (!class_exists(SymfonySerializer::class)) {
throw new LogicException(sprintf('The default Messenger Serializer requires Symfony\'s Serializer component. Try running "composer require symfony/serializer".')); throw new LogicException(sprintf('The "%s" class requires Symfony\'s Serializer component. Try running "composer require symfony/serializer" or use "%s" instead.', __CLASS__, PhpSerializer::class));
} }
$encoders = [new XmlEncoder(), new JsonEncoder()]; $encoders = [new XmlEncoder(), new JsonEncoder()];
@ -98,6 +99,8 @@ class Serializer implements SerializerInterface
$context = $serializerStamp->getContext() + $context; $context = $serializerStamp->getContext() + $context;
} }
$envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);
$headers = ['type' => \get_class($envelope->getMessage())] + $this->encodeStamps($envelope); $headers = ['type' => \get_class($envelope->getMessage())] + $this->encodeStamps($envelope);
return [ return [

View File

@ -39,6 +39,9 @@ interface SerializerInterface
* Encodes an envelope content (message & stamps) to a common format understandable by transports. * Encodes an envelope content (message & stamps) to a common format understandable by transports.
* The encoded array should only contain scalars and arrays. * The encoded array should only contain scalars and arrays.
* *
* Stamps that implement NonSendableStampInterface should
* not be encoded.
*
* The most common keys of the encoded array are: * The most common keys of the encoded array are:
* - `body` (string) - the message body * - `body` (string) - the message body
* - `headers` (string<string>) - a key/value pair of headers * - `headers` (string<string>) - a key/value pair of headers

View File

@ -1157,6 +1157,7 @@ final class MimeTypes implements MimeTypesInterface
'audio/x-flac' => ['flac'], 'audio/x-flac' => ['flac'],
'audio/x-flac+ogg' => ['oga', 'ogg'], 'audio/x-flac+ogg' => ['oga', 'ogg'],
'audio/x-gsm' => ['gsm'], 'audio/x-gsm' => ['gsm'],
'audio/x-hx-aac-adts' => ['aac', 'adts', 'ass'],
'audio/x-imelody' => ['imy', 'ime'], 'audio/x-imelody' => ['imy', 'ime'],
'audio/x-iriver-pla' => ['pla'], 'audio/x-iriver-pla' => ['pla'],
'audio/x-it' => ['it'], 'audio/x-it' => ['it'],
@ -1631,7 +1632,7 @@ final class MimeTypes implements MimeTypesInterface
'a78' => ['application/x-atari-7800-rom'], 'a78' => ['application/x-atari-7800-rom'],
'aa' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'], 'aa' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'],
'aab' => ['application/x-authorware-bin'], 'aab' => ['application/x-authorware-bin'],
'aac' => ['audio/aac', 'audio/x-aac'], 'aac' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts'],
'aam' => ['application/x-authorware-map'], 'aam' => ['application/x-authorware-map'],
'aas' => ['application/x-authorware-seg'], 'aas' => ['application/x-authorware-seg'],
'aax' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'], 'aax' => ['audio/vnd.audible', 'audio/vnd.audible.aax', 'audio/x-pn-audibleaudio'],
@ -1648,7 +1649,7 @@ final class MimeTypes implements MimeTypesInterface
'adf' => ['application/x-amiga-disk-format'], 'adf' => ['application/x-amiga-disk-format'],
'adp' => ['audio/adpcm'], 'adp' => ['audio/adpcm'],
'ads' => ['text/x-adasrc'], 'ads' => ['text/x-adasrc'],
'adts' => ['audio/aac', 'audio/x-aac'], 'adts' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts'],
'aep' => ['application/vnd.audiograph'], 'aep' => ['application/vnd.audiograph'],
'afm' => ['application/x-font-afm', 'application/x-font-type1'], 'afm' => ['application/x-font-afm', 'application/x-font-type1'],
'afp' => ['application/vnd.ibm.modcap'], 'afp' => ['application/vnd.ibm.modcap'],
@ -1687,7 +1688,7 @@ final class MimeTypes implements MimeTypesInterface
'asm' => ['text/x-asm'], 'asm' => ['text/x-asm'],
'aso' => ['application/vnd.accpac.simply.aso'], 'aso' => ['application/vnd.accpac.simply.aso'],
'asp' => ['application/x-asp'], 'asp' => ['application/x-asp'],
'ass' => ['audio/aac', 'audio/x-aac', 'text/x-ssa'], 'ass' => ['audio/aac', 'audio/x-aac', 'audio/x-hx-aac-adts', 'text/x-ssa'],
'asx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-asf', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'], 'asx' => ['application/x-ms-asx', 'audio/x-ms-asx', 'video/x-ms-asf', 'video/x-ms-wax', 'video/x-ms-wmx', 'video/x-ms-wvx'],
'atc' => ['application/vnd.acucorp'], 'atc' => ['application/vnd.acucorp'],
'atom' => ['application/atom+xml'], 'atom' => ['application/atom+xml'],

View File

@ -199,7 +199,15 @@ abstract class AbstractToken implements TokenInterface
*/ */
public function __unserialize(array $data): void public function __unserialize(array $data): void
{ {
[$this->user, $this->authenticated, $this->roles, $this->attributes, $this->roleNames] = $data; [$this->user, $this->authenticated, $this->roles, $this->attributes] = $data;
// migration path to 4.3+
if (null === $this->roleNames = $data[4] ?? null) {
$this->roleNames = [];
foreach ($this->roles as $role) {
$this->roleNames[] = (string) $role;
}
}
} }
/** /**

View File

@ -68,10 +68,10 @@ class DataCollectorTranslator implements LegacyTranslatorInterface, TranslatorIn
{ {
if ($this->translator instanceof TranslatorInterface) { if ($this->translator instanceof TranslatorInterface) {
$trans = $this->translator->trans($id, ['%count%' => $number] + $parameters, $domain, $locale); $trans = $this->translator->trans($id, ['%count%' => $number] + $parameters, $domain, $locale);
} else {
$trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
} }
$trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
$this->collectMessage($locale, $domain, $id, $trans, ['%count%' => $number] + $parameters); $this->collectMessage($locale, $domain, $id, $trans, ['%count%' => $number] + $parameters);
return $trans; return $trans;