diff --git a/CHANGELOG.md b/CHANGELOG.md index ed3b53d14..4b0475167 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ CHANGELOG ========= +7.1 +--- + + * Add `CheckAliasValidityPass` to `lint:container` command + * Add `private_ranges` as a shortcut for private IP address ranges to the `trusted_proxies` option + * Mark classes `ConfigBuilderCacheWarmer`, `Router`, `SerializerCacheWarmer`, `TranslationsCacheWarmer`, `Translator` and `ValidatorCacheWarmer` as `final` + * Move the Router `cache_dir` to `kernel.build_dir` + * Deprecate the `router.cache_dir` config option + * Add `rate_limiter` tags to rate limiter services + * Add `secrets:reveal` command + * Add `rate_limiter` option to `http_client.default_options` and `http_client.scoped_clients` + * Attach the workflow's configuration to the `workflow` tag + * Add the `allowed_recipients` option for mailer to allow some users to receive + emails even if `recipients` is defined. + * Reset env vars when resetting the container + 7.0 --- diff --git a/CacheWarmer/AbstractPhpFileCacheWarmer.php b/CacheWarmer/AbstractPhpFileCacheWarmer.php index 3d7c99e4f..d809888be 100644 --- a/CacheWarmer/AbstractPhpFileCacheWarmer.php +++ b/CacheWarmer/AbstractPhpFileCacheWarmer.php @@ -19,14 +19,12 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface { - private string $phpArrayFile; - /** * @param string $phpArrayFile The PHP file where metadata are cached */ - public function __construct(string $phpArrayFile) - { - $this->phpArrayFile = $phpArrayFile; + public function __construct( + private string $phpArrayFile, + ) { } public function isOptional(): bool diff --git a/CacheWarmer/ConfigBuilderCacheWarmer.php b/CacheWarmer/ConfigBuilderCacheWarmer.php index 6693ddd3e..8b692c9c7 100644 --- a/CacheWarmer/ConfigBuilderCacheWarmer.php +++ b/CacheWarmer/ConfigBuilderCacheWarmer.php @@ -29,6 +29,8 @@ * Generate all config builders. * * @author Tobias Nyholm + * + * @final since Symfony 7.1 */ class ConfigBuilderCacheWarmer implements CacheWarmerInterface { diff --git a/CacheWarmer/RouterCacheWarmer.php b/CacheWarmer/RouterCacheWarmer.php index c2b9478a3..eed548046 100644 --- a/CacheWarmer/RouterCacheWarmer.php +++ b/CacheWarmer/RouterCacheWarmer.php @@ -36,6 +36,10 @@ public function __construct(ContainerInterface $container) public function warmUp(string $cacheDir, ?string $buildDir = null): array { + if (!$buildDir) { + return []; + } + $router = $this->container->get('router'); if ($router instanceof WarmableInterface) { diff --git a/CacheWarmer/SerializerCacheWarmer.php b/CacheWarmer/SerializerCacheWarmer.php index fcd67af1f..46da4daaa 100644 --- a/CacheWarmer/SerializerCacheWarmer.php +++ b/CacheWarmer/SerializerCacheWarmer.php @@ -23,19 +23,20 @@ * Warms up XML and YAML serializer metadata. * * @author Titouan Galopin + * + * @final since Symfony 7.1 */ class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer { - private array $loaders; - /** * @param LoaderInterface[] $loaders The serializer metadata loaders * @param string $phpArrayFile The PHP file where metadata are cached */ - public function __construct(array $loaders, string $phpArrayFile) - { + public function __construct( + private array $loaders, + string $phpArrayFile, + ) { parent::__construct($phpArrayFile); - $this->loaders = $loaders; } protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?string $buildDir = null): bool diff --git a/CacheWarmer/TranslationsCacheWarmer.php b/CacheWarmer/TranslationsCacheWarmer.php index b1c0fc6d7..19b2725c9 100644 --- a/CacheWarmer/TranslationsCacheWarmer.php +++ b/CacheWarmer/TranslationsCacheWarmer.php @@ -21,6 +21,8 @@ * Generates the catalogues for translations. * * @author Xavier Leune + * + * @final since Symfony 7.1 */ class TranslationsCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface { diff --git a/CacheWarmer/ValidatorCacheWarmer.php b/CacheWarmer/ValidatorCacheWarmer.php index bf1e5035f..6ecaa4bd1 100644 --- a/CacheWarmer/ValidatorCacheWarmer.php +++ b/CacheWarmer/ValidatorCacheWarmer.php @@ -24,18 +24,19 @@ * Warms up XML and YAML validator metadata. * * @author Titouan Galopin + * + * @final since Symfony 7.1 */ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer { - private ValidatorBuilder $validatorBuilder; - /** * @param string $phpArrayFile The PHP file where metadata are cached */ - public function __construct(ValidatorBuilder $validatorBuilder, string $phpArrayFile) - { + public function __construct( + private ValidatorBuilder $validatorBuilder, + string $phpArrayFile, + ) { parent::__construct($phpArrayFile); - $this->validatorBuilder = $validatorBuilder; } protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter, ?string $buildDir = null): bool diff --git a/Command/AssetsInstallCommand.php b/Command/AssetsInstallCommand.php index 264955d79..32b38de9a 100644 --- a/Command/AssetsInstallCommand.php +++ b/Command/AssetsInstallCommand.php @@ -41,15 +41,11 @@ class AssetsInstallCommand extends Command public const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; public const METHOD_RELATIVE_SYMLINK = 'relative symlink'; - private Filesystem $filesystem; - private string $projectDir; - - public function __construct(Filesystem $filesystem, string $projectDir) - { + public function __construct( + private Filesystem $filesystem, + private string $projectDir, + ) { parent::__construct(); - - $this->filesystem = $filesystem; - $this->projectDir = $projectDir; } protected function configure(): void @@ -264,7 +260,7 @@ private function getPublicDirectory(ContainerInterface $container): string return $defaultPublicDir; } - $composerConfig = json_decode(file_get_contents($composerFilePath), true); + $composerConfig = json_decode($this->filesystem->readFile($composerFilePath), true, flags: \JSON_THROW_ON_ERROR); return $composerConfig['extra']['public-dir'] ?? $defaultPublicDir; } diff --git a/Command/CacheClearCommand.php b/Command/CacheClearCommand.php index cdfc7f34f..55813664b 100644 --- a/Command/CacheClearCommand.php +++ b/Command/CacheClearCommand.php @@ -37,14 +37,14 @@ #[AsCommand(name: 'cache:clear', description: 'Clear the cache')] class CacheClearCommand extends Command { - private CacheClearerInterface $cacheClearer; private Filesystem $filesystem; - public function __construct(CacheClearerInterface $cacheClearer, ?Filesystem $filesystem = null) - { + public function __construct( + private CacheClearerInterface $cacheClearer, + ?Filesystem $filesystem = null, + ) { parent::__construct(); - $this->cacheClearer = $cacheClearer; $this->filesystem = $filesystem ?? new Filesystem(); } @@ -232,7 +232,7 @@ private function warmup(string $warmupDir, string $realBuildDir): void $search = [$warmupDir, str_replace('\\', '\\\\', $warmupDir)]; $replace = str_replace('\\', '/', $realBuildDir); foreach (Finder::create()->files()->in($warmupDir) as $file) { - $content = str_replace($search, $replace, file_get_contents($file), $count); + $content = str_replace($search, $replace, $this->filesystem->readFile($file), $count); if ($count) { file_put_contents($file, $content); } diff --git a/Command/CachePoolClearCommand.php b/Command/CachePoolClearCommand.php index 8b8b9cd35..d5320e7a9 100644 --- a/Command/CachePoolClearCommand.php +++ b/Command/CachePoolClearCommand.php @@ -32,18 +32,14 @@ #[AsCommand(name: 'cache:pool:clear', description: 'Clear cache pools')] final class CachePoolClearCommand extends Command { - private Psr6CacheClearer $poolClearer; - private ?array $poolNames; - /** * @param string[]|null $poolNames */ - public function __construct(Psr6CacheClearer $poolClearer, ?array $poolNames = null) - { + public function __construct( + private Psr6CacheClearer $poolClearer, + private ?array $poolNames = null, + ) { parent::__construct(); - - $this->poolClearer = $poolClearer; - $this->poolNames = $poolNames; } protected function configure(): void diff --git a/Command/CachePoolDeleteCommand.php b/Command/CachePoolDeleteCommand.php index dfa307bc0..e634e00f0 100644 --- a/Command/CachePoolDeleteCommand.php +++ b/Command/CachePoolDeleteCommand.php @@ -29,18 +29,14 @@ #[AsCommand(name: 'cache:pool:delete', description: 'Delete an item from a cache pool')] final class CachePoolDeleteCommand extends Command { - private Psr6CacheClearer $poolClearer; - private ?array $poolNames; - /** * @param string[]|null $poolNames */ - public function __construct(Psr6CacheClearer $poolClearer, ?array $poolNames = null) - { + public function __construct( + private Psr6CacheClearer $poolClearer, + private ?array $poolNames = null, + ) { parent::__construct(); - - $this->poolClearer = $poolClearer; - $this->poolNames = $poolNames; } protected function configure(): void diff --git a/Command/CachePoolInvalidateTagsCommand.php b/Command/CachePoolInvalidateTagsCommand.php index 9e6ef9330..f879a6d0d 100644 --- a/Command/CachePoolInvalidateTagsCommand.php +++ b/Command/CachePoolInvalidateTagsCommand.php @@ -30,14 +30,13 @@ #[AsCommand(name: 'cache:pool:invalidate-tags', description: 'Invalidate cache tags for all or a specific pool')] final class CachePoolInvalidateTagsCommand extends Command { - private ServiceProviderInterface $pools; private array $poolNames; - public function __construct(ServiceProviderInterface $pools) - { + public function __construct( + private ServiceProviderInterface $pools, + ) { parent::__construct(); - $this->pools = $pools; $this->poolNames = array_keys($pools->getProvidedServices()); } diff --git a/Command/CachePoolListCommand.php b/Command/CachePoolListCommand.php index 2659ad8fe..6b8e71eb0 100644 --- a/Command/CachePoolListCommand.php +++ b/Command/CachePoolListCommand.php @@ -25,16 +25,13 @@ #[AsCommand(name: 'cache:pool:list', description: 'List available cache pools')] final class CachePoolListCommand extends Command { - private array $poolNames; - /** * @param string[] $poolNames */ - public function __construct(array $poolNames) - { + public function __construct( + private array $poolNames, + ) { parent::__construct(); - - $this->poolNames = $poolNames; } protected function configure(): void diff --git a/Command/CachePoolPruneCommand.php b/Command/CachePoolPruneCommand.php index fc0dc6d79..fba1033f9 100644 --- a/Command/CachePoolPruneCommand.php +++ b/Command/CachePoolPruneCommand.php @@ -26,16 +26,13 @@ #[AsCommand(name: 'cache:pool:prune', description: 'Prune cache pools')] final class CachePoolPruneCommand extends Command { - private iterable $pools; - /** * @param iterable $pools */ - public function __construct(iterable $pools) - { + public function __construct( + private iterable $pools, + ) { parent::__construct(); - - $this->pools = $pools; } protected function configure(): void diff --git a/Command/CacheWarmupCommand.php b/Command/CacheWarmupCommand.php index 6f1073de4..a4b32a561 100644 --- a/Command/CacheWarmupCommand.php +++ b/Command/CacheWarmupCommand.php @@ -31,13 +31,10 @@ #[AsCommand(name: 'cache:warmup', description: 'Warm up an empty cache')] class CacheWarmupCommand extends Command { - private CacheWarmerAggregate $cacheWarmer; - - public function __construct(CacheWarmerAggregate $cacheWarmer) - { + public function __construct( + private CacheWarmerAggregate $cacheWarmer, + ) { parent::__construct(); - - $this->cacheWarmer = $cacheWarmer; } protected function configure(): void diff --git a/Command/ContainerLintCommand.php b/Command/ContainerLintCommand.php index b63ebe431..cd6e0657c 100644 --- a/Command/ContainerLintCommand.php +++ b/Command/ContainerLintCommand.php @@ -19,6 +19,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Compiler\CheckAliasValidityPass; use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\Compiler\ResolveFactoryClassPass; @@ -107,6 +108,7 @@ private function getContainerBuilder(): ContainerBuilder $container->setParameter('container.build_hash', 'lint_container'); $container->setParameter('container.build_id', 'lint_container'); + $container->addCompilerPass(new CheckAliasValidityPass(), PassConfig::TYPE_BEFORE_REMOVING, -100); $container->addCompilerPass(new CheckTypeDeclarationsPass(true), PassConfig::TYPE_AFTER_REMOVING, -100); return $this->container = $container; diff --git a/Command/DebugAutowiringCommand.php b/Command/DebugAutowiringCommand.php index f6efd8bef..77011b185 100644 --- a/Command/DebugAutowiringCommand.php +++ b/Command/DebugAutowiringCommand.php @@ -33,11 +33,10 @@ #[AsCommand(name: 'debug:autowiring', description: 'List classes/interfaces you can use for autowiring')] class DebugAutowiringCommand extends ContainerDebugCommand { - private ?FileLinkFormatter $fileLinkFormatter; - - public function __construct(?string $name = null, ?FileLinkFormatter $fileLinkFormatter = null) - { - $this->fileLinkFormatter = $fileLinkFormatter; + public function __construct( + ?string $name = null, + private ?FileLinkFormatter $fileLinkFormatter = null, + ) { parent::__construct($name); } diff --git a/Command/EventDispatcherDebugCommand.php b/Command/EventDispatcherDebugCommand.php index 1a74e8682..52816e7de 100644 --- a/Command/EventDispatcherDebugCommand.php +++ b/Command/EventDispatcherDebugCommand.php @@ -37,13 +37,10 @@ class EventDispatcherDebugCommand extends Command { private const DEFAULT_DISPATCHER = 'event_dispatcher'; - private ContainerInterface $dispatchers; - - public function __construct(ContainerInterface $dispatchers) - { + public function __construct( + private ContainerInterface $dispatchers, + ) { parent::__construct(); - - $this->dispatchers = $dispatchers; } protected function configure(): void diff --git a/Command/RouterDebugCommand.php b/Command/RouterDebugCommand.php index 9318b46be..54df49431 100644 --- a/Command/RouterDebugCommand.php +++ b/Command/RouterDebugCommand.php @@ -39,15 +39,11 @@ class RouterDebugCommand extends Command { use BuildDebugContainerTrait; - private RouterInterface $router; - private ?FileLinkFormatter $fileLinkFormatter; - - public function __construct(RouterInterface $router, ?FileLinkFormatter $fileLinkFormatter = null) - { + public function __construct( + private RouterInterface $router, + private ?FileLinkFormatter $fileLinkFormatter = null, + ) { parent::__construct(); - - $this->router = $router; - $this->fileLinkFormatter = $fileLinkFormatter; } protected function configure(): void diff --git a/Command/RouterMatchCommand.php b/Command/RouterMatchCommand.php index 7efd1f3ed..475b403ca 100644 --- a/Command/RouterMatchCommand.php +++ b/Command/RouterMatchCommand.php @@ -33,18 +33,14 @@ #[AsCommand(name: 'router:match', description: 'Help debug routes by simulating a path info match')] class RouterMatchCommand extends Command { - private RouterInterface $router; - private iterable $expressionLanguageProviders; - /** * @param iterable $expressionLanguageProviders */ - public function __construct(RouterInterface $router, iterable $expressionLanguageProviders = []) - { + public function __construct( + private RouterInterface $router, + private iterable $expressionLanguageProviders = [], + ) { parent::__construct(); - - $this->router = $router; - $this->expressionLanguageProviders = $expressionLanguageProviders; } protected function configure(): void diff --git a/Command/SecretsDecryptToLocalCommand.php b/Command/SecretsDecryptToLocalCommand.php index ac711e3db..f76e1d05a 100644 --- a/Command/SecretsDecryptToLocalCommand.php +++ b/Command/SecretsDecryptToLocalCommand.php @@ -28,14 +28,10 @@ #[AsCommand(name: 'secrets:decrypt-to-local', description: 'Decrypt all secrets and stores them in the local vault')] final class SecretsDecryptToLocalCommand extends Command { - private AbstractVault $vault; - private ?AbstractVault $localVault; - - public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) - { - $this->vault = $vault; - $this->localVault = $localVault; - + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { parent::__construct(); } diff --git a/Command/SecretsEncryptFromLocalCommand.php b/Command/SecretsEncryptFromLocalCommand.php index 46e0baffc..9740098e5 100644 --- a/Command/SecretsEncryptFromLocalCommand.php +++ b/Command/SecretsEncryptFromLocalCommand.php @@ -27,14 +27,10 @@ #[AsCommand(name: 'secrets:encrypt-from-local', description: 'Encrypt all local secrets to the vault')] final class SecretsEncryptFromLocalCommand extends Command { - private AbstractVault $vault; - private ?AbstractVault $localVault; - - public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) - { - $this->vault = $vault; - $this->localVault = $localVault; - + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { parent::__construct(); } diff --git a/Command/SecretsGenerateKeysCommand.php b/Command/SecretsGenerateKeysCommand.php index 989eff9fd..66a752eac 100644 --- a/Command/SecretsGenerateKeysCommand.php +++ b/Command/SecretsGenerateKeysCommand.php @@ -30,14 +30,10 @@ #[AsCommand(name: 'secrets:generate-keys', description: 'Generate new encryption keys')] final class SecretsGenerateKeysCommand extends Command { - private AbstractVault $vault; - private ?AbstractVault $localVault; - - public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) - { - $this->vault = $vault; - $this->localVault = $localVault; - + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { parent::__construct(); } diff --git a/Command/SecretsListCommand.php b/Command/SecretsListCommand.php index 9a24f4a90..cdfc51c39 100644 --- a/Command/SecretsListCommand.php +++ b/Command/SecretsListCommand.php @@ -31,14 +31,10 @@ #[AsCommand(name: 'secrets:list', description: 'List all secrets')] final class SecretsListCommand extends Command { - private AbstractVault $vault; - private ?AbstractVault $localVault; - - public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) - { - $this->vault = $vault; - $this->localVault = $localVault; - + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { parent::__construct(); } diff --git a/Command/SecretsRemoveCommand.php b/Command/SecretsRemoveCommand.php index 1789f2981..11660b00d 100644 --- a/Command/SecretsRemoveCommand.php +++ b/Command/SecretsRemoveCommand.php @@ -32,14 +32,10 @@ #[AsCommand(name: 'secrets:remove', description: 'Remove a secret from the vault')] final class SecretsRemoveCommand extends Command { - private AbstractVault $vault; - private ?AbstractVault $localVault; - - public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) - { - $this->vault = $vault; - $this->localVault = $localVault; - + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { parent::__construct(); } diff --git a/Command/SecretsRevealCommand.php b/Command/SecretsRevealCommand.php new file mode 100644 index 000000000..bcbdea11f --- /dev/null +++ b/Command/SecretsRevealCommand.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @internal + */ +#[AsCommand(name: 'secrets:reveal', description: 'Reveal the value of a secret')] +final class SecretsRevealCommand extends Command +{ + public function __construct( + private readonly AbstractVault $vault, + private readonly ?AbstractVault $localVault = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret to reveal', null, fn () => array_keys($this->vault->list())) + ->setHelp(<<<'EOF' +The %command.name% command reveals a stored secret. + + %command.full_name% +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + $secrets = $this->vault->list(true); + $localSecrets = $this->localVault?->list(true); + + $name = (string) $input->getArgument('name'); + + if (null !== $localSecrets && \array_key_exists($name, $localSecrets)) { + $io->writeln($localSecrets[$name]); + } else { + if (!\array_key_exists($name, $secrets)) { + $io->error(sprintf('The secret "%s" does not exist.', $name)); + + return self::INVALID; + } + + $io->writeln($secrets[$name]); + } + + return self::SUCCESS; + } +} diff --git a/Command/SecretsSetCommand.php b/Command/SecretsSetCommand.php index 2d2b8c5cb..49a20af76 100644 --- a/Command/SecretsSetCommand.php +++ b/Command/SecretsSetCommand.php @@ -33,14 +33,10 @@ #[AsCommand(name: 'secrets:set', description: 'Set a secret in the vault')] final class SecretsSetCommand extends Command { - private AbstractVault $vault; - private ?AbstractVault $localVault; - - public function __construct(AbstractVault $vault, ?AbstractVault $localVault = null) - { - $this->vault = $vault; - $this->localVault = $localVault; - + public function __construct( + private AbstractVault $vault, + private ?AbstractVault $localVault = null, + ) { parent::__construct(); } diff --git a/Command/TranslationDebugCommand.php b/Command/TranslationDebugCommand.php index ecb0ad8d7..2438d3feb 100644 --- a/Command/TranslationDebugCommand.php +++ b/Command/TranslationDebugCommand.php @@ -50,27 +50,17 @@ class TranslationDebugCommand extends Command public const MESSAGE_UNUSED = 1; public const MESSAGE_EQUALS_FALLBACK = 2; - private TranslatorInterface $translator; - private TranslationReaderInterface $reader; - private ExtractorInterface $extractor; - private ?string $defaultTransPath; - private ?string $defaultViewsPath; - private array $transPaths; - private array $codePaths; - private array $enabledLocales; - - public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, ?string $defaultTransPath = null, ?string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) - { + public function __construct( + private TranslatorInterface $translator, + private TranslationReaderInterface $reader, + private ExtractorInterface $extractor, + private ?string $defaultTransPath = null, + private ?string $defaultViewsPath = null, + private array $transPaths = [], + private array $codePaths = [], + private array $enabledLocales = [], + ) { parent::__construct(); - - $this->translator = $translator; - $this->reader = $reader; - $this->extractor = $extractor; - $this->defaultTransPath = $defaultTransPath; - $this->defaultViewsPath = $defaultViewsPath; - $this->transPaths = $transPaths; - $this->codePaths = $codePaths; - $this->enabledLocales = $enabledLocales; } protected function configure(): void @@ -223,8 +213,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - if (!\in_array(self::MESSAGE_UNUSED, $states) && $input->getOption('only-unused') - || !\in_array(self::MESSAGE_MISSING, $states) && $input->getOption('only-missing') + if (!\in_array(self::MESSAGE_UNUSED, $states, true) && $input->getOption('only-unused') + || !\in_array(self::MESSAGE_MISSING, $states, true) && $input->getOption('only-missing') ) { continue; } diff --git a/Command/TranslationUpdateCommand.php b/Command/TranslationUpdateCommand.php index fc95e0217..1a883f81e 100644 --- a/Command/TranslationUpdateCommand.php +++ b/Command/TranslationUpdateCommand.php @@ -50,29 +50,18 @@ class TranslationUpdateCommand extends Command 'xlf20' => ['xlf', '2.0'], ]; - private TranslationWriterInterface $writer; - private TranslationReaderInterface $reader; - private ExtractorInterface $extractor; - private string $defaultLocale; - private ?string $defaultTransPath; - private ?string $defaultViewsPath; - private array $transPaths; - private array $codePaths; - private array $enabledLocales; - - public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, ?string $defaultTransPath = null, ?string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) - { + public function __construct( + private TranslationWriterInterface $writer, + private TranslationReaderInterface $reader, + private ExtractorInterface $extractor, + private string $defaultLocale, + private ?string $defaultTransPath = null, + private ?string $defaultViewsPath = null, + private array $transPaths = [], + private array $codePaths = [], + private array $enabledLocales = [], + ) { parent::__construct(); - - $this->writer = $writer; - $this->reader = $reader; - $this->extractor = $extractor; - $this->defaultLocale = $defaultLocale; - $this->defaultTransPath = $defaultTransPath; - $this->defaultViewsPath = $defaultViewsPath; - $this->transPaths = $transPaths; - $this->codePaths = $codePaths; - $this->enabledLocales = $enabledLocales; } protected function configure(): void diff --git a/Console/Application.php b/Console/Application.php index 14a907e2e..1c41849e7 100644 --- a/Console/Application.php +++ b/Console/Application.php @@ -30,14 +30,12 @@ */ class Application extends BaseApplication { - private KernelInterface $kernel; private bool $commandsRegistered = false; private array $registrationErrors = []; - public function __construct(KernelInterface $kernel) - { - $this->kernel = $kernel; - + public function __construct( + private KernelInterface $kernel, + ) { parent::__construct('Symfony', Kernel::VERSION); $inputDefinition = $this->getDefinition(); diff --git a/Console/Descriptor/JsonDescriptor.php b/Console/Descriptor/JsonDescriptor.php index 3f5085ef2..88cf4162c 100644 --- a/Console/Descriptor/JsonDescriptor.php +++ b/Console/Descriptor/JsonDescriptor.php @@ -323,7 +323,7 @@ private function getContainerAliasData(Alias $alias): array private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, array $options): array { $data = []; - $event = \array_key_exists('event', $options) ? $options['event'] : null; + $event = $options['event'] ?? null; if (null !== $event) { foreach ($eventDispatcher->getListeners($event) as $listener) { @@ -393,7 +393,7 @@ private function getCallableData(mixed $callable): array $data['type'] = 'closure'; $r = new \ReflectionFunction($callable); - if (str_contains($r->name, '{closure')) { + if ($r->isAnonymous()) { return $data; } $data['name'] = $r->name; diff --git a/Console/Descriptor/MarkdownDescriptor.php b/Console/Descriptor/MarkdownDescriptor.php index 7537e3d42..7965990bd 100644 --- a/Console/Descriptor/MarkdownDescriptor.php +++ b/Console/Descriptor/MarkdownDescriptor.php @@ -403,7 +403,7 @@ protected function describeCallable(mixed $callable, array $options = []): void $string .= "\n- Type: `closure`"; $r = new \ReflectionFunction($callable); - if (str_contains($r->name, '{closure')) { + if ($r->isAnonymous()) { $this->write($string."\n"); return; diff --git a/Console/Descriptor/TextDescriptor.php b/Console/Descriptor/TextDescriptor.php index 60834250b..d728128ce 100644 --- a/Console/Descriptor/TextDescriptor.php +++ b/Console/Descriptor/TextDescriptor.php @@ -38,11 +38,9 @@ */ class TextDescriptor extends Descriptor { - private ?FileLinkFormatter $fileLinkFormatter; - - public function __construct(?FileLinkFormatter $fileLinkFormatter = null) - { - $this->fileLinkFormatter = $fileLinkFormatter; + public function __construct( + private ?FileLinkFormatter $fileLinkFormatter = null, + ) { } protected function describeRouteCollection(RouteCollection $routes, array $options = []): void @@ -649,7 +647,7 @@ private function formatCallable(mixed $callable): string if ($callable instanceof \Closure) { $r = new \ReflectionFunction($callable); - if (str_contains($r->name, '{closure')) { + if ($r->isAnonymous()) { return 'Closure()'; } if ($class = $r->getClosureCalledClass()) { diff --git a/Console/Descriptor/XmlDescriptor.php b/Console/Descriptor/XmlDescriptor.php index 05848b1b2..c52b19667 100644 --- a/Console/Descriptor/XmlDescriptor.php +++ b/Console/Descriptor/XmlDescriptor.php @@ -501,7 +501,7 @@ private function getContainerParameterDocument(mixed $parameter, ?array $depreca private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, array $options): \DOMDocument { - $event = \array_key_exists('event', $options) ? $options['event'] : null; + $event = $options['event'] ?? null; $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($eventDispatcherXML = $dom->createElement('event-dispatcher')); @@ -581,7 +581,7 @@ private function getCallableDocument(mixed $callable): \DOMDocument $callableXML->setAttribute('type', 'closure'); $r = new \ReflectionFunction($callable); - if (str_contains($r->name, '{closure')) { + if ($r->isAnonymous()) { return $dom; } $callableXML->setAttribute('name', $r->name); diff --git a/Controller/RedirectController.php b/Controller/RedirectController.php index fbb52ead7..1001fad63 100644 --- a/Controller/RedirectController.php +++ b/Controller/RedirectController.php @@ -27,15 +27,11 @@ */ class RedirectController { - private ?UrlGeneratorInterface $router; - private ?int $httpPort; - private ?int $httpsPort; - - public function __construct(?UrlGeneratorInterface $router = null, ?int $httpPort = null, ?int $httpsPort = null) - { - $this->router = $router; - $this->httpPort = $httpPort; - $this->httpsPort = $httpsPort; + public function __construct( + private ?UrlGeneratorInterface $router = null, + private ?int $httpPort = null, + private ?int $httpsPort = null, + ) { } /** diff --git a/Controller/TemplateController.php b/Controller/TemplateController.php index 97631572c..bcbcc382d 100644 --- a/Controller/TemplateController.php +++ b/Controller/TemplateController.php @@ -23,11 +23,9 @@ */ class TemplateController { - private ?Environment $twig; - - public function __construct(?Environment $twig = null) - { - $this->twig = $twig; + public function __construct( + private ?Environment $twig = null, + ) { } /** diff --git a/DependencyInjection/Compiler/UnusedTagsPass.php b/DependencyInjection/Compiler/UnusedTagsPass.php index 1d21c6b66..a2a141afb 100644 --- a/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/DependencyInjection/Compiler/UnusedTagsPass.php @@ -82,6 +82,7 @@ class UnusedTagsPass implements CompilerPassInterface 'routing.route_loader', 'scheduler.schedule_provider', 'scheduler.task', + 'security.access_token_handler.oidc.signature_algorithm', 'security.authenticator.login_linker', 'security.expression_language_provider', 'security.remember_me_handler', @@ -110,7 +111,7 @@ public function process(ContainerBuilder $container): void foreach ($container->findUnusedTags() as $tag) { // skip known tags - if (\in_array($tag, self::KNOWN_TAGS)) { + if (\in_array($tag, self::KNOWN_TAGS, true)) { continue; } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 6a006845f..92c20d139 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -29,6 +29,7 @@ use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\IpUtils; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; use Symfony\Component\Mailer\Mailer; @@ -43,6 +44,7 @@ use Symfony\Component\Serializer\Encoder\JsonDecode; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\Translator; +use Symfony\Component\TypeInfo\Type; use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Validator\Validation; use Symfony\Component\Webhook\Controller\WebhookController; @@ -54,14 +56,12 @@ */ class Configuration implements ConfigurationInterface { - private bool $debug; - /** * @param bool $debug Whether debugging is enabled or not */ - public function __construct(bool $debug) - { - $this->debug = $debug; + public function __construct( + private bool $debug, + ) { } /** @@ -111,7 +111,12 @@ public function getConfigTreeBuilder(): TreeBuilder ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() ->prototype('scalar')->end() ->end() - ->scalarNode('trusted_proxies')->end() + ->scalarNode('trusted_proxies') + ->beforeNormalization() + ->ifTrue(fn ($v) => 'private_ranges' === $v) + ->then(fn ($v) => implode(',', IpUtils::PRIVATE_SUBNETS)) + ->end() + ->end() ->arrayNode('trusted_headers') ->fixXmlConfig('trusted_header') ->performNoDeepMerging() @@ -158,6 +163,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addAnnotationsSection($rootNode); $this->addSerializerSection($rootNode, $enableIfStandalone); $this->addPropertyAccessSection($rootNode, $willBeAvailable); + $this->addTypeInfoSection($rootNode, $enableIfStandalone); $this->addPropertyInfoSection($rootNode, $enableIfStandalone); $this->addCacheSection($rootNode, $willBeAvailable); $this->addPhpErrorsSection($rootNode); @@ -435,7 +441,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void if (!\is_string($value)) { return true; } - if (class_exists(WorkflowEvents::class) && !\in_array($value, WorkflowEvents::ALIASES)) { + if (class_exists(WorkflowEvents::class) && !\in_array($value, WorkflowEvents::ALIASES, true)) { return true; } } @@ -478,8 +484,6 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void return array_values($places); }) ->end() - ->isRequired() - ->requiresAtLeastOneElement() ->prototype('array') ->children() ->scalarNode('name') @@ -613,7 +617,10 @@ private function addRouterSection(ArrayNodeDefinition $rootNode): void ->children() ->scalarNode('resource')->isRequired()->end() ->scalarNode('type')->end() - ->scalarNode('cache_dir')->defaultValue('%kernel.cache_dir%')->end() + ->scalarNode('cache_dir') + ->defaultValue('%kernel.build_dir%') + ->setDeprecated('symfony/framework-bundle', '7.1', 'Setting the "%path%.%node%" configuration option is deprecated. It will be removed in version 8.0.') + ->end() ->scalarNode('default_uri') ->info('The default URI used to generate URLs in a non-HTTP context') ->defaultNull() @@ -1111,7 +1118,6 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $e ->end() ->arrayNode('default_context') ->normalizeKeys(false) - ->useAttributeAsKey('name') ->beforeNormalization() ->ifTrue(fn () => $this->debug && class_exists(JsonParser::class)) ->then(fn (array $v) => $v + [JsonDecode::DETAILED_ERROR_MESSAGES => true]) @@ -1157,6 +1163,18 @@ private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable ; } + private function addTypeInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('type_info') + ->info('Type info configuration') + ->{$enableIfStandalone('symfony/type-info', Type::class)}() + ->end() + ->end() + ; + } + private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void { $rootNode @@ -1587,6 +1605,7 @@ function ($a) { ->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end() ->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries))')->end() ->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end() + ->floatNode('jitter')->defaultValue(0.1)->min(0)->max(1)->info('Randomness to apply to the delay (between 0 and 1)')->end() ->end() ->end() ->scalarNode('rate_limiter') @@ -1710,17 +1729,32 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->fixXmlConfig('scoped_client') ->beforeNormalization() ->always(function ($config) { - if (empty($config['scoped_clients']) || !\is_array($config['default_options']['retry_failed'] ?? null)) { + if (empty($config['scoped_clients'])) { + return $config; + } + + $hasDefaultRateLimiter = isset($config['default_options']['rate_limiter']); + $hasDefaultRetryFailed = \is_array($config['default_options']['retry_failed'] ?? null); + + if (!$hasDefaultRateLimiter && !$hasDefaultRetryFailed) { return $config; } foreach ($config['scoped_clients'] as &$scopedConfig) { - if (!isset($scopedConfig['retry_failed']) || true === $scopedConfig['retry_failed']) { - $scopedConfig['retry_failed'] = $config['default_options']['retry_failed']; - continue; + if ($hasDefaultRateLimiter) { + if (!isset($scopedConfig['rate_limiter']) || true === $scopedConfig['rate_limiter']) { + $scopedConfig['rate_limiter'] = $config['default_options']['rate_limiter']; + } elseif (false === $scopedConfig['rate_limiter']) { + $scopedConfig['rate_limiter'] = null; + } } - if (\is_array($scopedConfig['retry_failed'])) { - $scopedConfig['retry_failed'] += $config['default_options']['retry_failed']; + + if ($hasDefaultRetryFailed) { + if (!isset($scopedConfig['retry_failed']) || true === $scopedConfig['retry_failed']) { + $scopedConfig['retry_failed'] = $config['default_options']['retry_failed']; + } elseif (\is_array($scopedConfig['retry_failed'])) { + $scopedConfig['retry_failed'] += $config['default_options']['retry_failed']; + } } } @@ -1825,6 +1859,10 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->normalizeKeys(false) ->variablePrototype()->end() ->end() + ->scalarNode('rate_limiter') + ->defaultNull() + ->info('Rate limiter name to use for throttling requests') + ->end() ->append($this->createHttpClientRetrySection()) ->end() ->end() @@ -1973,6 +2011,10 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->normalizeKeys(false) ->variablePrototype()->end() ->end() + ->scalarNode('rate_limiter') + ->defaultNull() + ->info('Rate limiter name to use for throttling requests') + ->end() ->append($this->createHttpClientRetrySection()) ->end() ->end() @@ -2075,12 +2117,23 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl ->arrayNode('envelope') ->info('Mailer Envelope configuration') ->fixXmlConfig('recipient') + ->fixXmlConfig('allowed_recipient') ->children() ->scalarNode('sender')->end() ->arrayNode('recipients') ->performNoDeepMerging() ->beforeNormalization() - ->ifArray() + ->ifArray() + ->then(fn ($v) => array_filter(array_values($v))) + ->end() + ->prototype('scalar')->end() + ->end() + ->arrayNode('allowed_recipients') + ->info('A list of regular expressions that allow recipients when "recipients" option is defined.') + ->example(['.*@example\.com']) + ->performNoDeepMerging() + ->beforeNormalization() + ->ifArray() ->then(fn ($v) => array_filter(array_values($v))) ->end() ->prototype('scalar')->end() diff --git a/DependencyInjection/FrameworkExtension.php b/DependencyInjection/FrameworkExtension.php index de513066c..8786d04bd 100644 --- a/DependencyInjection/FrameworkExtension.php +++ b/DependencyInjection/FrameworkExtension.php @@ -53,6 +53,7 @@ use Symfony\Component\Console\Debug\CliRequest; use Symfony\Component\Console\Messenger\RunCommandMessageHandler; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -70,6 +71,7 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Glob; use Symfony\Component\Form\Extension\HtmlSanitizer\Type\TextTypeHtmlSanitizerExtension; @@ -85,6 +87,7 @@ use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; use Symfony\Component\HttpClient\RetryableHttpClient; use Symfony\Component\HttpClient\ScopingHttpClient; +use Symfony\Component\HttpClient\ThrottlingHttpClient; use Symfony\Component\HttpClient\UriTemplateHttpClient; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\AsController; @@ -167,6 +170,8 @@ use Symfony\Component\Translation\LocaleSwitcher; use Symfony\Component\Translation\PseudoLocalizationTranslator; use Symfony\Component\Translation\Translator; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver; use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Uid\UuidV4; use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider; @@ -345,6 +350,7 @@ public function load(array $configs, ContainerBuilder $container): void } if ($this->readConfigEnabled('http_client', $container, $config['http_client'])) { + $this->readConfigEnabled('rate_limiter', $container, $config['rate_limiter']); // makes sure that isInitializedConfigEnabled() will work $this->registerHttpClientConfiguration($config['http_client'], $container, $loader); } @@ -388,6 +394,10 @@ public function load(array $configs, ContainerBuilder $container): void $container->removeDefinition('console.command.serializer_debug'); } + if ($this->readConfigEnabled('type_info', $container, $config['type_info'])) { + $this->registerTypeInfoConfiguration($container, $loader); + } + if ($propertyInfoEnabled) { $this->registerPropertyInfoConfiguration($container, $loader); } @@ -689,9 +699,9 @@ public function load(array $configs, ContainerBuilder $container): void $taskAttributeClass, static function (ChildDefinition $definition, AsPeriodicTask|AsCronTask $attribute, \ReflectionClass|\ReflectionMethod $reflector): void { $tagAttributes = get_object_vars($attribute) + [ - 'trigger' => match ($attribute::class) { - AsPeriodicTask::class => 'every', - AsCronTask::class => 'cron', + 'trigger' => match (true) { + $attribute instanceof AsPeriodicTask => 'every', + $attribute instanceof AsCronTask => 'cron', }, ]; if ($reflector instanceof \ReflectionMethod) { @@ -1020,7 +1030,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $workflowDefinition->replaceArgument(3, $name); $workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']); - $workflowDefinition->addTag('workflow', ['name' => $name]); + $workflowDefinition->addTag('workflow', ['name' => $name, 'metadata' => $workflow['metadata']]); if ('workflow' === $type) { $workflowDefinition->addTag('workflow.workflow', ['name' => $name]); } elseif ('state_machine' === $type) { @@ -1094,29 +1104,6 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $container->setParameter('workflow.has_guard_listeners', true); } } - - $listenerAttributes = [ - Workflow\Attribute\AsAnnounceListener::class, - Workflow\Attribute\AsCompletedListener::class, - Workflow\Attribute\AsEnterListener::class, - Workflow\Attribute\AsEnteredListener::class, - Workflow\Attribute\AsGuardListener::class, - Workflow\Attribute\AsLeaveListener::class, - Workflow\Attribute\AsTransitionListener::class, - ]; - - foreach ($listenerAttributes as $attribute) { - $container->registerAttributeForAutoconfiguration($attribute, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) { - $tagAttributes = get_object_vars($attribute); - if ($reflector instanceof \ReflectionMethod) { - if (isset($tagAttributes['method'])) { - throw new LogicException(sprintf('"%s" attribute cannot declare a method on "%s::%s()".', $attribute::class, $reflector->class, $reflector->name)); - } - $tagAttributes['method'] = $reflector->getName(); - } - $definition->addTag('kernel.event_listener', $tagAttributes); - }); - } } private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void @@ -1773,6 +1760,7 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c if (!$this->readConfigEnabled('secrets', $container, $config)) { $container->removeDefinition('console.command.secrets_set'); $container->removeDefinition('console.command.secrets_list'); + $container->removeDefinition('console.command.secrets_reveal'); $container->removeDefinition('console.command.secrets_remove'); $container->removeDefinition('console.command.secrets_generate_key'); $container->removeDefinition('console.command.secrets_decrypt_to_local'); @@ -1954,6 +1942,25 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container, } } + private function registerTypeInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!class_exists(Type::class)) { + throw new LogicException('TypeInfo support cannot be enabled as the TypeInfo component is not installed. Try running "composer require symfony/type-info".'); + } + + $loader->load('type_info.php'); + + if (ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/type-info'])) { + $container->register('type_info.resolver.string', StringTypeResolver::class); + + /** @var ServiceLocatorArgument $resolversLocator */ + $resolversLocator = $container->getDefinition('type_info.resolver')->getArgument(0); + $resolversLocator->setValues($resolversLocator->getValues() + [ + 'string' => new Reference('type_info.resolver.string'), + ]); + } + } + private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { $loader->load('lock.php'); @@ -2180,7 +2187,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder ->setArguments([$transport['dsn'], $transport['options'] + ['transport_name' => $name], new Reference($serializerId)]) ->addTag('messenger.receiver', [ 'alias' => $name, - 'is_failure_transport' => \in_array($name, $failureTransports), + 'is_failure_transport' => \in_array($name, $failureTransports, true), ]) ; $container->setDefinition($transportId = 'messenger.transport.'.$name, $transportDefinition); @@ -2195,7 +2202,8 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder ->replaceArgument(0, $transport['retry_strategy']['max_retries']) ->replaceArgument(1, $transport['retry_strategy']['delay']) ->replaceArgument(2, $transport['retry_strategy']['multiplier']) - ->replaceArgument(3, $transport['retry_strategy']['max_delay']); + ->replaceArgument(3, $transport['retry_strategy']['max_delay']) + ->replaceArgument(4, $transport['retry_strategy']['jitter']); $container->setDefinition($retryServiceId, $retryDefinition); $transportRetryReferences[$name] = new Reference($retryServiceId); @@ -2407,6 +2415,8 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $loader->load('http_client.php'); $options = $config['default_options'] ?? []; + $rateLimiter = $options['rate_limiter'] ?? null; + unset($options['rate_limiter']); $retryOptions = $options['retry_failed'] ?? ['enabled' => false]; unset($options['retry_failed']); $defaultUriTemplateVars = $options['vars'] ?? []; @@ -2428,6 +2438,10 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $container->removeAlias(HttpClient::class); } + if (null !== $rateLimiter) { + $this->registerThrottlingHttpClient($rateLimiter, 'http_client', $container); + } + if ($this->readConfigEnabled('http_client.retry_failed', $container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, 'http_client', $container); } @@ -2449,6 +2463,8 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $scope = $scopeConfig['scope'] ?? null; unset($scopeConfig['scope']); + $rateLimiter = $scopeConfig['rate_limiter'] ?? null; + unset($scopeConfig['rate_limiter']); $retryOptions = $scopeConfig['retry_failed'] ?? ['enabled' => false]; unset($scopeConfig['retry_failed']); @@ -2468,6 +2484,10 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder ; } + if (null !== $rateLimiter) { + $this->registerThrottlingHttpClient($rateLimiter, $name, $container); + } + if ($this->readConfigEnabled('http_client.scoped_clients.'.$name.'.retry_failed', $container, $retryOptions)) { $this->registerRetryableHttpClient($retryOptions, $name, $container); } @@ -2505,6 +2525,25 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder } } + private function registerThrottlingHttpClient(string $rateLimiter, string $name, ContainerBuilder $container): void + { + if (!class_exists(ThrottlingHttpClient::class)) { + throw new LogicException('Rate limiter support cannot be enabled as version 7.1+ of the HttpClient component is required.'); + } + + if (!$this->isInitializedConfigEnabled('rate_limiter')) { + throw new LogicException('Rate limiter cannot be used within HttpClient as the RateLimiter component is not enabled.'); + } + + $container->register($name.'.throttling.limiter', LimiterInterface::class) + ->setFactory([new Reference('limiter.'.$rateLimiter), 'create']); + + $container + ->register($name.'.throttling', ThrottlingHttpClient::class) + ->setDecoratedService($name, null, 15) // higher priority than RetryableHttpClient (10) + ->setArguments([new Reference($name.'.throttling.inner'), new Reference($name.'.throttling.limiter')]); + } + private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container): void { if (null !== $options['retry_strategy']) { @@ -2561,6 +2600,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co } $classToServices = [ + MailerBridge\Azure\Transport\AzureTransportFactory::class => 'mailer.transport_factory.azure', MailerBridge\Brevo\Transport\BrevoTransportFactory::class => 'mailer.transport_factory.brevo', MailerBridge\Google\Transport\GmailTransportFactory::class => 'mailer.transport_factory.gmail', MailerBridge\Infobip\Transport\InfobipTransportFactory::class => 'mailer.transport_factory.infobip', @@ -2570,6 +2610,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co MailerBridge\MailPace\Transport\MailPaceTransportFactory::class => 'mailer.transport_factory.mailpace', MailerBridge\Mailchimp\Transport\MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', MailerBridge\Postmark\Transport\PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', + MailerBridge\Resend\Transport\ResendTransportFactory::class => 'mailer.transport_factory.resend', MailerBridge\Scaleway\Transport\ScalewayTransportFactory::class => 'mailer.transport_factory.scaleway', MailerBridge\Sendgrid\Transport\SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid', MailerBridge\Amazon\Transport\SesTransportFactory::class => 'mailer.transport_factory.amazon', @@ -2586,9 +2627,11 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co if ($webhookEnabled) { $webhookRequestParsers = [ MailerBridge\Brevo\Webhook\BrevoRequestParser::class => 'mailer.webhook.request_parser.brevo', + MailerBridge\MailerSend\Webhook\MailerSendRequestParser::class => 'mailer.webhook.request_parser.mailersend', MailerBridge\Mailgun\Webhook\MailgunRequestParser::class => 'mailer.webhook.request_parser.mailgun', MailerBridge\Mailjet\Webhook\MailjetRequestParser::class => 'mailer.webhook.request_parser.mailjet', MailerBridge\Postmark\Webhook\PostmarkRequestParser::class => 'mailer.webhook.request_parser.postmark', + MailerBridge\Resend\Webhook\ResendRequestParser::class => 'mailer.webhook.request_parser.resend', MailerBridge\Sendgrid\Webhook\SendgridRequestParser::class => 'mailer.webhook.request_parser.sendgrid', ]; @@ -2604,6 +2647,7 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co $envelopeListener = $container->getDefinition('mailer.envelope_listener'); $envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null); $envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null); + $envelopeListener->setArgument(2, $config['envelope']['allowed_recipients'] ?? []); if ($config['headers']) { $headers = new Definition(Headers::class); @@ -2696,6 +2740,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ NotifierBridge\AllMySms\AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms', NotifierBridge\AmazonSns\AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', NotifierBridge\Bandwidth\BandwidthTransportFactory::class => 'notifier.transport_factory.bandwidth', + NotifierBridge\Bluesky\BlueskyTransportFactory::class => 'notifier.transport_factory.bluesky', NotifierBridge\Brevo\BrevoTransportFactory::class => 'notifier.transport_factory.brevo', NotifierBridge\Chatwork\ChatworkTransportFactory::class => 'notifier.transport_factory.chatwork', NotifierBridge\Clickatell\ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', @@ -2721,6 +2766,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ NotifierBridge\LightSms\LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', NotifierBridge\LineNotify\LineNotifyTransportFactory::class => 'notifier.transport_factory.line-notify', NotifierBridge\LinkedIn\LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', + NotifierBridge\Lox24\Lox24TransportFactory::class => 'notifier.transport_factory.lox24', NotifierBridge\Mailjet\MailjetTransportFactory::class => 'notifier.transport_factory.mailjet', NotifierBridge\Mastodon\MastodonTransportFactory::class => 'notifier.transport_factory.mastodon', NotifierBridge\Mattermost\MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', @@ -2738,19 +2784,24 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ NotifierBridge\PagerDuty\PagerDutyTransportFactory::class => 'notifier.transport_factory.pager-duty', NotifierBridge\Plivo\PlivoTransportFactory::class => 'notifier.transport_factory.plivo', NotifierBridge\Pushover\PushoverTransportFactory::class => 'notifier.transport_factory.pushover', + NotifierBridge\Pushy\PushyTransportFactory::class => 'notifier.transport_factory.pushy', NotifierBridge\Redlink\RedlinkTransportFactory::class => 'notifier.transport_factory.redlink', NotifierBridge\RingCentral\RingCentralTransportFactory::class => 'notifier.transport_factory.ring-central', NotifierBridge\RocketChat\RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', NotifierBridge\Sendberry\SendberryTransportFactory::class => 'notifier.transport_factory.sendberry', NotifierBridge\SimpleTextin\SimpleTextinTransportFactory::class => 'notifier.transport_factory.simple-textin', + NotifierBridge\Sevenio\SevenIoTransportFactory::class => 'notifier.transport_factory.sevenio', NotifierBridge\Sinch\SinchTransportFactory::class => 'notifier.transport_factory.sinch', NotifierBridge\Slack\SlackTransportFactory::class => 'notifier.transport_factory.slack', NotifierBridge\Sms77\Sms77TransportFactory::class => 'notifier.transport_factory.sms77', NotifierBridge\Smsapi\SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi', NotifierBridge\SmsBiuras\SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras', + NotifierBridge\Smsbox\SmsboxTransportFactory::class => 'notifier.transport_factory.smsbox', NotifierBridge\Smsc\SmscTransportFactory::class => 'notifier.transport_factory.smsc', NotifierBridge\SmsFactor\SmsFactorTransportFactory::class => 'notifier.transport_factory.sms-factor', NotifierBridge\Smsmode\SmsmodeTransportFactory::class => 'notifier.transport_factory.smsmode', + NotifierBridge\SmsSluzba\SmsSluzbaTransportFactory::class => 'notifier.transport_factory.sms-sluzba', + NotifierBridge\Smsense\SmsenseTransportFactory::class => 'notifier.transport_factory.smsense', NotifierBridge\SpotHit\SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit', NotifierBridge\Telegram\TelegramTransportFactory::class => 'notifier.transport_factory.telegram', NotifierBridge\Telnyx\TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', @@ -2758,6 +2809,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ NotifierBridge\TurboSms\TurboSmsTransportFactory::class => 'notifier.transport_factory.turbo-sms', NotifierBridge\Twilio\TwilioTransportFactory::class => 'notifier.transport_factory.twilio', NotifierBridge\Twitter\TwitterTransportFactory::class => 'notifier.transport_factory.twitter', + NotifierBridge\Unifonic\UnifonicTransportFactory::class => 'notifier.transport_factory.unifonic', NotifierBridge\Vonage\VonageTransportFactory::class => 'notifier.transport_factory.vonage', NotifierBridge\Yunpian\YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', NotifierBridge\Zendesk\ZendeskTransportFactory::class => 'notifier.transport_factory.zendesk', @@ -2864,7 +2916,8 @@ private function registerRateLimiterConfiguration(array $config, ContainerBuilde // default configuration (when used by other DI extensions) $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; - $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')); + $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')) + ->addTag('rate_limiter', ['name' => $name]); if (null !== $limiterConfig['lock_factory']) { if (!interface_exists(LockInterface::class)) { @@ -3068,7 +3121,7 @@ private function getPublicDirectory(ContainerBuilder $container): string } $container->addResource(new FileResource($composerFilePath)); - $composerConfig = json_decode(file_get_contents($composerFilePath), true); + $composerConfig = json_decode((new Filesystem())->readFile($composerFilePath), true, flags: \JSON_THROW_ON_ERROR); return isset($composerConfig['extra']['public-dir']) ? $projectDir.'/'.$composerConfig['extra']['public-dir'] : $defaultPublicDir; } diff --git a/EventListener/ConsoleProfilerListener.php b/EventListener/ConsoleProfilerListener.php index c2a71d0a7..7bf23f04c 100644 --- a/EventListener/ConsoleProfilerListener.php +++ b/EventListener/ConsoleProfilerListener.php @@ -77,7 +77,7 @@ public function initialize(ConsoleCommandEvent $event): void return; } - $request->attributes->set('_stopwatch_token', substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6)); + $request->attributes->set('_stopwatch_token', substr(hash('xxh128', uniqid(mt_rand(), true)), 0, 6)); $this->stopwatch->openSection(); } diff --git a/HttpCache/HttpCache.php b/HttpCache/HttpCache.php index 38eee7361..f163708cc 100644 --- a/HttpCache/HttpCache.php +++ b/HttpCache/HttpCache.php @@ -28,19 +28,19 @@ class HttpCache extends BaseHttpCache { protected ?string $cacheDir = null; - protected KernelInterface $kernel; private ?StoreInterface $store = null; - private ?SurrogateInterface $surrogate; private array $options; /** * @param $cache The cache directory (default used if null) or the storage instance */ - public function __construct(KernelInterface $kernel, string|StoreInterface|null $cache = null, ?SurrogateInterface $surrogate = null, ?array $options = null) - { - $this->kernel = $kernel; - $this->surrogate = $surrogate; + public function __construct( + protected KernelInterface $kernel, + string|StoreInterface|null $cache = null, + private ?SurrogateInterface $surrogate = null, + ?array $options = null, + ) { $this->options = $options ?? []; if ($cache instanceof StoreInterface) { diff --git a/Kernel/MicroKernelTrait.php b/Kernel/MicroKernelTrait.php index f8c746013..b3f49c059 100644 --- a/Kernel/MicroKernelTrait.php +++ b/Kernel/MicroKernelTrait.php @@ -214,7 +214,7 @@ public function loadRoutes(LoaderInterface $loader): RouteCollection if (\is_array($controller) && [0, 1] === array_keys($controller) && $this === $controller[0]) { $route->setDefault('_controller', ['kernel', $controller[1]]); - } elseif ($controller instanceof \Closure && $this === ($r = new \ReflectionFunction($controller))->getClosureThis() && !str_contains($r->name, '{closure')) { + } elseif ($controller instanceof \Closure && $this === ($r = new \ReflectionFunction($controller))->getClosureThis() && !$r->isAnonymous()) { $route->setDefault('_controller', ['kernel', $r->name]); } } diff --git a/Resources/config/console.php b/Resources/config/console.php index 334d20426..b4f7dfcf3 100644 --- a/Resources/config/console.php +++ b/Resources/config/console.php @@ -33,6 +33,7 @@ use Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand; use Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand; use Symfony\Bundle\FrameworkBundle\Command\SecretsRemoveCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsRevealCommand; use Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand; use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand; @@ -355,6 +356,13 @@ ]) ->tag('console.command') + ->set('console.command.secrets_reveal', SecretsRevealCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + ->set('console.command.secrets_decrypt_to_local', SecretsDecryptToLocalCommand::class) ->args([ service('secrets.vault'), diff --git a/Resources/config/mailer_transports.php b/Resources/config/mailer_transports.php index 06c9632d8..5434b4c56 100644 --- a/Resources/config/mailer_transports.php +++ b/Resources/config/mailer_transports.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; +use Symfony\Component\Mailer\Bridge\Azure\Transport\AzureTransportFactory; use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; use Symfony\Component\Mailer\Bridge\Infobip\Transport\InfobipTransportFactory; @@ -21,6 +22,7 @@ use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory; use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendTransportFactory; use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory; use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; use Symfony\Component\Mailer\Transport\AbstractTransportFactory; @@ -38,69 +40,33 @@ service('http_client')->ignoreOnInvalid(), service('logger')->ignoreOnInvalid(), ]) - ->tag('monolog.logger', ['channel' => 'mailer']) - - ->set('mailer.transport_factory.amazon', SesTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.brevo', BrevoTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.gmail', GmailTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.infobip', InfobipTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.mailersend', MailerSendTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.mailchimp', MandrillTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.mailjet', MailjetTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.mailgun', MailgunTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.mailpace', MailPaceTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.postmark', PostmarkTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.sendgrid', SendgridTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.null', NullTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.scaleway', ScalewayTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.sendmail', SendmailTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory') - - ->set('mailer.transport_factory.smtp', EsmtpTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory', ['priority' => -100]) - - ->set('mailer.transport_factory.native', NativeTransportFactory::class) - ->parent('mailer.transport_factory.abstract') - ->tag('mailer.transport_factory'); + ->tag('monolog.logger', ['channel' => 'mailer']); + + $factories = [ + 'amazon' => SesTransportFactory::class, + 'azure' => AzureTransportFactory::class, + 'brevo' => BrevoTransportFactory::class, + 'gmail' => GmailTransportFactory::class, + 'infobip' => InfobipTransportFactory::class, + 'mailchimp' => MandrillTransportFactory::class, + 'mailersend' => MailerSendTransportFactory::class, + 'mailgun' => MailgunTransportFactory::class, + 'mailjet' => MailjetTransportFactory::class, + 'mailpace' => MailPaceTransportFactory::class, + 'native' => NativeTransportFactory::class, + 'null' => NullTransportFactory::class, + 'postmark' => PostmarkTransportFactory::class, + 'resend' => ResendTransportFactory::class, + 'scaleway' => ScalewayTransportFactory::class, + 'sendgrid' => SendgridTransportFactory::class, + 'sendmail' => SendmailTransportFactory::class, + 'smtp' => EsmtpTransportFactory::class, + ]; + + foreach ($factories as $name => $class) { + $container->services() + ->set('mailer.transport_factory.'.$name, $class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory'); + } }; diff --git a/Resources/config/mailer_webhook.php b/Resources/config/mailer_webhook.php index bb487b36c..f9d2b9686 100644 --- a/Resources/config/mailer_webhook.php +++ b/Resources/config/mailer_webhook.php @@ -13,12 +13,16 @@ use Symfony\Component\Mailer\Bridge\Brevo\RemoteEvent\BrevoPayloadConverter; use Symfony\Component\Mailer\Bridge\Brevo\Webhook\BrevoRequestParser; +use Symfony\Component\Mailer\Bridge\MailerSend\RemoteEvent\MailerSendPayloadConverter; +use Symfony\Component\Mailer\Bridge\MailerSend\Webhook\MailerSendRequestParser; use Symfony\Component\Mailer\Bridge\Mailgun\RemoteEvent\MailgunPayloadConverter; use Symfony\Component\Mailer\Bridge\Mailgun\Webhook\MailgunRequestParser; use Symfony\Component\Mailer\Bridge\Mailjet\RemoteEvent\MailjetPayloadConverter; use Symfony\Component\Mailer\Bridge\Mailjet\Webhook\MailjetRequestParser; use Symfony\Component\Mailer\Bridge\Postmark\RemoteEvent\PostmarkPayloadConverter; use Symfony\Component\Mailer\Bridge\Postmark\Webhook\PostmarkRequestParser; +use Symfony\Component\Mailer\Bridge\Resend\RemoteEvent\ResendPayloadConverter; +use Symfony\Component\Mailer\Bridge\Resend\Webhook\ResendRequestParser; use Symfony\Component\Mailer\Bridge\Sendgrid\RemoteEvent\SendgridPayloadConverter; use Symfony\Component\Mailer\Bridge\Sendgrid\Webhook\SendgridRequestParser; @@ -29,6 +33,11 @@ ->args([service('mailer.payload_converter.brevo')]) ->alias(BrevoRequestParser::class, 'mailer.webhook.request_parser.brevo') + ->set('mailer.payload_converter.mailersend', MailerSendPayloadConverter::class) + ->set('mailer.webhook.request_parser.mailersend', MailerSendRequestParser::class) + ->args([service('mailer.payload_converter.mailersend')]) + ->alias(MailerSendRequestParser::class, 'mailer.webhook.request_parser.mailersend') + ->set('mailer.payload_converter.mailgun', MailgunPayloadConverter::class) ->set('mailer.webhook.request_parser.mailgun', MailgunRequestParser::class) ->args([service('mailer.payload_converter.mailgun')]) @@ -44,6 +53,11 @@ ->args([service('mailer.payload_converter.postmark')]) ->alias(PostmarkRequestParser::class, 'mailer.webhook.request_parser.postmark') + ->set('mailer.payload_converter.resend', ResendPayloadConverter::class) + ->set('mailer.webhook.request_parser.resend', ResendRequestParser::class) + ->args([service('mailer.payload_converter.resend')]) + ->alias(ResendRequestParser::class, 'mailer.webhook.request_parser.resend') + ->set('mailer.payload_converter.sendgrid', SendgridPayloadConverter::class) ->set('mailer.webhook.request_parser.sendgrid', SendgridRequestParser::class) ->args([service('mailer.payload_converter.sendgrid')]) diff --git a/Resources/config/messenger.php b/Resources/config/messenger.php index 556affd07..df2476096 100644 --- a/Resources/config/messenger.php +++ b/Resources/config/messenger.php @@ -135,6 +135,9 @@ ->tag('messenger.transport_factory') ->set('messenger.transport.in_memory.factory', InMemoryTransportFactory::class) + ->args([ + service('clock')->nullOnInvalid(), + ]) ->tag('messenger.transport_factory') ->tag('kernel.reset', ['method' => 'reset']) @@ -160,6 +163,7 @@ abstract_arg('delay ms'), abstract_arg('multiplier'), abstract_arg('max delay ms'), + abstract_arg('jitter'), ]) // rate limiter diff --git a/Resources/config/notifier_transports.php b/Resources/config/notifier_transports.php index 3feb1c080..df9be94ed 100644 --- a/Resources/config/notifier_transports.php +++ b/Resources/config/notifier_transports.php @@ -20,148 +20,108 @@ ->set('notifier.transport_factory.abstract', AbstractTransportFactory::class) ->abstract() - ->args([service('event_dispatcher'), service('http_client')->ignoreOnInvalid()]) - - ->set('notifier.transport_factory.brevo', Bridge\Brevo\BrevoTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.slack', Bridge\Slack\SlackTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.linked-in', Bridge\LinkedIn\LinkedInTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.telegram', Bridge\Telegram\TelegramTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.mattermost', Bridge\Mattermost\MattermostTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.vonage', Bridge\Vonage\VonageTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.rocket-chat', Bridge\RocketChat\RocketChatTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.google-chat', Bridge\GoogleChat\GoogleChatTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.twilio', Bridge\Twilio\TwilioTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.twitter', Bridge\Twitter\TwitterTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.all-my-sms', Bridge\AllMySms\AllMySmsTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.firebase', Bridge\Firebase\FirebaseTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.forty-six-elks', Bridge\FortySixElks\FortySixElksTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.free-mobile', Bridge\FreeMobile\FreeMobileTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.spot-hit', Bridge\SpotHit\SpotHitTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.fake-chat', Bridge\FakeChat\FakeChatTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.fake-sms', Bridge\FakeSms\FakeSmsTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.ovh-cloud', Bridge\OvhCloud\OvhCloudTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.sinch', Bridge\Sinch\SinchTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.zulip', Bridge\Zulip\ZulipTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.infobip', Bridge\Infobip\InfobipTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.isendpro', Bridge\Isendpro\IsendproTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.mobyt', Bridge\Mobyt\MobytTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.smsapi', Bridge\Smsapi\SmsapiTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.esendex', Bridge\Esendex\EsendexTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.sendberry', Bridge\Sendberry\SendberryTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.iqsms', Bridge\Iqsms\IqsmsTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.octopush', Bridge\Octopush\OctopushTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.discord', Bridge\Discord\DiscordTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.microsoft-teams', Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.gateway-api', Bridge\GatewayApi\GatewayApiTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.mercure', Bridge\Mercure\MercureTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.gitter', Bridge\Gitter\GitterTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.clickatell', Bridge\Clickatell\ClickatellTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.contact-everyone', Bridge\ContactEveryone\ContactEveryoneTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') + ->args([ + service('event_dispatcher'), + service('http_client')->ignoreOnInvalid(), + ]); + + $chatterFactories = [ + 'bluesky' => Bridge\Bluesky\BlueskyTransportFactory::class, + 'brevo' => Bridge\Brevo\BrevoTransportFactory::class, + 'chatwork' => Bridge\Chatwork\ChatworkTransportFactory::class, + 'discord' => Bridge\Discord\DiscordTransportFactory::class, + 'fake-chat' => Bridge\FakeChat\FakeChatTransportFactory::class, + 'firebase' => Bridge\Firebase\FirebaseTransportFactory::class, + 'gitter' => Bridge\Gitter\GitterTransportFactory::class, + 'google-chat' => Bridge\GoogleChat\GoogleChatTransportFactory::class, + 'line-notify' => Bridge\LineNotify\LineNotifyTransportFactory::class, + 'linked-in' => Bridge\LinkedIn\LinkedInTransportFactory::class, + 'mastodon' => Bridge\Mastodon\MastodonTransportFactory::class, + 'mattermost' => Bridge\Mattermost\MattermostTransportFactory::class, + 'mercure' => Bridge\Mercure\MercureTransportFactory::class, + 'microsoft-teams' => Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory::class, + 'pager-duty' => Bridge\PagerDuty\PagerDutyTransportFactory::class, + 'rocket-chat' => Bridge\RocketChat\RocketChatTransportFactory::class, + 'slack' => Bridge\Slack\SlackTransportFactory::class, + 'telegram' => Bridge\Telegram\TelegramTransportFactory::class, + 'twitter' => Bridge\Twitter\TwitterTransportFactory::class, + 'zendesk' => Bridge\Zendesk\ZendeskTransportFactory::class, + 'zulip' => Bridge\Zulip\ZulipTransportFactory::class, + ]; + + foreach ($chatterFactories as $name => $class) { + $container->services() + ->set('notifier.transport_factory.'.$name, $class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory'); + } + + $texterFactories = [ + 'all-my-sms' => Bridge\AllMySms\AllMySmsTransportFactory::class, + 'bandwidth' => Bridge\Bandwidth\BandwidthTransportFactory::class, + 'click-send' => Bridge\ClickSend\ClickSendTransportFactory::class, + 'clickatell' => Bridge\Clickatell\ClickatellTransportFactory::class, + 'contact-everyone' => Bridge\ContactEveryone\ContactEveryoneTransportFactory::class, + 'engagespot' => Bridge\Engagespot\EngagespotTransportFactory::class, + 'esendex' => Bridge\Esendex\EsendexTransportFactory::class, + 'expo' => Bridge\Expo\ExpoTransportFactory::class, + 'fake-sms' => Bridge\FakeSms\FakeSmsTransportFactory::class, + 'forty-six-elks' => Bridge\FortySixElks\FortySixElksTransportFactory::class, + 'free-mobile' => Bridge\FreeMobile\FreeMobileTransportFactory::class, + 'gateway-api' => Bridge\GatewayApi\GatewayApiTransportFactory::class, + 'go-ip' => Bridge\GoIp\GoIpTransportFactory::class, + 'infobip' => Bridge\Infobip\InfobipTransportFactory::class, + 'iqsms' => Bridge\Iqsms\IqsmsTransportFactory::class, + 'isendpro' => Bridge\Isendpro\IsendproTransportFactory::class, + 'kaz-info-teh' => Bridge\KazInfoTeh\KazInfoTehTransportFactory::class, + 'light-sms' => Bridge\LightSms\LightSmsTransportFactory::class, + 'lox24' => Bridge\Lox24\Lox24TransportFactory::class, + 'mailjet' => Bridge\Mailjet\MailjetTransportFactory::class, + 'message-bird' => Bridge\MessageBird\MessageBirdTransportFactory::class, + 'message-media' => Bridge\MessageMedia\MessageMediaTransportFactory::class, + 'mobyt' => Bridge\Mobyt\MobytTransportFactory::class, + 'novu' => Bridge\Novu\NovuTransportFactory::class, + 'ntfy' => Bridge\Ntfy\NtfyTransportFactory::class, + 'octopush' => Bridge\Octopush\OctopushTransportFactory::class, + 'one-signal' => Bridge\OneSignal\OneSignalTransportFactory::class, + 'orange-sms' => Bridge\OrangeSms\OrangeSmsTransportFactory::class, + 'ovh-cloud' => Bridge\OvhCloud\OvhCloudTransportFactory::class, + 'plivo' => Bridge\Plivo\PlivoTransportFactory::class, + 'pushover' => Bridge\Pushover\PushoverTransportFactory::class, + 'pushy' => Bridge\Pushy\PushyTransportFactory::class, + 'redlink' => Bridge\Redlink\RedlinkTransportFactory::class, + 'ring-central' => Bridge\RingCentral\RingCentralTransportFactory::class, + 'sendberry' => Bridge\Sendberry\SendberryTransportFactory::class, + 'sevenio' => Bridge\Sevenio\SevenIoTransportFactory::class, + 'simple-textin' => Bridge\SimpleTextin\SimpleTextinTransportFactory::class, + 'sinch' => Bridge\Sinch\SinchTransportFactory::class, + 'sms-biuras' => Bridge\SmsBiuras\SmsBiurasTransportFactory::class, + 'sms-factor' => Bridge\SmsFactor\SmsFactorTransportFactory::class, + 'sms-sluzba' => Bridge\SmsSluzba\SmsSluzbaTransportFactory::class, + 'sms77' => Bridge\Sms77\Sms77TransportFactory::class, + 'smsapi' => Bridge\Smsapi\SmsapiTransportFactory::class, + 'smsbox' => Bridge\Smsbox\SmsboxTransportFactory::class, + 'smsc' => Bridge\Smsc\SmscTransportFactory::class, + 'smsense' => Bridge\Smsense\SmsenseTransportFactory::class, + 'smsmode' => Bridge\Smsmode\SmsmodeTransportFactory::class, + 'spot-hit' => Bridge\SpotHit\SpotHitTransportFactory::class, + 'telnyx' => Bridge\Telnyx\TelnyxTransportFactory::class, + 'termii' => Bridge\Termii\TermiiTransportFactory::class, + 'turbo-sms' => Bridge\TurboSms\TurboSmsTransportFactory::class, + 'twilio' => Bridge\Twilio\TwilioTransportFactory::class, + 'unifonic' => Bridge\Unifonic\UnifonicTransportFactory::class, + 'vonage' => Bridge\Vonage\VonageTransportFactory::class, + 'yunpian' => Bridge\Yunpian\YunpianTransportFactory::class, + ]; + + foreach ($texterFactories as $name => $class) { + $container->services() + ->set('notifier.transport_factory.'.$name, $class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory'); + } + $container->services() ->set('notifier.transport_factory.amazon-sns', Bridge\AmazonSns\AmazonSnsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') @@ -171,136 +131,5 @@ ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.light-sms', Bridge\LightSms\LightSmsTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.sms-biuras', Bridge\SmsBiuras\SmsBiurasTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.smsc', Bridge\Smsc\SmscTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.sms-factor', Bridge\SmsFactor\SmsFactorTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.message-bird', Bridge\MessageBird\MessageBirdTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.message-media', Bridge\MessageMedia\MessageMediaTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.telnyx', Bridge\Telnyx\TelnyxTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.mailjet', Bridge\Mailjet\MailjetTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.yunpian', Bridge\Yunpian\YunpianTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.turbo-sms', Bridge\TurboSms\TurboSmsTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.sms77', Bridge\Sms77\Sms77TransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.one-signal', Bridge\OneSignal\OneSignalTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.orange-sms', Bridge\OrangeSms\OrangeSmsTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.expo', Bridge\Expo\ExpoTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.kaz-info-teh', Bridge\KazInfoTeh\KazInfoTehTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.engagespot', Bridge\Engagespot\EngagespotTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.zendesk', Bridge\Zendesk\ZendeskTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.chatwork', Bridge\Chatwork\ChatworkTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.termii', Bridge\Termii\TermiiTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.ring-central', Bridge\RingCentral\RingCentralTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.plivo', Bridge\Plivo\PlivoTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.bandwidth', Bridge\Bandwidth\BandwidthTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.line-notify', Bridge\LineNotify\LineNotifyTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.mastodon', Bridge\Mastodon\MastodonTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.pager-duty', Bridge\PagerDuty\PagerDutyTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('chatter.transport_factory') - - ->set('notifier.transport_factory.pushover', Bridge\Pushover\PushoverTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.simple-textin', Bridge\SimpleTextin\SimpleTextinTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.click-send', Bridge\ClickSend\ClickSendTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.smsmode', Bridge\Smsmode\SmsmodeTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.novu', Bridge\Novu\NovuTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.ntfy', Bridge\Ntfy\NtfyTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - - ->set('notifier.transport_factory.redlink', Bridge\Redlink\RedlinkTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') - ->set('notifier.transport_factory.go-ip', Bridge\GoIp\GoIpTransportFactory::class) - ->parent('notifier.transport_factory.abstract') - ->tag('texter.transport_factory') ; }; diff --git a/Resources/config/schema/symfony-1.0.xsd b/Resources/config/schema/symfony-1.0.xsd index 2f70a3481..d8d23168d 100644 --- a/Resources/config/schema/symfony-1.0.xsd +++ b/Resources/config/schema/symfony-1.0.xsd @@ -27,6 +27,7 @@ + @@ -327,6 +328,10 @@ + + + + @@ -609,6 +614,7 @@ + @@ -660,6 +666,7 @@ + @@ -690,6 +697,7 @@ + @@ -756,6 +764,7 @@ + diff --git a/Resources/config/secrets.php b/Resources/config/secrets.php index a21d28270..8192f2f06 100644 --- a/Resources/config/secrets.php +++ b/Resources/config/secrets.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault; use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault; +use Symfony\Component\DependencyInjection\StaticEnvVarLoader; return static function (ContainerConfigurator $container) { $container->services() @@ -21,6 +22,9 @@ abstract_arg('Secret dir, set in FrameworkExtension'), service('secrets.decryption_key')->ignoreOnInvalid(), ]) + + ->set('secrets.env_var_loader', StaticEnvVarLoader::class) + ->args([service('secrets.vault')]) ->tag('container.env_var_loader') ->set('secrets.decryption_key') diff --git a/Resources/config/type_info.php b/Resources/config/type_info.php new file mode 100644 index 000000000..71e3646a1 --- /dev/null +++ b/Resources/config/type_info.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory; +use Symfony\Component\TypeInfo\TypeResolver\ReflectionParameterTypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\ReflectionPropertyTypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\ReflectionReturnTypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\ReflectionTypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + // type context + ->set('type_info.type_context_factory', TypeContextFactory::class) + ->args([service('type_info.resolver.string')->nullOnInvalid()]) + + // type resolvers + ->set('type_info.resolver', TypeResolver::class) + ->args([service_locator([ + \ReflectionType::class => service('type_info.resolver.reflection_type'), + \ReflectionParameter::class => service('type_info.resolver.reflection_parameter'), + \ReflectionProperty::class => service('type_info.resolver.reflection_property'), + \ReflectionFunctionAbstract::class => service('type_info.resolver.reflection_return'), + ])]) + ->alias(TypeResolverInterface::class, 'type_info.resolver') + + ->set('type_info.resolver.reflection_type', ReflectionTypeResolver::class) + ->args([service('type_info.type_context_factory')]) + + ->set('type_info.resolver.reflection_parameter', ReflectionParameterTypeResolver::class) + ->args([service('type_info.resolver.reflection_type'), service('type_info.type_context_factory')]) + + ->set('type_info.resolver.reflection_property', ReflectionPropertyTypeResolver::class) + ->args([service('type_info.resolver.reflection_type'), service('type_info.type_context_factory')]) + + ->set('type_info.resolver.reflection_return', ReflectionReturnTypeResolver::class) + ->args([service('type_info.resolver.reflection_type'), service('type_info.type_context_factory')]) + ; +}; diff --git a/Routing/Attribute/AsRoutingConditionService.php b/Routing/Attribute/AsRoutingConditionService.php index 13f8ff26a..5e481d73a 100644 --- a/Routing/Attribute/AsRoutingConditionService.php +++ b/Routing/Attribute/AsRoutingConditionService.php @@ -41,6 +41,10 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class AsRoutingConditionService extends AutoconfigureTag { + /** + * @param string|null $alias The alias of the service to use it in routing condition expressions + * @param int $priority Defines a priority that allows the routing condition service to override a service with the same alias + */ public function __construct( ?string $alias = null, int $priority = 0, diff --git a/Routing/DelegatingLoader.php b/Routing/DelegatingLoader.php index 3239d1094..42d461773 100644 --- a/Routing/DelegatingLoader.php +++ b/Routing/DelegatingLoader.php @@ -29,14 +29,12 @@ class DelegatingLoader extends BaseDelegatingLoader { private bool $loading = false; - private array $defaultOptions; - private array $defaultRequirements; - - public function __construct(LoaderResolverInterface $resolver, array $defaultOptions = [], array $defaultRequirements = []) - { - $this->defaultOptions = $defaultOptions; - $this->defaultRequirements = $defaultRequirements; + public function __construct( + LoaderResolverInterface $resolver, + private array $defaultOptions = [], + private array $defaultRequirements = [], + ) { parent::__construct($resolver); } diff --git a/Routing/Router.php b/Routing/Router.php index 53d8b06d0..4dfb71e74 100644 --- a/Routing/Router.php +++ b/Routing/Router.php @@ -30,6 +30,8 @@ * This Router creates the Loader only when the cache is empty. * * @author Fabien Potencier + * + * @final since Symfony 7.1 */ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberInterface { @@ -82,10 +84,14 @@ public function getRouteCollection(): RouteCollection public function warmUp(string $cacheDir, ?string $buildDir = null): array { + if (!$buildDir) { + return []; + } + $currentDir = $this->getOption('cache_dir'); - // force cache generation - $this->setOption('cache_dir', $cacheDir); + // force cache generation in build_dir + $this->setOption('cache_dir', $buildDir); $this->getMatcher(); $this->getGenerator(); diff --git a/Secrets/DotenvVault.php b/Secrets/DotenvVault.php index 994b31d18..7bdd74c37 100644 --- a/Secrets/DotenvVault.php +++ b/Secrets/DotenvVault.php @@ -16,10 +16,9 @@ */ class DotenvVault extends AbstractVault { - private string $dotenvFile; - - public function __construct(string $dotenvFile) - { + public function __construct( + private string $dotenvFile, + ) { $this->dotenvFile = strtr($dotenvFile, '/', \DIRECTORY_SEPARATOR); } diff --git a/Test/BrowserKitAssertionsTrait.php b/Test/BrowserKitAssertionsTrait.php index 125aa45a7..7eea648cb 100644 --- a/Test/BrowserKitAssertionsTrait.php +++ b/Test/BrowserKitAssertionsTrait.php @@ -28,14 +28,14 @@ */ trait BrowserKitAssertionsTrait { - public static function assertResponseIsSuccessful(string $message = ''): void + public static function assertResponseIsSuccessful(string $message = '', bool $verbose = true): void { - self::assertThatForResponse(new ResponseConstraint\ResponseIsSuccessful(), $message); + self::assertThatForResponse(new ResponseConstraint\ResponseIsSuccessful($verbose), $message); } - public static function assertResponseStatusCodeSame(int $expectedCode, string $message = ''): void + public static function assertResponseStatusCodeSame(int $expectedCode, string $message = '', bool $verbose = true): void { - self::assertThatForResponse(new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message); + self::assertThatForResponse(new ResponseConstraint\ResponseStatusCodeSame($expectedCode, $verbose), $message); } public static function assertResponseFormatSame(?string $expectedFormat, string $message = ''): void @@ -43,9 +43,9 @@ public static function assertResponseFormatSame(?string $expectedFormat, string self::assertThatForResponse(new ResponseConstraint\ResponseFormatSame(self::getRequest(), $expectedFormat), $message); } - public static function assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = ''): void + public static function assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', bool $verbose = true): void { - $constraint = new ResponseConstraint\ResponseIsRedirected(); + $constraint = new ResponseConstraint\ResponseIsRedirected($verbose); if ($expectedLocation) { if (class_exists(ResponseConstraint\ResponseHeaderLocationSame::class)) { $locationConstraint = new ResponseConstraint\ResponseHeaderLocationSame(self::getRequest(), $expectedLocation); @@ -100,9 +100,9 @@ public static function assertResponseCookieValueSame(string $name, string $expec ), $message); } - public static function assertResponseIsUnprocessable(string $message = ''): void + public static function assertResponseIsUnprocessable(string $message = '', bool $verbose = true): void { - self::assertThatForResponse(new ResponseConstraint\ResponseIsUnprocessable(), $message); + self::assertThatForResponse(new ResponseConstraint\ResponseIsUnprocessable($verbose), $message); } public static function assertBrowserHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = ''): void diff --git a/Test/DomCrawlerAssertionsTrait.php b/Test/DomCrawlerAssertionsTrait.php index a16709461..024c78f75 100644 --- a/Test/DomCrawlerAssertionsTrait.php +++ b/Test/DomCrawlerAssertionsTrait.php @@ -26,12 +26,12 @@ trait DomCrawlerAssertionsTrait { public static function assertSelectorExists(string $selector, string $message = ''): void { - self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorExists($selector), $message); + self::assertThat(self::getCrawler(), new CrawlerSelectorExists($selector), $message); } public static function assertSelectorNotExists(string $selector, string $message = ''): void { - self::assertThat(self::getCrawler(), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorExists($selector)), $message); + self::assertThat(self::getCrawler(), new LogicalNot(new CrawlerSelectorExists($selector)), $message); } public static function assertSelectorCount(int $expectedCount, string $selector, string $message = ''): void @@ -42,7 +42,7 @@ public static function assertSelectorCount(int $expectedCount, string $selector, public static function assertSelectorTextContains(string $selector, string $text, string $message = ''): void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new CrawlerSelectorExists($selector), new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text) ), $message); } @@ -50,7 +50,7 @@ public static function assertSelectorTextContains(string $selector, string $text public static function assertAnySelectorTextContains(string $selector, string $text, string $message = ''): void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new CrawlerSelectorExists($selector), new DomCrawlerConstraint\CrawlerAnySelectorTextContains($selector, $text) ), $message); } @@ -58,7 +58,7 @@ public static function assertAnySelectorTextContains(string $selector, string $t public static function assertSelectorTextSame(string $selector, string $text, string $message = ''): void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new CrawlerSelectorExists($selector), new DomCrawlerConstraint\CrawlerSelectorTextSame($selector, $text) ), $message); } @@ -66,7 +66,7 @@ public static function assertSelectorTextSame(string $selector, string $text, st public static function assertAnySelectorTextSame(string $selector, string $text, string $message = ''): void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new CrawlerSelectorExists($selector), new DomCrawlerConstraint\CrawlerAnySelectorTextSame($selector, $text) ), $message); } @@ -74,7 +74,7 @@ public static function assertAnySelectorTextSame(string $selector, string $text, public static function assertSelectorTextNotContains(string $selector, string $text, string $message = ''): void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new CrawlerSelectorExists($selector), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text)) ), $message); } @@ -82,7 +82,7 @@ public static function assertSelectorTextNotContains(string $selector, string $t public static function assertAnySelectorTextNotContains(string $selector, string $text, string $message = ''): void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new CrawlerSelectorExists($selector), new LogicalNot(new DomCrawlerConstraint\CrawlerAnySelectorTextContains($selector, $text)) ), $message); } @@ -100,7 +100,7 @@ public static function assertPageTitleContains(string $expectedTitle, string $me public static function assertInputValueSame(string $fieldName, string $expectedValue, string $message = ''): void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"), + new CrawlerSelectorExists("input[name=\"$fieldName\"]"), new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue) ), $message); } @@ -108,7 +108,7 @@ public static function assertInputValueSame(string $fieldName, string $expectedV public static function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = ''): void { self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"), + new CrawlerSelectorExists("input[name=\"$fieldName\"]"), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue)) ), $message); } diff --git a/Test/TestBrowserToken.php b/Test/TestBrowserToken.php index 25d71d084..e186b2c44 100644 --- a/Test/TestBrowserToken.php +++ b/Test/TestBrowserToken.php @@ -21,17 +21,16 @@ */ class TestBrowserToken extends AbstractToken { - private string $firewallName; - - public function __construct(array $roles = [], ?UserInterface $user = null, string $firewallName = 'main') - { + public function __construct( + array $roles = [], + ?UserInterface $user = null, + private string $firewallName = 'main', + ) { parent::__construct($roles); if (null !== $user) { $this->setUser($user); } - - $this->firewallName = $firewallName; } public function getFirewallName(): string diff --git a/Tests/CacheWarmer/RouterCacheWarmerTest.php b/Tests/CacheWarmer/RouterCacheWarmerTest.php index 727b566e1..615010a47 100644 --- a/Tests/CacheWarmer/RouterCacheWarmerTest.php +++ b/Tests/CacheWarmer/RouterCacheWarmerTest.php @@ -12,43 +12,68 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\CacheWarmer; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\Routing\RouterInterface; class RouterCacheWarmerTest extends TestCase { - public function testWarmUpWithWarmebleInterface() + public function testWarmUpWithWarmableInterfaceWithBuildDir() { - $containerMock = $this->getMockBuilder(ContainerInterface::class)->onlyMethods(['get', 'has'])->getMock(); + $container = new Container(); - $routerMock = $this->getMockBuilder(testRouterInterfaceWithWarmebleInterface::class)->onlyMethods(['match', 'generate', 'getContext', 'setContext', 'getRouteCollection', 'warmUp'])->getMock(); - $containerMock->expects($this->any())->method('get')->with('router')->willReturn($routerMock); - $routerCacheWarmer = new RouterCacheWarmer($containerMock); + $routerMock = $this->getMockBuilder(testRouterInterfaceWithWarmableInterface::class)->onlyMethods(['match', 'generate', 'getContext', 'setContext', 'getRouteCollection', 'warmUp'])->getMock(); + $container->set('router', $routerMock); + $routerCacheWarmer = new RouterCacheWarmer($container); - $routerCacheWarmer->warmUp('/tmp'); - $routerMock->expects($this->any())->method('warmUp')->with('/tmp')->willReturn([]); + $routerCacheWarmer->warmUp('/tmp/cache', '/tmp/build'); + $routerMock->expects($this->any())->method('warmUp')->with('/tmp/cache', '/tmp/build')->willReturn([]); $this->addToAssertionCount(1); } - public function testWarmUpWithoutWarmebleInterface() + public function testWarmUpWithoutWarmableInterfaceWithBuildDir() { - $containerMock = $this->getMockBuilder(ContainerInterface::class)->onlyMethods(['get', 'has'])->getMock(); + $container = new Container(); - $routerMock = $this->getMockBuilder(testRouterInterfaceWithoutWarmebleInterface::class)->onlyMethods(['match', 'generate', 'getContext', 'setContext', 'getRouteCollection'])->getMock(); - $containerMock->expects($this->any())->method('get')->with('router')->willReturn($routerMock); - $routerCacheWarmer = new RouterCacheWarmer($containerMock); + $routerMock = $this->getMockBuilder(testRouterInterfaceWithoutWarmableInterface::class)->onlyMethods(['match', 'generate', 'getContext', 'setContext', 'getRouteCollection'])->getMock(); + $container->set('router', $routerMock); + $routerCacheWarmer = new RouterCacheWarmer($container); $this->expectException(\LogicException::class); $this->expectExceptionMessage('cannot be warmed up because it does not implement "Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface"'); - $routerCacheWarmer->warmUp('/tmp'); + $routerCacheWarmer->warmUp('/tmp/cache', '/tmp/build'); + } + + public function testWarmUpWithWarmableInterfaceWithoutBuildDir() + { + $container = new Container(); + + $routerMock = $this->getMockBuilder(testRouterInterfaceWithWarmableInterface::class)->onlyMethods(['match', 'generate', 'getContext', 'setContext', 'getRouteCollection', 'warmUp'])->getMock(); + $container->set('router', $routerMock); + $routerCacheWarmer = new RouterCacheWarmer($container); + + $preload = $routerCacheWarmer->warmUp('/tmp'); + $routerMock->expects($this->never())->method('warmUp'); + self::assertSame([], $preload); + $this->addToAssertionCount(1); + } + + public function testWarmUpWithoutWarmableInterfaceWithoutBuildDir() + { + $container = new Container(); + + $routerMock = $this->getMockBuilder(testRouterInterfaceWithoutWarmableInterface::class)->onlyMethods(['match', 'generate', 'getContext', 'setContext', 'getRouteCollection'])->getMock(); + $container->set('router', $routerMock); + $routerCacheWarmer = new RouterCacheWarmer($container); + $preload = $routerCacheWarmer->warmUp('/tmp'); + self::assertSame([], $preload); } } -interface testRouterInterfaceWithWarmebleInterface extends RouterInterface, WarmableInterface +interface testRouterInterfaceWithWarmableInterface extends RouterInterface, WarmableInterface { } -interface testRouterInterfaceWithoutWarmebleInterface extends RouterInterface +interface testRouterInterfaceWithoutWarmableInterface extends RouterInterface { } diff --git a/Tests/Command/CacheClearCommand/CacheClearCommandTest.php b/Tests/Command/CacheClearCommand/CacheClearCommandTest.php index 78b13905e..b950b5fd9 100644 --- a/Tests/Command/CacheClearCommand/CacheClearCommandTest.php +++ b/Tests/Command/CacheClearCommand/CacheClearCommandTest.php @@ -75,7 +75,7 @@ function () use ($file) { $kernelRef = new \ReflectionObject($this->kernel); $kernelFile = $kernelRef->getFileName(); /** @var ResourceInterface[] $meta */ - $meta = unserialize(file_get_contents($containerMetaFile)); + $meta = unserialize($this->fs->readFile($containerMetaFile)); $found = false; foreach ($meta as $resource) { if ((string) $resource === $kernelFile) { @@ -93,7 +93,7 @@ function () use ($file) { ); $this->assertMatchesRegularExpression( sprintf('/\'kernel.container_class\'\s*=>\s*\'%s\'/', $containerClass), - file_get_contents($containerFile), + $this->fs->readFile($containerFile), 'kernel.container_class is properly set on the dumped container' ); } diff --git a/Tests/Command/CachePoolClearCommandTest.php b/Tests/Command/CachePoolClearCommandTest.php index fb7358831..3a927f217 100644 --- a/Tests/Command/CachePoolClearCommandTest.php +++ b/Tests/Command/CachePoolClearCommandTest.php @@ -17,7 +17,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\Console\Tester\CommandCompletionTester; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; use Symfony\Component\HttpKernel\KernelInterface; @@ -54,13 +54,11 @@ public static function provideCompletionSuggestions(): iterable private function getKernel(): MockObject&KernelInterface { - $container = $this->createMock(ContainerInterface::class); - $kernel = $this->createMock(KernelInterface::class); $kernel ->expects($this->any()) ->method('getContainer') - ->willReturn($container); + ->willReturn(new Container()); $kernel ->expects($this->once()) diff --git a/Tests/Command/CachePoolDeleteCommandTest.php b/Tests/Command/CachePoolDeleteCommandTest.php index caa7eb550..3db39e121 100644 --- a/Tests/Command/CachePoolDeleteCommandTest.php +++ b/Tests/Command/CachePoolDeleteCommandTest.php @@ -18,7 +18,7 @@ use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; use Symfony\Component\HttpKernel\KernelInterface; @@ -108,13 +108,11 @@ public static function provideCompletionSuggestions(): iterable private function getKernel(): MockObject&KernelInterface { - $container = $this->createMock(ContainerInterface::class); - $kernel = $this->createMock(KernelInterface::class); $kernel ->expects($this->any()) ->method('getContainer') - ->willReturn($container); + ->willReturn(new Container()); $kernel ->expects($this->once()) diff --git a/Tests/Command/CachePruneCommandTest.php b/Tests/Command/CachePruneCommandTest.php index 54467f1ef..a2d0ad7fe 100644 --- a/Tests/Command/CachePruneCommandTest.php +++ b/Tests/Command/CachePruneCommandTest.php @@ -18,7 +18,7 @@ use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpKernel\KernelInterface; class CachePruneCommandTest extends TestCase @@ -50,13 +50,11 @@ private function getEmptyRewindableGenerator(): RewindableGenerator private function getKernel(): MockObject&KernelInterface { - $container = $this->createMock(ContainerInterface::class); - $kernel = $this->createMock(KernelInterface::class); $kernel ->expects($this->any()) ->method('getContainer') - ->willReturn($container); + ->willReturn(new Container()); $kernel ->expects($this->once()) diff --git a/Tests/Command/RouterMatchCommandTest.php b/Tests/Command/RouterMatchCommandTest.php index 7dab41991..b6b6771f9 100644 --- a/Tests/Command/RouterMatchCommandTest.php +++ b/Tests/Command/RouterMatchCommandTest.php @@ -16,7 +16,7 @@ use Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Route; @@ -72,24 +72,11 @@ private function getRouter() private function getKernel() { - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->atLeastOnce()) - ->method('has') - ->willReturnCallback(fn ($id) => 'console.command_loader' !== $id) - ; - $container - ->expects($this->any()) - ->method('get') - ->with('router') - ->willReturn($this->getRouter()) - ; - $kernel = $this->createMock(KernelInterface::class); $kernel ->expects($this->any()) ->method('getContainer') - ->willReturn($container) + ->willReturn(new Container()) ; $kernel ->expects($this->once()) diff --git a/Tests/Command/SecretsRevealCommandTest.php b/Tests/Command/SecretsRevealCommandTest.php new file mode 100644 index 000000000..94643db2c --- /dev/null +++ b/Tests/Command/SecretsRevealCommandTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Command\SecretsRevealCommand; +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Tester\CommandTester; + +class SecretsRevealCommandTest extends TestCase +{ + public function testExecute() + { + $vault = $this->createMock(AbstractVault::class); + $vault->method('list')->willReturn(['secretKey' => 'secretValue']); + + $command = new SecretsRevealCommand($vault); + + $tester = new CommandTester($command); + $this->assertSame(Command::SUCCESS, $tester->execute(['name' => 'secretKey'])); + + $this->assertEquals('secretValue', trim($tester->getDisplay(true))); + } + + public function testInvalidName() + { + $vault = $this->createMock(AbstractVault::class); + $vault->method('list')->willReturn(['secretKey' => 'secretValue']); + + $command = new SecretsRevealCommand($vault); + + $tester = new CommandTester($command); + $this->assertSame(Command::INVALID, $tester->execute(['name' => 'undefinedKey'])); + + $this->assertStringContainsString('The secret "undefinedKey" does not exist.', trim($tester->getDisplay(true))); + } + + /** + * @backupGlobals enabled + */ + public function testLocalVaultOverride() + { + $vault = $this->createMock(AbstractVault::class); + $vault->method('list')->willReturn(['secretKey' => 'secretValue']); + + $_ENV = ['secretKey' => 'newSecretValue']; + $localVault = new DotenvVault('/not/a/path'); + + $command = new SecretsRevealCommand($vault, $localVault); + + $tester = new CommandTester($command); + $this->assertSame(Command::SUCCESS, $tester->execute(['name' => 'secretKey'])); + + $this->assertEquals('newSecretValue', trim($tester->getDisplay(true))); + } + + /** + * @backupGlobals enabled + */ + public function testOnlyLocalVaultContainsName() + { + $vault = $this->createMock(AbstractVault::class); + $vault->method('list')->willReturn(['otherKey' => 'secretValue']); + + $_ENV = ['secretKey' => 'secretValue']; + $localVault = new DotenvVault('/not/a/path'); + + $command = new SecretsRevealCommand($vault, $localVault); + + $tester = new CommandTester($command); + $this->assertSame(Command::SUCCESS, $tester->execute(['name' => 'secretKey'])); + + $this->assertEquals('secretValue', trim($tester->getDisplay(true))); + } +} diff --git a/Tests/Console/ApplicationTest.php b/Tests/Console/ApplicationTest.php index 4411d59ba..9afb5a2fd 100644 --- a/Tests/Console/ApplicationTest.php +++ b/Tests/Console/ApplicationTest.php @@ -22,11 +22,11 @@ use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\ApplicationTester; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\BundleInterface; use Symfony\Component\HttpKernel\KernelInterface; @@ -241,12 +241,10 @@ private function createEventForSuggestingPackages(string $command, array $altern private function getKernel(array $bundles, $useDispatcher = false) { - $container = $this->createMock(ContainerInterface::class); - - $requestStack = $this->createMock(RequestStack::class); - $requestStack->expects($this->any()) - ->method('push') - ; + $container = new Container(new ParameterBag([ + 'console.command.ids' => [], + 'console.lazy_command.ids' => [], + ])); if ($useDispatcher) { $dispatcher = $this->createMock(EventDispatcherInterface::class); @@ -255,45 +253,9 @@ private function getKernel(array $bundles, $useDispatcher = false) ->method('dispatch') ; - $container->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap([ - ['.virtual_request_stack', 2, $requestStack], - ['event_dispatcher', 1, $dispatcher], - ]) - ; + $container->set('event_dispatcher', $dispatcher); } - $container - ->expects($this->exactly(2)) - ->method('hasParameter') - ->willReturnCallback(function (...$args) { - static $series = [ - ['console.command.ids'], - ['console.lazy_command.ids'], - ]; - - $this->assertSame(array_shift($series), $args); - - return true; - }) - ; - - $container - ->expects($this->exactly(2)) - ->method('getParameter') - ->willReturnCallback(function (...$args) { - static $series = [ - ['console.lazy_command.ids'], - ['console.command.ids'], - ]; - - $this->assertSame(array_shift($series), $args); - - return []; - }) - ; - $kernel = $this->createMock(KernelInterface::class); $kernel->expects($this->once())->method('boot'); $kernel diff --git a/Tests/Controller/ControllerResolverTest.php b/Tests/Controller/ControllerResolverTest.php index 3571ac13a..7c7398fd3 100644 --- a/Tests/Controller/ControllerResolverTest.php +++ b/Tests/Controller/ControllerResolverTest.php @@ -16,7 +16,6 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver; use Symfony\Component\DependencyInjection\Container; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Tests\Controller\ContainerControllerResolverTest; @@ -107,7 +106,7 @@ class_exists(AbstractControllerTest::class); protected function createControllerResolver(?LoggerInterface $logger = null, ?Psr11ContainerInterface $container = null) { if (!$container) { - $container = $this->createMockContainer(); + $container = new Container(); } return new ControllerResolver($container, $logger); @@ -117,11 +116,6 @@ protected function createMockParser() { return $this->createMock(ControllerNameParser::class); } - - protected function createMockContainer() - { - return $this->createMock(ContainerInterface::class); - } } class DummyController extends AbstractController diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index aed072dfb..b32d8681b 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -28,6 +28,7 @@ use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory; use Symfony\Component\Serializer\Encoder\JsonDecode; +use Symfony\Component\TypeInfo\Type; use Symfony\Component\Uid\Factory\UuidFactory; class ConfigurationTest extends TestCase @@ -566,6 +567,46 @@ public function testEnabledLockNeedsResources() ]); } + public function testScopedHttpClientsInheritRateLimiterAndRetryFailedConfiguration() + { + $processor = new Processor(); + $configuration = new Configuration(true); + + $config = $processor->processConfiguration($configuration, [[ + 'http_client' => [ + 'default_options' => ['rate_limiter' => 'default_limiter', 'retry_failed' => ['max_retries' => 77]], + 'scoped_clients' => [ + 'foo' => ['base_uri' => 'http://example.com'], + 'bar' => ['base_uri' => 'http://example.com', 'rate_limiter' => true, 'retry_failed' => true], + 'baz' => ['base_uri' => 'http://example.com', 'rate_limiter' => false, 'retry_failed' => false], + 'qux' => ['base_uri' => 'http://example.com', 'rate_limiter' => 'foo_limiter', 'retry_failed' => ['max_retries' => 88, 'delay' => 999]], + ], + ], + ]]); + + $scopedClients = $config['http_client']['scoped_clients']; + + $this->assertSame('default_limiter', $scopedClients['foo']['rate_limiter']); + $this->assertTrue($scopedClients['foo']['retry_failed']['enabled']); + $this->assertSame(77, $scopedClients['foo']['retry_failed']['max_retries']); + $this->assertSame(1000, $scopedClients['foo']['retry_failed']['delay']); + + $this->assertSame('default_limiter', $scopedClients['bar']['rate_limiter']); + $this->assertTrue($scopedClients['bar']['retry_failed']['enabled']); + $this->assertSame(77, $scopedClients['bar']['retry_failed']['max_retries']); + $this->assertSame(1000, $scopedClients['bar']['retry_failed']['delay']); + + $this->assertNull($scopedClients['baz']['rate_limiter']); + $this->assertFalse($scopedClients['baz']['retry_failed']['enabled']); + $this->assertSame(3, $scopedClients['baz']['retry_failed']['max_retries']); + $this->assertSame(1000, $scopedClients['baz']['retry_failed']['delay']); + + $this->assertSame('foo_limiter', $scopedClients['qux']['rate_limiter']); + $this->assertTrue($scopedClients['qux']['retry_failed']['enabled']); + $this->assertSame(88, $scopedClients['qux']['retry_failed']['max_retries']); + $this->assertSame(999, $scopedClients['qux']['retry_failed']['delay']); + } + protected static function getBundleDefaultConfig() { return [ @@ -660,6 +701,9 @@ protected static function getBundleDefaultConfig() 'throw_exception_on_invalid_index' => false, 'throw_exception_on_invalid_property_path' => true, ], + 'type_info' => [ + 'enabled' => !class_exists(FullStack::class) && class_exists(Type::class), + ], 'property_info' => [ 'enabled' => !class_exists(FullStack::class), ], @@ -670,7 +714,7 @@ protected static function getBundleDefaultConfig() 'https_port' => 443, 'strict_requirements' => true, 'utf8' => true, - 'cache_dir' => '%kernel.cache_dir%', + 'cache_dir' => '%kernel.build_dir%', ], 'session' => [ 'enabled' => false, diff --git a/Tests/DependencyInjection/Fixtures/php/full.php b/Tests/DependencyInjection/Fixtures/php/full.php index b5d8061e4..4fbf72a9f 100644 --- a/Tests/DependencyInjection/Fixtures/php/full.php +++ b/Tests/DependencyInjection/Fixtures/php/full.php @@ -70,6 +70,7 @@ 'default_context' => ['enable_max_depth' => true], ], 'property_info' => true, + 'type_info' => true, 'ide' => 'file%%link%%format', 'request' => [ 'formats' => [ diff --git a/Tests/DependencyInjection/Fixtures/php/http_client_rate_limiter.php b/Tests/DependencyInjection/Fixtures/php/http_client_rate_limiter.php new file mode 100644 index 000000000..c8256d913 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/php/http_client_rate_limiter.php @@ -0,0 +1,27 @@ +loadFromExtension('framework', [ + 'annotations' => false, + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'rate_limiter' => [ + 'foo_limiter' => [ + 'lock_factory' => null, + 'policy' => 'token_bucket', + 'limit' => 10, + 'rate' => ['interval' => '5 seconds', 'amount' => 10], + ], + ], + 'http_client' => [ + 'default_options' => [ + 'rate_limiter' => 'default_limiter', + ], + 'scoped_clients' => [ + 'foo' => [ + 'base_uri' => 'http://example.com', + 'rate_limiter' => 'foo_limiter', + ], + ], + ], +]); diff --git a/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php b/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php index 683872982..3357bf354 100644 --- a/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php +++ b/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php @@ -13,6 +13,7 @@ 'envelope' => [ 'sender' => 'sender@example.org', 'recipients' => ['redirected@example.org'], + 'allowed_recipients' => ['foobar@example\.org'], ], 'headers' => [ 'from' => 'from@example.org', diff --git a/Tests/DependencyInjection/Fixtures/php/mailer_with_transports.php b/Tests/DependencyInjection/Fixtures/php/mailer_with_transports.php index 361fe731c..e51fd056b 100644 --- a/Tests/DependencyInjection/Fixtures/php/mailer_with_transports.php +++ b/Tests/DependencyInjection/Fixtures/php/mailer_with_transports.php @@ -16,6 +16,7 @@ 'envelope' => [ 'sender' => 'sender@example.org', 'recipients' => ['redirected@example.org', 'redirected1@example.org'], + 'allowed_recipients' => ['foobar@example\.org', '.*@example\.com'], ], 'headers' => [ 'from' => 'from@example.org', diff --git a/Tests/DependencyInjection/Fixtures/php/trusted_proxies_private_ranges.php b/Tests/DependencyInjection/Fixtures/php/trusted_proxies_private_ranges.php new file mode 100644 index 000000000..e3a5dc4a5 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/php/trusted_proxies_private_ranges.php @@ -0,0 +1,5 @@ +loadFromExtension('framework', [ + 'trusted_proxies' => 'private_ranges', +]); diff --git a/Tests/DependencyInjection/Fixtures/php/type_info.php b/Tests/DependencyInjection/Fixtures/php/type_info.php new file mode 100644 index 000000000..0e7dcbae0 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/php/type_info.php @@ -0,0 +1,11 @@ +loadFromExtension('framework', [ + 'annotations' => false, + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'type_info' => [ + 'enabled' => true, + ], +]); diff --git a/Tests/DependencyInjection/Fixtures/xml/full.xml b/Tests/DependencyInjection/Fixtures/xml/full.xml index 92e4405a0..fd5d52e1c 100644 --- a/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -40,5 +40,6 @@ + diff --git a/Tests/DependencyInjection/Fixtures/xml/http_client_rate_limiter.xml b/Tests/DependencyInjection/Fixtures/xml/http_client_rate_limiter.xml new file mode 100644 index 000000000..8c9dbcdad --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/xml/http_client_rate_limiter.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml b/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml index 3436cf417..d48b7423a 100644 --- a/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml +++ b/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml @@ -13,6 +13,7 @@ sender@example.org redirected@example.org + foobar@example\.org from@example.org bcc1@example.org diff --git a/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml b/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml index 1cd8523b6..9bfd18d91 100644 --- a/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml +++ b/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml @@ -16,6 +16,8 @@ sender@example.org redirected@example.org redirected1@example.org + foobar@example\.org + .*@example\.com from@example.org bcc1@example.org diff --git a/Tests/DependencyInjection/Fixtures/xml/trusted_proxies_private_ranges.xml b/Tests/DependencyInjection/Fixtures/xml/trusted_proxies_private_ranges.xml new file mode 100644 index 000000000..700f84959 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/xml/trusted_proxies_private_ranges.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/Tests/DependencyInjection/Fixtures/xml/type_info.xml b/Tests/DependencyInjection/Fixtures/xml/type_info.xml new file mode 100644 index 000000000..0fe4d525d --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/xml/type_info.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/Tests/DependencyInjection/Fixtures/yml/full.yml b/Tests/DependencyInjection/Fixtures/yml/full.yml index 883e9d6c2..96001f1d2 100644 --- a/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -59,6 +59,7 @@ framework: max_depth_handler: my.max.depth.handler default_context: enable_max_depth: true + type_info: ~ property_info: ~ ide: file%%link%%format request: diff --git a/Tests/DependencyInjection/Fixtures/yml/http_client_rate_limiter.yml b/Tests/DependencyInjection/Fixtures/yml/http_client_rate_limiter.yml new file mode 100644 index 000000000..6376192b7 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/yml/http_client_rate_limiter.yml @@ -0,0 +1,19 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + rate_limiter: + foo_limiter: + lock_factory: null + policy: token_bucket + limit: 10 + rate: { interval: '5 seconds', amount: 10 } + http_client: + default_options: + rate_limiter: default_limiter + scoped_clients: + foo: + base_uri: http://example.com + rate_limiter: foo_limiter diff --git a/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml b/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml index e826d6bdc..ea703bdad 100644 --- a/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml +++ b/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml @@ -10,6 +10,8 @@ framework: sender: sender@example.org recipients: - redirected@example.org + allowed_recipients: + - foobar@example\.org headers: from: from@example.org bcc: [bcc1@example.org, bcc2@example.org] diff --git a/Tests/DependencyInjection/Fixtures/yml/mailer_with_transports.yml b/Tests/DependencyInjection/Fixtures/yml/mailer_with_transports.yml index 59a5f14fd..ae10f6aee 100644 --- a/Tests/DependencyInjection/Fixtures/yml/mailer_with_transports.yml +++ b/Tests/DependencyInjection/Fixtures/yml/mailer_with_transports.yml @@ -13,6 +13,9 @@ framework: recipients: - redirected@example.org - redirected1@example.org + allowed_recipients: + - foobar@example\.org + - .*@example\.com headers: from: from@example.org bcc: [bcc1@example.org, bcc2@example.org] diff --git a/Tests/DependencyInjection/Fixtures/yml/trusted_proxies_private_ranges.yml b/Tests/DependencyInjection/Fixtures/yml/trusted_proxies_private_ranges.yml new file mode 100644 index 000000000..b98bb2f78 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/yml/trusted_proxies_private_ranges.yml @@ -0,0 +1,2 @@ +framework: + trusted_proxies: private_ranges diff --git a/Tests/DependencyInjection/Fixtures/yml/type_info.yml b/Tests/DependencyInjection/Fixtures/yml/type_info.yml new file mode 100644 index 000000000..4d6b405b2 --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/yml/type_info.yml @@ -0,0 +1,8 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + type_info: + enabled: true diff --git a/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 845056799..cb97f2a47 100644 --- a/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -52,6 +52,8 @@ use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\RetryableHttpClient; use Symfony\Component\HttpClient\ScopingHttpClient; +use Symfony\Component\HttpClient\ThrottlingHttpClient; +use Symfony\Component\HttpFoundation\IpUtils; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; @@ -83,7 +85,6 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Webhook\Client\RequestParser; use Symfony\Component\Webhook\Controller\WebhookController; -use Symfony\Component\Workflow; use Symfony\Component\Workflow\Exception\InvalidDefinitionException; use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; use Symfony\Component\Workflow\WorkflowEvents; @@ -301,7 +302,15 @@ public function testWorkflows() $this->assertArrayHasKey('index_4', $args); $this->assertNull($args['index_4'], 'Workflows has eventsToDispatch=null'); - $this->assertSame(['workflow' => [['name' => 'article']], 'workflow.workflow' => [['name' => 'article']]], $container->getDefinition('workflow.article')->getTags()); + $tags = $container->getDefinition('workflow.article')->getTags(); + $this->assertArrayHasKey('workflow', $tags); + $this->assertArrayHasKey('workflow.workflow', $tags); + $this->assertSame([['name' => 'article']], $tags['workflow.workflow']); + $this->assertSame('article', $tags['workflow'][0]['name'] ?? null); + $this->assertSame([ + 'title' => 'article workflow', + 'description' => 'workflow for articles', + ], $tags['workflow'][0]['metadata'] ?? null); $this->assertTrue($container->hasDefinition('workflow.article.definition'), 'Workflow definition is registered as a service'); @@ -332,7 +341,14 @@ public function testWorkflows() $this->assertSame('state_machine.abstract', $container->getDefinition('state_machine.pull_request')->getParent()); $this->assertTrue($container->hasDefinition('state_machine.pull_request.definition'), 'State machine definition is registered as a service'); - $this->assertSame(['workflow' => [['name' => 'pull_request']], 'workflow.state_machine' => [['name' => 'pull_request']]], $container->getDefinition('state_machine.pull_request')->getTags()); + $tags = $container->getDefinition('state_machine.pull_request')->getTags(); + $this->assertArrayHasKey('workflow', $tags); + $this->assertArrayHasKey('workflow.state_machine', $tags); + $this->assertSame([['name' => 'pull_request']], $tags['workflow.state_machine']); + $this->assertSame('pull_request', $tags['workflow'][0]['name'] ?? null); + $this->assertSame([ + 'title' => 'workflow title', + ], $tags['workflow'][0]['metadata'] ?? null); $stateMachineDefinition = $container->getDefinition('state_machine.pull_request.definition'); @@ -1630,6 +1646,12 @@ public function testSerializerServiceIsNotRegisteredWhenDisabled() $this->assertFalse($container->hasDefinition('serializer')); } + public function testTypeInfoEnabled() + { + $container = $this->createContainerFromFile('type_info'); + $this->assertTrue($container->has('type_info.resolver')); + } + public function testPropertyInfoEnabled() { $container = $this->createContainerFromFile('property_info'); @@ -1944,9 +1966,6 @@ public function testHttpClientOverrideDefaultOptions() public function testHttpClientRetry() { - if (!class_exists(RetryableHttpClient::class)) { - $this->expectException(LogicException::class); - } $container = $this->createContainerFromFile('http_client_retry'); $this->assertSame([429, 500 => ['GET', 'HEAD']], $container->getDefinition('http_client.retry_strategy')->getArgument(0)); @@ -2004,12 +2023,42 @@ public function testHttpClientFullDefaultOptions() $this->assertSame(['foo' => ['bar' => 'baz']], $defaultOptions['extra']); } + public function testHttpClientRateLimiter() + { + if (!class_exists(ThrottlingHttpClient::class)) { + $this->expectException(LogicException::class); + } + + $container = $this->createContainerFromFile('http_client_rate_limiter'); + + $this->assertTrue($container->hasDefinition('http_client.throttling')); + $definition = $container->getDefinition('http_client.throttling'); + $this->assertSame(ThrottlingHttpClient::class, $definition->getClass()); + $this->assertSame('http_client', $definition->getDecoratedService()[0]); + $this->assertCount(2, $arguments = $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $arguments[0]); + $this->assertSame('http_client.throttling.inner', (string) $arguments[0]); + $this->assertInstanceOf(Reference::class, $arguments[1]); + $this->assertSame('http_client.throttling.limiter', (string) $arguments[1]); + + $this->assertTrue($container->hasDefinition('foo.throttling')); + $definition = $container->getDefinition('foo.throttling'); + $this->assertSame(ThrottlingHttpClient::class, $definition->getClass()); + $this->assertSame('foo', $definition->getDecoratedService()[0]); + $this->assertCount(2, $arguments = $definition->getArguments()); + $this->assertInstanceOf(Reference::class, $arguments[0]); + $this->assertSame('foo.throttling.inner', (string) $arguments[0]); + $this->assertInstanceOf(Reference::class, $arguments[1]); + $this->assertSame('foo.throttling.limiter', (string) $arguments[1]); + } + public static function provideMailer(): iterable { yield [ 'mailer_with_dsn', ['main' => 'smtp://example.com'], ['redirected@example.org'], + ['foobar@example\.org'], ]; yield [ 'mailer_with_transports', @@ -2018,13 +2067,14 @@ public static function provideMailer(): iterable 'transport2' => 'smtp://example2.com', ], ['redirected@example.org', 'redirected1@example.org'], + ['foobar@example\.org', '.*@example\.com'], ]; } /** * @dataProvider provideMailer */ - public function testMailer(string $configFile, array $expectedTransports, array $expectedRecipients) + public function testMailer(string $configFile, array $expectedTransports, array $expectedRecipients, array $expectedAllowedRecipients) { $container = $this->createContainerFromFile($configFile); @@ -2037,6 +2087,7 @@ public function testMailer(string $configFile, array $expectedTransports, array $l = $container->getDefinition('mailer.envelope_listener'); $this->assertSame('sender@example.org', $l->getArgument(0)); $this->assertSame($expectedRecipients, $l->getArgument(1)); + $this->assertSame($expectedAllowedRecipients, $l->getArgument(2)); $this->assertEquals(new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE), $container->getDefinition('mailer.mailer')->getArgument(1)); $this->assertTrue($container->hasDefinition('mailer.message_listener')); @@ -2286,6 +2337,13 @@ public function testNotifierWithSpecificMessageBus() $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('notifier.channel.sms')->getArgument(1)); } + public function testTrustedProxiesWithPrivateRanges() + { + $container = $this->createContainerFromFile('trusted_proxies_private_ranges'); + + $this->assertSame(IpUtils::PRIVATE_SUBNETS, array_map('trim', explode(',', $container->getParameter('kernel.trusted_proxies')))); + } + public function testWebhook() { if (!class_exists(WebhookController::class)) { diff --git a/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/Tests/DependencyInjection/PhpFrameworkExtensionTest.php index 53268ffd2..deac159b6 100644 --- a/Tests/DependencyInjection/PhpFrameworkExtensionTest.php +++ b/Tests/DependencyInjection/PhpFrameworkExtensionTest.php @@ -245,4 +245,24 @@ public function testRateLimiterLockFactory() $container->getDefinition('limiter.without_lock')->getArgument(2); } + + public function testRateLimiterIsTagged() + { + $container = $this->createContainerFromClosure(function (ContainerBuilder $container) { + $container->loadFromExtension('framework', [ + 'annotations' => false, + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'lock' => true, + 'rate_limiter' => [ + 'first' => ['policy' => 'fixed_window', 'limit' => 10, 'interval' => '1 hour'], + 'second' => ['policy' => 'fixed_window', 'limit' => 10, 'interval' => '1 hour'], + ], + ]); + }); + + $this->assertSame('first', $container->getDefinition('limiter.first')->getTag('rate_limiter')[0]['name']); + $this->assertSame('second', $container->getDefinition('limiter.second')->getTag('rate_limiter')[0]['name']); + } } diff --git a/Tests/Functional/PropertyInfoTest.php b/Tests/Functional/PropertyInfoTest.php index c61955d37..18cd61b08 100644 --- a/Tests/Functional/PropertyInfoTest.php +++ b/Tests/Functional/PropertyInfoTest.php @@ -11,7 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; +use Symfony\Component\TypeInfo\Type; class PropertyInfoTest extends AbstractWebTestCase { @@ -19,7 +20,29 @@ public function testPhpDocPriority() { static::bootKernel(['test_case' => 'Serializer']); - $this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT))], static::getContainer()->get('property_info')->getTypes('Symfony\Bundle\FrameworkBundle\Tests\Functional\Dummy', 'codes')); + $propertyInfo = static::getContainer()->get('property_info'); + + if (!method_exists($propertyInfo, 'getType')) { + $this->markTestSkipped(); + } + + $this->assertEquals(Type::list(Type::int()), $propertyInfo->getType(Dummy::class, 'codes')); + } + + /** + * @group legacy + */ + public function testPhpDocPriorityLegacy() + { + static::bootKernel(['test_case' => 'Serializer']); + + $propertyInfo = static::getContainer()->get('property_info'); + + if (!method_exists($propertyInfo, 'getTypes')) { + $this->markTestSkipped(); + } + + $this->assertEquals([new LegacyType('array', false, null, true, new LegacyType('int'), new LegacyType('int'))], $propertyInfo->getTypes(Dummy::class, 'codes')); } } diff --git a/Tests/Functional/TypeInfoTest.php b/Tests/Functional/TypeInfoTest.php new file mode 100644 index 000000000..6acdb9c81 --- /dev/null +++ b/Tests/Functional/TypeInfoTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +use PHPStan\PhpDocParser\Parser\PhpDocParser; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\app\TypeInfo\Dummy; +use Symfony\Component\TypeInfo\Type; + +class TypeInfoTest extends AbstractWebTestCase +{ + public function testComponent() + { + static::bootKernel(['test_case' => 'TypeInfo']); + + $this->assertEquals(Type::string(), static::getContainer()->get('type_info.resolver')->resolve(new \ReflectionProperty(Dummy::class, 'name'))); + + if (!class_exists(PhpDocParser::class)) { + $this->markTestSkipped('"phpstan/phpdoc-parser" dependency is required.'); + } + + $this->assertEquals(Type::int(), static::getContainer()->get('type_info.resolver')->resolve('int')); + } +} diff --git a/Tests/Functional/app/TypeInfo/Dummy.php b/Tests/Functional/app/TypeInfo/Dummy.php new file mode 100644 index 000000000..0f517df51 --- /dev/null +++ b/Tests/Functional/app/TypeInfo/Dummy.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\app\TypeInfo; + +/** + * @author Mathias Arlaud + * @author Baptiste Leduc + */ +class Dummy +{ + public string $name; +} diff --git a/Tests/Functional/app/TypeInfo/bundles.php b/Tests/Functional/app/TypeInfo/bundles.php new file mode 100644 index 000000000..15ff182c6 --- /dev/null +++ b/Tests/Functional/app/TypeInfo/bundles.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle; + +return [ + new FrameworkBundle(), + new TestBundle(), +]; diff --git a/Tests/Functional/app/TypeInfo/config.yml b/Tests/Functional/app/TypeInfo/config.yml new file mode 100644 index 000000000..35c7bb4c4 --- /dev/null +++ b/Tests/Functional/app/TypeInfo/config.yml @@ -0,0 +1,11 @@ +imports: + - { resource: ../config/default.yml } + +framework: + http_method_override: false + type_info: true + +services: + type_info.resolver.alias: + alias: type_info.resolver + public: true diff --git a/Tests/Routing/RouterTest.php b/Tests/Routing/RouterTest.php index 3e185b54c..11c0dc7e6 100644 --- a/Tests/Routing/RouterTest.php +++ b/Tests/Routing/RouterTest.php @@ -23,6 +23,8 @@ use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\Routing\Loader\YamlFileLoader; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -406,7 +408,8 @@ public function testExceptionOnNonStringParameter() $routes->add('foo', new Route('/%object%')); $sc = $this->getPsr11ServiceContainer($routes); - $parameters = $this->getParameterBag(['object' => new \stdClass()]); + $parameters = new Container(); + $parameters->set('object', new \stdClass()); $router = new Router($sc, 'foo', [], null, $parameters); @@ -424,19 +427,15 @@ public function testExceptionOnNonStringParameterWithSfContainer() $sc = $this->getServiceContainer($routes); - $pc = $this->createMock(ContainerInterface::class); - $pc - ->expects($this->once()) - ->method('get') - ->willReturn(new \stdClass()) - ; + $pc = new Container(); + $pc->set('object', new \stdClass()); $router = new Router($sc, 'foo', [], null, $pc); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('The container parameter "object", used in the route configuration value "/%object%", must be a string or numeric, but it is of type "stdClass".'); - $router->getRouteCollection()->get('foo'); + $router->getRouteCollection(); } /** @@ -483,7 +482,9 @@ public function testGetRouteCollectionAddsContainerParametersResource() $router = new Router($sc, 'foo', [], null, $parameters); - $router->getRouteCollection(); + $routeCollection = $router->getRouteCollection(); + + $this->assertEquals([new ContainerParametersResource(['locale' => 'en'])], $routeCollection->getResources()); } public function testGetRouteCollectionAddsContainerParametersResourceWithSfContainer() @@ -617,13 +618,8 @@ private function getServiceContainer(RouteCollection $routes): Container ->willReturn($routes) ; - $sc = $this->getMockBuilder(Container::class)->onlyMethods(['get'])->getMock(); - - $sc - ->expects($this->once()) - ->method('get') - ->willReturn($loader) - ; + $sc = new Container(); + $sc->set('routing.loader', $loader); return $sc; } @@ -638,26 +634,14 @@ private function getPsr11ServiceContainer(RouteCollection $routes): ContainerInt ->willReturn($routes) ; - $sc = $this->createMock(ContainerInterface::class); - - $sc - ->expects($this->once()) - ->method('get') - ->willReturn($loader) - ; + $container = new Container(); + $container->set('routing.loader', $loader); - return $sc; + return $container; } private function getParameterBag(array $params = []): ContainerInterface { - $bag = $this->createMock(ContainerInterface::class); - $bag - ->expects($this->any()) - ->method('get') - ->willReturnCallback(fn ($key) => $params[$key] ?? null) - ; - - return $bag; + return new ContainerBag(new Container(new ParameterBag($params))); } } diff --git a/Tests/Secrets/SodiumVaultTest.php b/Tests/Secrets/SodiumVaultTest.php index 603d13504..96d5dcea1 100644 --- a/Tests/Secrets/SodiumVaultTest.php +++ b/Tests/Secrets/SodiumVaultTest.php @@ -21,16 +21,18 @@ class SodiumVaultTest extends TestCase { private string $secretsDir; + private Filesystem $filesystem; protected function setUp(): void { + $this->filesystem = new Filesystem(); $this->secretsDir = sys_get_temp_dir().'/sf_secrets/test/'; - (new Filesystem())->remove($this->secretsDir); + $this->filesystem->remove($this->secretsDir); } protected function tearDown(): void { - (new Filesystem())->remove($this->secretsDir); + $this->filesystem->remove($this->secretsDir); } public function testGenerateKeys() @@ -41,8 +43,8 @@ public function testGenerateKeys() $this->assertFileExists($this->secretsDir.'/test.encrypt.public.php'); $this->assertFileExists($this->secretsDir.'/test.decrypt.private.php'); - $encKey = file_get_contents($this->secretsDir.'/test.encrypt.public.php'); - $decKey = file_get_contents($this->secretsDir.'/test.decrypt.private.php'); + $encKey = $this->filesystem->readFile($this->secretsDir.'/test.encrypt.public.php'); + $decKey = $this->filesystem->readFile($this->secretsDir.'/test.decrypt.private.php'); $this->assertFalse($vault->generateKeys()); $this->assertStringEqualsFile($this->secretsDir.'/test.encrypt.public.php', $encKey); diff --git a/Tests/Translation/TranslatorTest.php b/Tests/Translation/TranslatorTest.php index c31119ad3..e481a965e 100644 --- a/Tests/Translation/TranslatorTest.php +++ b/Tests/Translation/TranslatorTest.php @@ -15,7 +15,7 @@ use Symfony\Bundle\FrameworkBundle\Translation\Translator; use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Config\Resource\FileExistenceResource; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Translation\Exception\InvalidArgumentException; use Symfony\Component\Translation\Formatter\MessageFormatter; @@ -100,16 +100,6 @@ public function testTransWithCaching() $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); } - public function testTransWithCachingWithInvalidLocale() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid "invalid locale" locale.'); - $loader = $this->createMock(LoaderInterface::class); - $translator = $this->getTranslator($loader, ['cache_dir' => $this->tmpDir], 'loader', TranslatorWithInvalidLocale::class); - - $translator->trans('foo'); - } - public function testLoadResourcesWithoutCaching() { $loader = new YamlFileLoader(); @@ -127,8 +117,7 @@ public function testLoadResourcesWithoutCaching() public function testGetDefaultLocale() { - $container = $this->createMock(\Psr\Container\ContainerInterface::class); - $translator = new Translator($container, new MessageFormatter(), 'en'); + $translator = new Translator(new Container(), new MessageFormatter(), 'en'); $this->assertSame('en', $translator->getLocale()); } @@ -137,9 +126,8 @@ public function testInvalidOptions() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('The Translator does not support the following options: \'foo\''); - $container = $this->createMock(ContainerInterface::class); - new Translator($container, new MessageFormatter(), 'en', [], ['foo' => 'bar']); + new Translator(new Container(), new MessageFormatter(), 'en', [], ['foo' => 'bar']); } /** @dataProvider getDebugModeAndCacheDirCombinations */ @@ -304,12 +292,9 @@ protected function getLoader() protected function getContainer($loader) { - $container = $this->createMock(ContainerInterface::class); - $container - ->expects($this->any()) - ->method('get') - ->willReturn($loader) - ; + $container = new Container(); + $container->set('loader', $loader); + $container->set('yml', $loader); return $container; } @@ -418,11 +403,3 @@ private function createTranslator($loader, $options, $translatorClass = Translat ); } } - -class TranslatorWithInvalidLocale extends Translator -{ - public function getLocale(): string - { - return 'invalid locale'; - } -} diff --git a/Translation/Translator.php b/Translation/Translator.php index 2f25aa74c..870d69ae1 100644 --- a/Translation/Translator.php +++ b/Translation/Translator.php @@ -21,11 +21,11 @@ /** * @author Fabien Potencier + * + * @final since Symfony 7.1 */ class Translator extends BaseTranslator implements WarmableInterface { - protected ContainerInterface $container; - protected array $loaderIds; protected array $options = [ 'cache_dir' => null, 'debug' => false, @@ -57,11 +57,6 @@ class Translator extends BaseTranslator implements WarmableInterface */ private array $scannedDirectories; - /** - * @var string[] - */ - private array $enabledLocales; - /** * Constructor. * @@ -72,14 +67,18 @@ class Translator extends BaseTranslator implements WarmableInterface * * resource_files: List of translation resources available grouped by locale. * * cache_vary: An array of data that is serialized to generate the cached catalogue name. * + * @param string[] $enabledLocales + * * @throws InvalidArgumentException */ - public function __construct(ContainerInterface $container, MessageFormatterInterface $formatter, string $defaultLocale, array $loaderIds = [], array $options = [], array $enabledLocales = []) - { - $this->container = $container; - $this->loaderIds = $loaderIds; - $this->enabledLocales = $enabledLocales; - + public function __construct( + protected ContainerInterface $container, + MessageFormatterInterface $formatter, + string $defaultLocale, + protected array $loaderIds = [], + array $options = [], + private array $enabledLocales = [], + ) { // check option names if ($diff = array_diff(array_keys($options), array_keys($this->options))) { throw new InvalidArgumentException(sprintf('The Translator does not support the following options: \'%s\'.', implode('\', \'', $diff))); diff --git a/composer.json b/composer.json index ed45087d2..af934f35d 100644 --- a/composer.json +++ b/composer.json @@ -21,14 +21,14 @@ "ext-xml": "*", "symfony/cache": "^6.4|^7.0", "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dependency-injection": "^7.1", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.4|^7.0", "symfony/event-dispatcher": "^6.4|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/filesystem": "^6.4|^7.0", + "symfony/filesystem": "^7.1", "symfony/finder": "^6.4|^7.0", "symfony/routing": "^6.4|^7.0" }, @@ -64,6 +64,7 @@ "symfony/string": "^6.4|^7.0", "symfony/translation": "^6.4|^7.0", "symfony/twig-bundle": "^6.4|^7.0", + "symfony/type-info": "^7.1", "symfony/validator": "^6.4|^7.0", "symfony/workflow": "^6.4|^7.0", "symfony/yaml": "^6.4|^7.0",