Merge branch '5.2' into 5.x

* 5.2:
  Fix #36973: Command description consistency
  Render email once
This commit is contained in:
Fabien Potencier 2021-03-06 09:05:19 +01:00
commit e53bb8b4f2
42 changed files with 92 additions and 62 deletions

View File

@ -34,7 +34,7 @@ class ServerLogCommand extends Command
private $handler;
protected static $defaultName = 'server:log';
protected static $defaultDescription = 'Starts a log server that displays logs in real time';
protected static $defaultDescription = 'Start a log server that displays logs in real time';
public function isEnabled()
{

View File

@ -33,7 +33,7 @@ use Twig\Loader\FilesystemLoader;
class DebugCommand extends Command
{
protected static $defaultName = 'debug:twig';
protected static $defaultDescription = 'Shows a list of twig functions, filters, globals and tests';
protected static $defaultDescription = 'Show a list of twig functions, filters, globals and tests';
private $twig;
private $projectDir;

View File

@ -35,7 +35,7 @@ use Twig\Source;
class LintCommand extends Command
{
protected static $defaultName = 'lint:twig';
protected static $defaultDescription = 'Lints a Twig template and outputs encountered errors';
protected static $defaultDescription = 'Lint a Twig template and outputs encountered errors';
private $twig;

View File

@ -46,6 +46,14 @@ final class BodyRenderer implements BodyRendererInterface
}
$messageContext = $message->getContext();
$previousRenderingKey = $messageContext[__CLASS__] ?? null;
unset($messageContext[__CLASS__]);
$currentRenderingKey = md5(serialize([$messageContext, $message->getTextTemplate(), $message->getHtmlTemplate()]));
if ($previousRenderingKey === $currentRenderingKey) {
return;
}
if (isset($messageContext['email'])) {
throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message)));
}
@ -66,6 +74,7 @@ final class BodyRenderer implements BodyRendererInterface
if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) {
$message->text($this->convertHtmlToText(\is_resource($html) ? stream_get_contents($html) : $html));
}
$message->context($message->getContext() + [__CLASS__ => $currentRenderingKey]);
}
private function convertHtmlToText(string $html): string

View File

@ -79,6 +79,27 @@ HTML;
$this->prepareEmail('Text', '', ['email' => 'reserved!']);
}
public function testRenderedOnce()
{
$twig = new Environment(new ArrayLoader([
'text' => 'Text',
]));
$renderer = new BodyRenderer($twig);
$email = (new TemplatedEmail())
->to('fabien@symfony.com')
->from('helene@symfony.com')
;
$email->textTemplate('text');
$renderer->render($email);
$this->assertEquals('Text', $email->getTextBody());
$email->text('reset');
$renderer->render($email);
$this->assertEquals('reset', $email->getTextBody());
}
private function prepareEmail(?string $text, ?string $html, array $context = []): TemplatedEmail
{
$twig = new Environment(new ArrayLoader([

View File

@ -30,7 +30,7 @@ use Symfony\Component\HttpKernel\KernelInterface;
class AboutCommand extends Command
{
protected static $defaultName = 'about';
protected static $defaultDescription = 'Displays information about the current project';
protected static $defaultDescription = 'Display information about the current project';
/**
* {@inheritdoc}

View File

@ -40,7 +40,7 @@ class AssetsInstallCommand extends Command
public const METHOD_RELATIVE_SYMLINK = 'relative symlink';
protected static $defaultName = 'assets:install';
protected static $defaultDescription = 'Installs bundles web assets under a public directory';
protected static $defaultDescription = 'Install bundle\'s web assets under a public directory';
private $filesystem;
private $projectDir;
@ -62,7 +62,7 @@ class AssetsInstallCommand extends Command
->setDefinition([
new InputArgument('target', InputArgument::OPTIONAL, 'The target directory', null),
])
->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlinks the assets instead of copying it')
->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlink the assets instead of copying them')
->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks')
->addOption('no-cleanup', null, InputOption::VALUE_NONE, 'Do not remove the assets of the bundles that no longer exist')
->setDescription(self::$defaultDescription)

View File

@ -36,7 +36,7 @@ use Symfony\Component\HttpKernel\RebootableInterface;
class CacheClearCommand extends Command
{
protected static $defaultName = 'cache:clear';
protected static $defaultDescription = 'Clears the cache';
protected static $defaultDescription = 'Clear the cache';
private $cacheClearer;
private $filesystem;

View File

@ -28,7 +28,7 @@ use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
final class CachePoolClearCommand extends Command
{
protected static $defaultName = 'cache:pool:clear';
protected static $defaultDescription = 'Clears cache pools';
protected static $defaultDescription = 'Clear cache pools';
private $poolClearer;

View File

@ -26,7 +26,7 @@ use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
final class CachePoolDeleteCommand extends Command
{
protected static $defaultName = 'cache:pool:delete';
protected static $defaultDescription = 'Deletes an item from a cache pool';
protected static $defaultDescription = 'Delete an item from a cache pool';
private $poolClearer;

View File

@ -25,7 +25,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class CachePoolPruneCommand extends Command
{
protected static $defaultName = 'cache:pool:prune';
protected static $defaultDescription = 'Prunes cache pools';
protected static $defaultDescription = 'Prune cache pools';
private $pools;

View File

@ -29,7 +29,7 @@ use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate;
class CacheWarmupCommand extends Command
{
protected static $defaultName = 'cache:warmup';
protected static $defaultDescription = 'Warms up an empty cache';
protected static $defaultDescription = 'Warm up an empty cache';
private $cacheWarmer;

View File

@ -33,7 +33,7 @@ use Symfony\Component\Yaml\Yaml;
class ConfigDebugCommand extends AbstractConfigCommand
{
protected static $defaultName = 'debug:config';
protected static $defaultDescription = 'Dumps the current configuration for an extension';
protected static $defaultDescription = 'Dump the current configuration for an extension';
/**
* {@inheritdoc}

View File

@ -36,7 +36,7 @@ use Symfony\Component\Yaml\Yaml;
class ConfigDumpReferenceCommand extends AbstractConfigCommand
{
protected static $defaultName = 'config:dump-reference';
protected static $defaultDescription = 'Dumps the default configuration for an extension';
protected static $defaultDescription = 'Dump the default configuration for an extension';
/**
* {@inheritdoc}

View File

@ -35,7 +35,7 @@ class ContainerDebugCommand extends Command
use BuildDebugContainerTrait;
protected static $defaultName = 'debug:container';
protected static $defaultDescription = 'Displays current services for an application';
protected static $defaultDescription = 'Display current services for an application';
/**
* {@inheritdoc}
@ -45,18 +45,18 @@ class ContainerDebugCommand extends Command
$this
->setDefinition([
new InputArgument('name', InputArgument::OPTIONAL, 'A service name (foo)'),
new InputOption('show-arguments', null, InputOption::VALUE_NONE, 'Used to show arguments in services'),
new InputOption('show-hidden', null, InputOption::VALUE_NONE, 'Used to show hidden (internal) services'),
new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Shows all services with a specific tag'),
new InputOption('tags', null, InputOption::VALUE_NONE, 'Displays tagged services for an application'),
new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Displays a specific parameter for an application'),
new InputOption('parameters', null, InputOption::VALUE_NONE, 'Displays parameters for an application'),
new InputOption('types', null, InputOption::VALUE_NONE, 'Displays types (classes/interfaces) available in the container'),
new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Displays a specific environment variable used in the container'),
new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Displays environment variables used in the container'),
new InputOption('show-arguments', null, InputOption::VALUE_NONE, 'Show arguments in services'),
new InputOption('show-hidden', null, InputOption::VALUE_NONE, 'Show hidden (internal) services'),
new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Show all services with a specific tag'),
new InputOption('tags', null, InputOption::VALUE_NONE, 'Display tagged services for an application'),
new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Display a specific parameter for an application'),
new InputOption('parameters', null, InputOption::VALUE_NONE, 'Display parameters for an application'),
new InputOption('types', null, InputOption::VALUE_NONE, 'Display types (classes/interfaces) available in the container'),
new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Display a specific environment variable used in the container'),
new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Display environment variables used in the container'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Displays deprecations generated when compiling and warming up the container'),
new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Display deprecations generated when compiling and warming up the container'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'

View File

@ -30,7 +30,7 @@ use Symfony\Component\HttpKernel\Kernel;
final class ContainerLintCommand extends Command
{
protected static $defaultName = 'lint:container';
protected static $defaultDescription = 'Ensures that arguments injected into services match type declarations';
protected static $defaultDescription = 'Ensure that arguments injected into services match type declarations';
/**
* @var ContainerBuilder

View File

@ -30,7 +30,7 @@ use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
class DebugAutowiringCommand extends ContainerDebugCommand
{
protected static $defaultName = 'debug:autowiring';
protected static $defaultDescription = 'Lists classes/interfaces you can use for autowiring';
protected static $defaultDescription = 'List classes/interfaces you can use for autowiring';
private $supportsHref;
private $fileLinkFormatter;

View File

@ -33,7 +33,7 @@ class EventDispatcherDebugCommand extends Command
private const DEFAULT_DISPATCHER = 'event_dispatcher';
protected static $defaultName = 'debug:event-dispatcher';
protected static $defaultDescription = 'Displays configured listeners for an application';
protected static $defaultDescription = 'Display configured listeners for an application';
private $dispatchers;
public function __construct(ContainerInterface $dispatchers)

View File

@ -36,7 +36,7 @@ class RouterDebugCommand extends Command
use BuildDebugContainerTrait;
protected static $defaultName = 'debug:router';
protected static $defaultDescription = 'Displays current routes for an application';
protected static $defaultDescription = 'Display current routes for an application';
private $router;
private $fileLinkFormatter;

View File

@ -31,7 +31,7 @@ use Symfony\Component\Routing\RouterInterface;
class RouterMatchCommand extends Command
{
protected static $defaultName = 'router:match';
protected static $defaultDescription = 'Helps debug routes by simulating a path info match';
protected static $defaultDescription = 'Help debug routes by simulating a path info match';
private $router;
private $expressionLanguageProviders;
@ -52,9 +52,9 @@ class RouterMatchCommand extends Command
$this
->setDefinition([
new InputArgument('path_info', InputArgument::REQUIRED, 'A path info'),
new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Sets the HTTP method'),
new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Sets the URI scheme (usually http or https)'),
new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Sets the URI host'),
new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Set the HTTP method'),
new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Set the URI scheme (usually http or https)'),
new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Set the URI host'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'

View File

@ -27,7 +27,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class SecretsDecryptToLocalCommand extends Command
{
protected static $defaultName = 'secrets:decrypt-to-local';
protected static $defaultDescription = 'Decrypts all secrets and stores them in the local vault';
protected static $defaultDescription = 'Decrypt all secrets and stores them in the local vault';
private $vault;
private $localVault;
@ -44,7 +44,7 @@ final class SecretsDecryptToLocalCommand extends Command
{
$this
->setDescription(self::$defaultDescription)
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces overriding of secrets that already exist in the local vault')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force overriding of secrets that already exist in the local vault')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command decrypts all secrets and copies them in the local vault.

View File

@ -26,7 +26,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class SecretsEncryptFromLocalCommand extends Command
{
protected static $defaultName = 'secrets:encrypt-from-local';
protected static $defaultDescription = 'Encrypts all local secrets to the vault';
protected static $defaultDescription = 'Encrypt all local secrets to the vault';
private $vault;
private $localVault;

View File

@ -29,7 +29,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class SecretsGenerateKeysCommand extends Command
{
protected static $defaultName = 'secrets:generate-keys';
protected static $defaultDescription = 'Generates new encryption keys';
protected static $defaultDescription = 'Generate new encryption keys';
private $vault;
private $localVault;
@ -46,8 +46,8 @@ final class SecretsGenerateKeysCommand extends Command
{
$this
->setDescription(self::$defaultDescription)
->addOption('local', 'l', InputOption::VALUE_NONE, 'Updates the local vault.')
->addOption('rotate', 'r', InputOption::VALUE_NONE, 'Re-encrypts existing secrets with the newly generated keys.')
->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')
->addOption('rotate', 'r', InputOption::VALUE_NONE, 'Re-encrypt existing secrets with the newly generated keys.')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command generates a new encryption key.

View File

@ -30,7 +30,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class SecretsListCommand extends Command
{
protected static $defaultName = 'secrets:list';
protected static $defaultDescription = 'Lists all secrets';
protected static $defaultDescription = 'List all secrets';
private $vault;
private $localVault;

View File

@ -29,7 +29,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class SecretsRemoveCommand extends Command
{
protected static $defaultName = 'secrets:remove';
protected static $defaultDescription = 'Removes a secret from the vault';
protected static $defaultDescription = 'Remove a secret from the vault';
private $vault;
private $localVault;
@ -47,7 +47,7 @@ final class SecretsRemoveCommand extends Command
$this
->setDescription(self::$defaultDescription)
->addArgument('name', InputArgument::REQUIRED, 'The name of the secret')
->addOption('local', 'l', InputOption::VALUE_NONE, 'Updates the local vault.')
->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command removes a secret from the vault.

View File

@ -30,7 +30,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class SecretsSetCommand extends Command
{
protected static $defaultName = 'secrets:set';
protected static $defaultDescription = 'Sets a secret in the vault';
protected static $defaultDescription = 'Set a secret in the vault';
private $vault;
private $localVault;
@ -49,8 +49,8 @@ final class SecretsSetCommand extends Command
->setDescription(self::$defaultDescription)
->addArgument('name', InputArgument::REQUIRED, 'The name of the secret')
->addArgument('file', InputArgument::OPTIONAL, 'A file where to read the secret from or "-" for reading from STDIN')
->addOption('local', 'l', InputOption::VALUE_NONE, 'Updates the local vault.')
->addOption('random', 'r', InputOption::VALUE_OPTIONAL, 'Generates a random value.', false)
->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')
->addOption('random', 'r', InputOption::VALUE_OPTIONAL, 'Generate a random value.', false)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command stores a secret in the vault.

View File

@ -47,7 +47,7 @@ class TranslationDebugCommand extends Command
public const MESSAGE_EQUALS_FALLBACK = 2;
protected static $defaultName = 'debug:translation';
protected static $defaultDescription = 'Displays translation messages information';
protected static $defaultDescription = 'Display translation messages information';
private $translator;
private $reader;
@ -80,8 +80,8 @@ class TranslationDebugCommand extends Command
new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'),
new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'The messages domain'),
new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Displays only missing messages'),
new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Displays only unused messages'),
new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Display only missing messages'),
new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Display only unused messages'),
new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'),
])
->setDescription(self::$defaultDescription)

View File

@ -42,7 +42,7 @@ class TranslationUpdateCommand extends Command
private const SORT_ORDERS = [self::ASC, self::DESC];
protected static $defaultName = 'translation:update';
protected static $defaultDescription = 'Updates the translation file';
protected static $defaultDescription = 'Update the translation file';
private $writer;
private $reader;

View File

@ -41,7 +41,7 @@ class WorkflowDumpCommand extends Command
->setDefinition([
new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'),
new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'),
new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Labels a graph'),
new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Label a graph'),
new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format [dot|puml]', 'dot'),
])
->setDescription(self::$defaultDescription)

View File

@ -37,7 +37,7 @@ use Symfony\Component\Security\Core\Encoder\SelfSaltingEncoderInterface;
class UserPasswordEncoderCommand extends Command
{
protected static $defaultName = 'security:encode-password';
protected static $defaultDescription = 'Encodes a password';
protected static $defaultDescription = 'Encode a password';
private $encoderFactory;
private $userClasses;

View File

@ -40,7 +40,7 @@ class HelpCommand extends Command
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
])
->setDescription('Displays help for a command')
->setDescription('Display help for a command')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:

View File

@ -37,7 +37,7 @@ class ListCommand extends Command
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
])
->setDescription('Lists commands')
->setDescription('List commands')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:

View File

@ -32,7 +32,7 @@ use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
class DebugCommand extends Command
{
protected static $defaultName = 'debug:form';
protected static $defaultDescription = 'Displays form type information';
protected static $defaultDescription = 'Display form type information';
private $formRegistry;
private $namespaces;

View File

@ -36,7 +36,7 @@ use Symfony\Component\Messenger\Worker;
class ConsumeMessagesCommand extends Command
{
protected static $defaultName = 'messenger:consume';
protected static $defaultDescription = 'Consumes messages';
protected static $defaultDescription = 'Consume messages';
private $routableBus;
private $receiverLocator;

View File

@ -26,7 +26,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
class DebugCommand extends Command
{
protected static $defaultName = 'debug:messenger';
protected static $defaultDescription = 'Lists messages you can dispatch using the message buses';
protected static $defaultDescription = 'List messages you can dispatch using the message buses';
private $mapping;

View File

@ -35,7 +35,7 @@ use Symfony\Component\Messenger\Worker;
class FailedMessagesRetryCommand extends AbstractFailedMessagesCommand
{
protected static $defaultName = 'messenger:failed:retry';
protected static $defaultDescription = 'Retries one or more messages from the failure transport';
protected static $defaultDescription = 'Retry one or more messages from the failure transport';
private $eventDispatcher;
private $messageBus;

View File

@ -28,7 +28,7 @@ use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface;
class FailedMessagesShowCommand extends AbstractFailedMessagesCommand
{
protected static $defaultName = 'messenger:failed:show';
protected static $defaultDescription = 'Shows one or more messages from the failure transport';
protected static $defaultDescription = 'Show one or more messages from the failure transport';
/**
* {@inheritdoc}

View File

@ -25,7 +25,7 @@ use Symfony\Component\Messenger\Transport\SetupableTransportInterface;
class SetupTransportsCommand extends Command
{
protected static $defaultName = 'messenger:setup-transports';
protected static $defaultDescription = 'Prepares the required infrastructure for the transport';
protected static $defaultDescription = 'Prepare the required infrastructure for the transport';
private $transportLocator;
private $transportNames;

View File

@ -25,7 +25,7 @@ use Symfony\Component\Messenger\EventListener\StopWorkerOnRestartSignalListener;
class StopWorkersCommand extends Command
{
protected static $defaultName = 'messenger:stop-workers';
protected static $defaultDescription = 'Stops workers after their current message';
protected static $defaultDescription = 'Stop workers after their current message';
private $restartSignalCachePool;

View File

@ -31,7 +31,7 @@ use Symfony\Component\Translation\Util\XliffUtils;
class XliffLintCommand extends Command
{
protected static $defaultName = 'lint:xliff';
protected static $defaultDescription = 'Lints an XLIFF file and outputs encountered errors';
protected static $defaultDescription = 'Lint an XLIFF file and outputs encountered errors';
private $format;
private $displayCorrectFiles;

View File

@ -35,7 +35,7 @@ use Symfony\Component\VarDumper\Server\DumpServer;
class ServerDumpCommand extends Command
{
protected static $defaultName = 'server:dump';
protected static $defaultDescription = 'Starts a dump server that collects and displays dumps in a single place';
protected static $defaultDescription = 'Start a dump server that collects and displays dumps in a single place';
private $server;

View File

@ -33,7 +33,7 @@ use Symfony\Component\Yaml\Yaml;
class LintCommand extends Command
{
protected static $defaultName = 'lint:yaml';
protected static $defaultDescription = 'Lints a YAML file and outputs encountered errors';
protected static $defaultDescription = 'Lint a YAML file and outputs encountered errors';
private $parser;
private $format;