Merge branch '4.3' into 4.4

* 4.3:
  [Routing] Add a param annotation for $annot.
  [DI] fix docblock
  [Console] fix docblock
  Add missing translations for Armenian locale
  [Process] Added missing return type.
  [Process] Doc block backport.
  Added doc block for Registry::supports().
  [Cache] Fix predis test
  Don't duplicate addresses in Sendgrid Transport
  Remove unnecessary statement
  Fix some docblocks.
  [Messenger] make delay exchange and queues durable like the normal ones by default
  Cancel delayed message if handler fails
  Added tests for #32370
This commit is contained in:
Nicolas Grekas 2019-08-19 13:17:23 +02:00
commit 3cd20c993d
16 changed files with 250 additions and 30 deletions

View File

@ -14,6 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\StyleInterface;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
@ -26,6 +27,9 @@ use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
*/
abstract class AbstractConfigCommand extends ContainerDebugCommand
{
/**
* @param OutputInterface|StyleInterface $output
*/
protected function listBundles($output)
{
$title = 'Available registered bundles with their extension alias if available';

View File

@ -111,8 +111,8 @@ class CookieJar
/**
* Updates the cookie jar from a response Set-Cookie headers.
*
* @param array $setCookies Set-Cookie headers from an HTTP response
* @param string $uri The base URL
* @param string[] $setCookies Set-Cookie headers from an HTTP response
* @param string $uri The base URL
*/
public function updateFromSetCookie(array $setCookies, $uri = null)
{

View File

@ -34,7 +34,7 @@ class PredisAdapterTest extends AbstractRedisAdapterTest
$params = [
'scheme' => 'tcp',
'host' => 'localhost',
'host' => $redisHost,
'port' => 6379,
'persistent' => 0,
'timeout' => 3,

View File

@ -110,7 +110,7 @@ trait TesterTrait
* @param array $inputs An array of strings representing each input
* passed to the command input stream
*
* @return self
* @return $this
*/
public function setInputs(array $inputs)
{

View File

@ -89,7 +89,7 @@ class ChildDefinition extends Definition
* @param int|string $index
* @param mixed $value
*
* @return self the current instance
* @return $this
*
* @throws InvalidArgumentException when $index isn't an integer
*/

View File

@ -1208,7 +1208,6 @@ EOF;
if (!$id->isDeprecated()) {
continue;
}
$id = (string) $id;
$code .= ' '.$this->doExport($alias).' => '.$this->doExport($this->generateMethodName($alias)).",\n";
}

View File

@ -0,0 +1,61 @@
<?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\Mailer\Bridge\Sendgrid\Tests\Http\Api;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Bridge\Sendgrid\Http\Api\SendgridTransport;
use Symfony\Component\Mime\Email;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
class SendgridTransportTest extends TestCase
{
public function testSend()
{
$email = new Email();
$email->from('foo@example.com')
->to('bar@example.com')
->bcc('baz@example.com');
$response = $this->createMock(ResponseInterface::class);
$response
->expects($this->once())
->method('getStatusCode')
->willReturn(202);
$httpClient = $this->createMock(HttpClientInterface::class);
$httpClient
->expects($this->once())
->method('request')
->with('POST', 'https://api.sendgrid.com/v3/mail/send', [
'json' => [
'personalizations' => [
[
'to' => [['email' => 'bar@example.com']],
'subject' => null,
'bcc' => [['email' => 'baz@example.com']],
],
],
'from' => ['email' => 'foo@example.com'],
'content' => [],
],
'auth_bearer' => 'foo',
])
->willReturn($response);
$mailer = new SendgridTransport('foo', $httpClient);
$mailer->send($email);
}
}

View File

@ -73,7 +73,7 @@ class SendgridApiTransport extends AbstractApiTransport
}
$personalization = [
'to' => array_map($addressStringifier, $this->getRecipients($email, $envelope)),
'to' => array_map($addressStringifier, $email->getTo()),
'subject' => $email->getSubject(),
];
if ($emails = array_map($addressStringifier, $email->getCc())) {

View File

@ -81,12 +81,16 @@ class DispatchAfterCurrentBusMiddleware implements MiddlewareInterface
// "Root dispatch" call is finished, dispatch stored messages.
$exceptions = [];
while (null !== $queueItem = array_shift($this->queue)) {
// Save how many messages are left in queue before handling the message
$queueLengthBefore = \count($this->queue);
try {
// Execute the stored messages
$queueItem->getStack()->next()->handle($queueItem->getEnvelope(), $queueItem->getStack());
} catch (\Exception $exception) {
// Gather all exceptions
$exceptions[] = $exception;
// Restore queue to previous state
$this->queue = \array_slice($this->queue, 0, $queueLengthBefore);
}
}

View File

@ -99,6 +99,86 @@ class DispatchAfterCurrentBusMiddlewareTest extends TestCase
$messageBus->dispatch($message);
}
public function testLongChainWithExceptions()
{
$command = new DummyMessage('Level 0');
$eventL1a = new DummyEvent('Event level 1A');
$eventL1b = new DummyEvent('Event level 1B'); // will dispatch 2 more events
$eventL1c = new DummyEvent('Event level 1C');
$eventL2a = new DummyEvent('Event level 2A'); // Will dispatch 1 event and throw exception
$eventL2b = new DummyEvent('Event level 2B'); // Will dispatch 1 event
$eventL3a = new DummyEvent('Event level 3A'); // This should never get handled.
$eventL3b = new DummyEvent('Event level 3B');
$middleware = new DispatchAfterCurrentBusMiddleware();
$handlingMiddleware = $this->createMock(MiddlewareInterface::class);
$eventBus = new MessageBus([
$middleware,
$handlingMiddleware,
]);
// The command bus will dispatch 3 events.
$commandBus = new MessageBus([
$middleware,
new DispatchingMiddleware($eventBus, [
new Envelope($eventL1a, [new DispatchAfterCurrentBusStamp()]),
new Envelope($eventL1b, [new DispatchAfterCurrentBusStamp()]),
new Envelope($eventL1c, [new DispatchAfterCurrentBusStamp()]),
]),
$handlingMiddleware,
]);
// Expect main dispatched message to be handled first:
$this->expectHandledMessage($handlingMiddleware, 0, $command);
$this->expectHandledMessage($handlingMiddleware, 1, $eventL1a);
// Handling $eventL1b will dispatch 2 more events
$handlingMiddleware->expects($this->at(2))->method('handle')->with($this->callback(function (Envelope $envelope) use ($eventL1b) {
return $envelope->getMessage() === $eventL1b;
}))->willReturnCallback(function ($envelope, StackInterface $stack) use ($eventBus, $eventL2a, $eventL2b) {
$envelope1 = new Envelope($eventL2a, [new DispatchAfterCurrentBusStamp()]);
$eventBus->dispatch($envelope1);
$eventBus->dispatch(new Envelope($eventL2b, [new DispatchAfterCurrentBusStamp()]));
return $stack->next()->handle($envelope, $stack);
});
$this->expectHandledMessage($handlingMiddleware, 3, $eventL1c);
// Handle $eventL2a will dispatch event and throw exception
$handlingMiddleware->expects($this->at(4))->method('handle')->with($this->callback(function (Envelope $envelope) use ($eventL2a) {
return $envelope->getMessage() === $eventL2a;
}))->willReturnCallback(function ($envelope, StackInterface $stack) use ($eventBus, $eventL3a) {
$eventBus->dispatch(new Envelope($eventL3a, [new DispatchAfterCurrentBusStamp()]));
throw new \RuntimeException('Some exception while handling Event level 2a');
});
// Make sure $eventL2b is handled, since it was dispatched from $eventL1b
$handlingMiddleware->expects($this->at(5))->method('handle')->with($this->callback(function (Envelope $envelope) use ($eventL2b) {
return $envelope->getMessage() === $eventL2b;
}))->willReturnCallback(function ($envelope, StackInterface $stack) use ($eventBus, $eventL3b) {
$eventBus->dispatch(new Envelope($eventL3b, [new DispatchAfterCurrentBusStamp()]));
return $stack->next()->handle($envelope, $stack);
});
// We dont handle exception L3a since L2a threw an exception.
$this->expectHandledMessage($handlingMiddleware, 6, $eventL3b);
// Note: $eventL3a should not be handled.
$this->expectException(DelayedMessageHandlingException::class);
$this->expectExceptionMessage('RuntimeException: Some exception while handling Event level 2a');
$commandBus->dispatch($command);
}
public function testHandleDelayedEventFromQueue()
{
$message = new DummyMessage('Hello');

View File

@ -364,7 +364,7 @@ class ConnectionTest extends TestCase
$amqpQueue->expects($this->once())->method('setName')->with(self::DEFAULT_EXCHANGE_NAME);
$amqpQueue->expects($this->once())->method('declareQueue');
$delayExchange->expects($this->once())->method('setName')->with('delay');
$delayExchange->expects($this->once())->method('setName')->with('delays');
$delayExchange->expects($this->once())->method('declareExchange');
$delayExchange->expects($this->once())->method('publish');
@ -398,7 +398,7 @@ class ConnectionTest extends TestCase
]);
$delayQueue->expects($this->once())->method('declareQueue');
$delayQueue->expects($this->once())->method('bind')->with('delay', 'delay_messages__5000');
$delayQueue->expects($this->once())->method('bind')->with('delays', 'delay_messages__5000');
$delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages__5000', AMQP_NOPARAM, ['headers' => ['x-some-headers' => 'foo']]);
@ -440,7 +440,7 @@ class ConnectionTest extends TestCase
]);
$delayQueue->expects($this->once())->method('declareQueue');
$delayQueue->expects($this->once())->method('bind')->with('delay', 'delay_messages__120000');
$delayQueue->expects($this->once())->method('bind')->with('delays', 'delay_messages__120000');
$delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages__120000', AMQP_NOPARAM, ['headers' => []]);
$connection->publish('{}', [], 120000);
@ -544,7 +544,7 @@ class ConnectionTest extends TestCase
]);
$delayQueue->expects($this->once())->method('declareQueue');
$delayQueue->expects($this->once())->method('bind')->with('delay', 'delay_messages_routing_key_120000');
$delayQueue->expects($this->once())->method('bind')->with('delays', 'delay_messages_routing_key_120000');
$delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages_routing_key_120000', AMQP_NOPARAM, ['headers' => []]);
$connection->publish('{}', [], 120000, new AmqpStamp('routing_key'));

View File

@ -60,7 +60,7 @@ class Connection
{
$this->connectionOptions = array_replace_recursive([
'delay' => [
'exchange_name' => 'delay',
'exchange_name' => 'delays',
'queue_name_pattern' => 'delay_%exchange_name%_%routing_key%_%delay%',
],
], $connectionOptions);
@ -92,7 +92,7 @@ class Connection
* * arguments: Extra arguments
* * delay:
* * queue_name_pattern: Pattern to use to create the queues (Default: "delay_%exchange_name%_%routing_key%_%delay%")
* * exchange_name: Name of the exchange to be used for the delayed/retried messages (Default: "delay")
* * exchange_name: Name of the exchange to be used for the delayed/retried messages (Default: "delays")
* * auto_setup: Enable or not the auto-setup of queues and exchanges (Default: true)
* * prefetch_count: set channel prefetch count
*/
@ -251,6 +251,11 @@ class Connection
$this->amqpDelayExchange = $this->amqpFactory->createExchange($this->channel());
$this->amqpDelayExchange->setName($this->connectionOptions['delay']['exchange_name']);
$this->amqpDelayExchange->setType(AMQP_EX_TYPE_DIRECT);
if ('delays' === $this->connectionOptions['delay']['exchange_name']) {
// only add the new flag when the name was not provided explicitly so we're using the new default name to prevent a redeclaration error
// the condition will be removed in 4.4
$this->amqpDelayExchange->setFlags(AMQP_DURABLE);
}
}
return $this->amqpDelayExchange;
@ -273,16 +278,24 @@ class Connection
[$delay, $this->exchangeOptions['name'], $routingKey ?? ''],
$this->connectionOptions['delay']['queue_name_pattern']
));
if ('delay_%exchange_name%_%routing_key%_%delay%' === $this->connectionOptions['delay']['queue_name_pattern']) {
// the condition will be removed in 4.4
$queue->setFlags(AMQP_DURABLE);
$extraArguments = [
// delete the delay queue 10 seconds after the message expires
// publishing another message redeclares the queue which renews the lease
'x-expires' => $delay + 10000,
];
} else {
$extraArguments = [];
}
$queue->setArguments([
'x-message-ttl' => $delay,
// delete the delay queue 10 seconds after the message expires
// publishing another message redeclares the queue which renews the lease
'x-expires' => $delay + 10000,
'x-dead-letter-exchange' => $this->exchangeOptions['name'],
// after being released from to DLX, make sure the original routing key will be used
// we must use an empty string instead of null for the argument to be picked up
'x-dead-letter-routing-key' => $routingKey ?? '',
]);
] + $extraArguments);
return $queue;
}

View File

@ -186,6 +186,8 @@ class Process implements \IteratorAggregate
* @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
* @param int|float|null $timeout The timeout in seconds or null to disable
*
* @return static
*
* @throws RuntimeException When proc_open is not installed
*/
public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
@ -240,7 +242,7 @@ class Process implements \IteratorAggregate
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @return self
* @return $this
*
* @throws ProcessFailedException if the process didn't terminate successfully
*
@ -967,7 +969,7 @@ class Process implements \IteratorAggregate
*
* @param string|array $commandline The command to execute
*
* @return self The current Process instance
* @return $this
*
* @deprecated since Symfony 4.2.
*/
@ -1001,13 +1003,13 @@ class Process implements \IteratorAggregate
}
/**
* Sets the process timeout (max. runtime).
* Sets the process timeout (max. runtime) in seconds.
*
* To disable the timeout, set this value to null.
*
* @param int|float|null $timeout The timeout in seconds
*
* @return self The current Process instance
* @return $this
*
* @throws InvalidArgumentException if the timeout is negative
*/
@ -1025,7 +1027,7 @@ class Process implements \IteratorAggregate
*
* @param int|float|null $timeout The timeout in seconds
*
* @return self The current Process instance
* @return $this
*
* @throws LogicException if the output is disabled
* @throws InvalidArgumentException if the timeout is negative
@ -1046,7 +1048,7 @@ class Process implements \IteratorAggregate
*
* @param bool $tty True to enabled and false to disable
*
* @return self The current Process instance
* @return $this
*
* @throws RuntimeException In case the TTY mode is not supported
*/
@ -1080,7 +1082,7 @@ class Process implements \IteratorAggregate
*
* @param bool $bool
*
* @return self
* @return $this
*/
public function setPty($bool)
{
@ -1120,7 +1122,7 @@ class Process implements \IteratorAggregate
*
* @param string $cwd The new working directory
*
* @return self The current Process instance
* @return $this
*/
public function setWorkingDirectory($cwd)
{
@ -1152,7 +1154,7 @@ class Process implements \IteratorAggregate
*
* @param array $env The new environment variables
*
* @return self The current Process instance
* @return $this
*/
public function setEnv(array $env)
{
@ -1183,7 +1185,7 @@ class Process implements \IteratorAggregate
*
* @param string|int|float|bool|resource|\Traversable|null $input The content
*
* @return self The current Process instance
* @return $this
*
* @throws LogicException In case the process is running
*/
@ -1203,7 +1205,7 @@ class Process implements \IteratorAggregate
*
* @param bool $inheritEnv
*
* @return self The current Process instance
* @return $this
*
* @deprecated since Symfony 4.4, env variables are always inherited
*/

View File

@ -15,6 +15,7 @@ use Doctrine\Common\Annotations\Reader;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Loader\LoaderResolverInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Routing\Annotation\Route as RouteAnnotation;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
@ -129,6 +130,10 @@ abstract class AnnotationClassLoader implements LoaderInterface
return $collection;
}
/**
* @param RouteAnnotation $annot or an object that exposes a similar interface
* @param array $globals
*/
protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method)
{
$name = $annot->getName();

View File

@ -43,8 +43,8 @@ class LogoutUrlGenerator
*
* @param string $key The firewall key
* @param string $logoutPath The path that starts the logout process
* @param string $csrfTokenId The ID of the CSRF token
* @param string $csrfParameter The CSRF token parameter name
* @param string|null $csrfTokenId The ID of the CSRF token
* @param string|null $csrfParameter The CSRF token parameter name
* @param string|null $context The listener context
*/
public function registerListener($key, $logoutPath, $csrfTokenId, $csrfParameter, CsrfTokenManagerInterface $csrfTokenManager = null, string $context = null)

View File

@ -314,6 +314,58 @@
<source>This is not a valid Business Identifier Code (BIC).</source>
<target>Սա վավեր Business Identifier Code (BIC) չէ։</target>
</trans-unit>
<trans-unit id="82">
<source>Error</source>
<target>Սխալ</target>
</trans-unit>
<trans-unit id="83">
<source>This is not a valid UUID.</source>
<target>Սա վավեր UUID չէ:</target>
</trans-unit>
<trans-unit id="84">
<source>This value should be a multiple of {{ compared_value }}.</source>
<target>Այս արժեքը պետք է լինի բազմակի {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="85">
<source>This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.</source>
<target>Բիզնեսի նույնականացման կոդը (BIC) կապված չէ IBAN- ի հետ {{ iban }}.</target>
</trans-unit>
<trans-unit id="86">
<source>This value should be valid JSON.</source>
<target>Այս արժեքը պետք է լինի վավեր JSON:</target>
</trans-unit>
<trans-unit id="87">
<source>This collection should contain only unique elements.</source>
<target>Այս հավաքածուն պետք է պարունակի միայն եզակի տարրեր:</target>
</trans-unit>
<trans-unit id="88">
<source>This value should be positive.</source>
<target>Այս արժեքը պետք է լինի դրական:</target>
</trans-unit>
<trans-unit id="89">
<source>This value should be either positive or zero.</source>
<target>Այս արժեքը պետք է լինի դրական կամ զրոյական:</target>
</trans-unit>
<trans-unit id="90">
<source>This value should be negative.</source>
<target>Այս արժեքը պետք է լինի բացասական:</target>
</trans-unit>
<trans-unit id="91">
<source>This value should be either negative or zero.</source>
<target>Այս արժեքը պետք է լինի բացասական կամ զրոյական:</target>
</trans-unit>
<trans-unit id="92">
<source>This value is not a valid timezone.</source>
<target>Այս արժեքը վավեր ժամանակի գոտի չէ:</target>
</trans-unit>
<trans-unit id="93">
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
<target>Այս գաղտնաբառն արտահոսվել է տվյալների խախտման մեջ, այն չպետք է օգտագործվի: Խնդրում ենք օգտագործել մեկ այլ գաղտնաբառ:</target>
</trans-unit>
<trans-unit id="94">
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>Այս արժեքը պետք է լինի միջև {{ min }} և {{ max }}.</target>
</trans-unit>
</body>
</file>
</xliff>