diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 885d5e0af2..fb773cc1b8 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -393,7 +393,7 @@ Routing ------- * The `generator_base_class`, `generator_cache_class`, `matcher_base_class`, and `matcher_cache_class` router - options have been removed. + options have been removed. If you are using multiple Router instances and need separate caches for them, set a unique `cache_dir` per Router instance instead. * `Serializable` implementing methods for `Route` and `CompiledRoute` are final. Instead of overwriting them, use `__serialize` and `__unserialize` as extension points which are forward compatible with the new serialization methods in PHP 7.4. diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index 35e806b10c..c5dcb2b043 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -40,7 +40,6 @@ class SymfonyTestsListenerTrait private $expectedDeprecations = array(); private $gatheredDeprecations = array(); private $previousErrorHandler; - private $reportUselessTests; private $error; private $runsInSeparateProcess = false; @@ -194,10 +193,6 @@ class SymfonyTestsListenerTrait public function startTest($test) { if (-2 < $this->state && ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) { - if (null !== $test->getTestResultObject()) { - $this->reportUselessTests = $test->getTestResultObject()->isStrictAboutTestsThatDoNotTestAnything(); - } - // This event is triggered before the test is re-run in isolation if ($this->willBeIsolated($test)) { $this->runsInSeparateProcess = tempnam(sys_get_temp_dir(), 'deprec'); @@ -243,11 +238,6 @@ class SymfonyTestsListenerTrait $className = \get_class($test); $groups = Test::getGroups($className, $test->getName(false)); - if (null !== $this->reportUselessTests) { - $test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything($this->reportUselessTests); - $this->reportUselessTests = null; - } - if ($errored = null !== $this->error) { $test->getTestResultObject()->addError($test, $this->error, 0); $this->error = null; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php index cc322847d0..1210b40ee0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php @@ -101,7 +101,7 @@ EOF (new SymfonyStyle($input, $output)) ->table(['Secret', 'Value'] + (null !== $localSecrets ? [2 => 'Local Value'] : []), $rows); - $io->comment("Local values override secret values.\nUse secrets:set --local to defined them."); + $io->comment("Local values override secret values.\nUse secrets:set --local to define them."); return 0; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index f578c2d246..c056928129 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -23,6 +23,7 @@ use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Translation\Catalogue\TargetOperation; use Symfony\Component\Translation\Extractor\ExtractorInterface; use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; use Symfony\Component\Translation\Reader\TranslationReaderInterface; use Symfony\Component\Translation\Writer\TranslationWriterInterface; @@ -216,6 +217,24 @@ EOF $resultMessage = 'Translation files were successfully updated'; + // move new messages to intl domain when possible + if (class_exists(\MessageFormatter::class)) { + foreach ($operation->getDomains() as $domain) { + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + $newMessages = $operation->getNewMessages($domain); + + if ([] === $newMessages || ([] === $currentCatalogue->all($intlDomain) && [] !== $currentCatalogue->all($domain))) { + continue; + } + + $result = $operation->getResult(); + $allIntlMessages = $result->all($intlDomain); + $currentMessages = array_diff_key($newMessages, $result->all($domain)); + $result->replace($currentMessages, $domain); + $result->replace($allIntlMessages + $newMessages, $intlDomain); + } + } + // show compiled list of messages if (true === $input->getOption('dump-messages')) { $extractedMessagesCount = 0; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 63727d8c1f..9ce51de76b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -311,6 +311,7 @@ class FrameworkExtension extends Extension $container->removeDefinition('console.command.messenger_failed_messages_retry'); $container->removeDefinition('console.command.messenger_failed_messages_show'); $container->removeDefinition('console.command.messenger_failed_messages_remove'); + $container->removeDefinition('cache.messenger.restart_workers_signal'); } if ($this->httpClientConfigEnabled = $this->isConfigEnabled($container, $config['http_client'])) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 0c91768a66..97c392812e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -412,6 +412,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php index 478b3983a1..07cc383189 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -43,6 +43,7 @@ abstract class KernelTestCase extends TestCase { static::ensureKernelShutdown(); static::$kernel = null; + static::$booted = false; } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/fragments_and_hinclude.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/fragments_and_hinclude.php new file mode 100644 index 0000000000..dbcf5b786d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/fragments_and_hinclude.php @@ -0,0 +1,8 @@ +loadFromExtension('framework', [ + 'fragments' => [ + 'enabled' => true, + 'hinclude_default_template' => 'global_hinclude_template', + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_disabled.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_disabled.php new file mode 100644 index 0000000000..e02542d977 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_disabled.php @@ -0,0 +1,5 @@ +loadFromExtension('framework', [ + 'messenger' => false, +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/fragments_and_hinclude.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/fragments_and_hinclude.xml new file mode 100644 index 0000000000..fb007313b9 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/fragments_and_hinclude.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_disabled.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_disabled.xml new file mode 100644 index 0000000000..6f57398b30 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_disabled.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/fragments_and_hinclude.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/fragments_and_hinclude.yml new file mode 100644 index 0000000000..b03f37da79 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/fragments_and_hinclude.yml @@ -0,0 +1,4 @@ +framework: + fragments: + enabled: true + hinclude_default_template: global_hinclude_template diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_disabled.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_disabled.yml new file mode 100644 index 0000000000..1b2d2d1a4f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_disabled.yml @@ -0,0 +1,2 @@ +framework: + messenger: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 28dc65d714..20ae6ae4ac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -159,6 +159,13 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertFalse($container->hasDefinition('esi')); } + public function testFragmentsAndHinclude() + { + $container = $this->createContainerFromFile('fragments_and_hinclude'); + $this->assertTrue($container->hasParameter('fragment.renderer.hinclude.global_template')); + $this->assertEquals('global_hinclude_template', $container->getParameter('fragment.renderer.hinclude.global_template')); + } + public function testSsi() { $container = $this->createContainerFromFile('full'); @@ -565,9 +572,23 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertTrue($container->hasDefinition('web_link.add_link_header_listener')); } + public function testMessengerServicesRemovedWhenDisabled() + { + $container = $this->createContainerFromFile('messenger_disabled'); + $this->assertFalse($container->hasDefinition('console.command.messenger_consume_messages')); + $this->assertFalse($container->hasDefinition('console.command.messenger_debug')); + $this->assertFalse($container->hasDefinition('console.command.messenger_stop_workers')); + $this->assertFalse($container->hasDefinition('console.command.messenger_setup_transports')); + $this->assertFalse($container->hasDefinition('console.command.messenger_failed_messages_retry')); + $this->assertFalse($container->hasDefinition('console.command.messenger_failed_messages_show')); + $this->assertFalse($container->hasDefinition('console.command.messenger_failed_messages_remove')); + $this->assertFalse($container->hasDefinition('cache.messenger.restart_workers_signal')); + } + public function testMessenger() { $container = $this->createContainerFromFile('messenger'); + $this->assertTrue($container->hasDefinition('console.command.messenger_consume_messages')); $this->assertTrue($container->hasAlias('messenger.default_bus')); $this->assertTrue($container->getAlias('messenger.default_bus')->isPublic()); $this->assertTrue($container->hasDefinition('messenger.transport.amqp.factory')); diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php index 4f889f485c..691c6659d5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\SecurityBundle\Debug; +use Symfony\Component\VarDumper\Caster\ClassStub; + /** * @author Robin Chalas * @@ -35,4 +37,13 @@ trait TraceableListenerTrait { return $this->listener; } + + public function getInfo(): array + { + return [ + 'response' => $this->response, + 'time' => $this->time, + 'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener), + ]; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php index 736e8e8a25..67b178f3f6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php @@ -59,13 +59,4 @@ final class WrappedLazyListener extends AbstractListener return $ret; } - - public function getInfo(): array - { - return [ - 'response' => $this->response, - 'time' => $this->time, - 'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener), - ]; - } } diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php index 11b921627f..d42a878c9d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php @@ -37,13 +37,4 @@ final class WrappedListener $this->time = microtime(true) - $startTime; $this->response = $event->getResponse(); } - - public function getInfo(): array - { - return [ - 'response' => $this->response, - 'time' => $this->time, - 'stub' => $this->stub ?? $this->stub = ClassStub::wrapCallable($this->listener), - ]; - } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php index 77eff72565..761aa314fa 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php @@ -114,6 +114,8 @@ class ResolveBindingsPass extends AbstractRecursivePass return parent::processValue($value, $isRoot); } + $bindingNames = []; + foreach ($bindings as $key => $binding) { list($bindingValue, $bindingId, $used, $bindingType, $file) = $binding->getValues(); if ($used) { @@ -123,7 +125,11 @@ class ResolveBindingsPass extends AbstractRecursivePass $this->unusedBindings[$bindingId] = [$key, $this->currentId, $bindingType, $file]; } - if (preg_match('/^(?:(?:array|bool|float|int|string) )?\$/', $key)) { + if (preg_match('/^(?:(?:array|bool|float|int|string|([^ $]++)) )\$/', $key, $m)) { + $bindingNames[substr($key, \strlen($m[0]))] = $binding; + } + + if (!isset($m[1])) { continue; } @@ -184,11 +190,17 @@ class ResolveBindingsPass extends AbstractRecursivePass continue; } - if (!$typeHint || '\\' !== $typeHint[0] || !isset($bindings[$typeHint = substr($typeHint, 1)])) { + if ($typeHint && '\\' === $typeHint[0] && isset($bindings[$typeHint = substr($typeHint, 1)])) { + $arguments[$key] = $this->getBindingValue($bindings[$typeHint]); + continue; } - $arguments[$key] = $this->getBindingValue($bindings[$typeHint]); + if (isset($bindingNames[$parameter->name])) { + $bindingKey = array_search($binding, $bindings, true); + $argumentType = substr($bindingKey, 0, strpos($bindingKey, ' ')); + $this->errorMessages[] = sprintf('Did you forget to add the type "%s" to argument "$%s" of method "%s::%s()"?', $argumentType, $parameter->name, $reflectionMethod->class, $reflectionMethod->name); + } } if ($arguments !== $call[1]) { diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index 0e23d93382..bd25e3f0ec 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -30,8 +30,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface public function __construct(ContainerInterface $container, \Traversable $loaders = null) { $this->container = $container; - $this->loaders = new \IteratorIterator($loaders ?? new \ArrayIterator()); - $this->loaders = $this->loaders->getInnerIterator(); + $this->loaders = $loaders ?? new \ArrayIterator(); } /** @@ -141,20 +140,32 @@ class EnvVarProcessor implements EnvVarProcessorInterface } } - $loaders = $this->loaders; - $this->loaders = new \ArrayIterator(); + if (false === $env || null === $env) { + $loaders = $this->loaders; + $this->loaders = new \ArrayIterator(); - try { - while ((false === $env || null === $env) && $loaders->valid()) { - $loader = $loaders->current(); - $loaders->next(); - $this->loadedVars[] = $vars = $loader->loadEnvVars(); - $env = $vars[$name] ?? false; + try { + $i = 0; + $ended = true; + $count = $loaders instanceof \Countable ? $loaders->count() : 0; + foreach ($loaders as $loader) { + if (\count($this->loadedVars) > $i++) { + continue; + } + $this->loadedVars[] = $vars = $loader->loadEnvVars(); + if (false !== $env = $vars[$name] ?? false) { + $ended = false; + break; + } + } + if ($ended || $count === $i) { + $loaders = $this->loaders; + } + } catch (ParameterCircularReferenceException $e) { + // skip loaders that need an env var that is not defined + } finally { + $this->loaders = $loaders; } - } catch (ParameterCircularReferenceException $e) { - // skip loaders that need an env var that is not defined - } finally { - $this->loaders = $loaders; } if (false === $env || null === $env) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php index 87a5cf2261..443114b0a2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php @@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Compiler\DefinitionErrorExceptionPass; use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy; @@ -169,4 +170,19 @@ class ResolveBindingsPassTest extends TestCase $this->assertSame([1 => 'bar'], $container->getDefinition(NamedArgumentsDummy::class)->getArguments()); } + + public function testEmptyBindingTypehint() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Did you forget to add the type "string" to argument "$apiKey" of method "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy::__construct()"?'); + + $container = new ContainerBuilder(); + $bindings = [ + 'string $apiKey' => new BoundArgument('foo'), + ]; + $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); + $definition->setBindings($bindings); + $pass = new ResolveBindingsPass(); + $pass->process($container); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php index 9970eb474f..df329b4f7e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php @@ -3,10 +3,12 @@ namespace Symfony\Component\DependencyInjection\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\EnvVarLoaderInterface; use Symfony\Component\DependencyInjection\EnvVarProcessor; +use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; class EnvVarProcessorTest extends TestCase { @@ -553,4 +555,44 @@ CSV; $result = $processor->getEnv('string', 'FOO_ENV_LOADER', function () {}); $this->assertSame('123', $result); // check twice } + + public function testCircularEnvLoader() + { + $container = new ContainerBuilder(); + $container->setParameter('env(FOO_CONTAINER)', 'foo'); + $container->compile(); + + $index = 0; + $loaders = function () use (&$index) { + if (0 === $index++) { + throw new ParameterCircularReferenceException(['FOO_CONTAINER']); + } + + yield new class() implements EnvVarLoaderInterface { + public function loadEnvVars(): array + { + return [ + 'FOO_ENV_LOADER' => '123', + ]; + } + }; + }; + + $processor = new EnvVarProcessor($container, new RewindableGenerator($loaders, 1)); + + $result = $processor->getEnv('string', 'FOO_CONTAINER', function () {}); + $this->assertSame('foo', $result); + + $result = $processor->getEnv('string', 'FOO_ENV_LOADER', function () {}); + $this->assertSame('123', $result); + + $result = $processor->getEnv('default', ':BAR_CONTAINER', function ($name) use ($processor) { + $this->assertSame('BAR_CONTAINER', $name); + + return $processor->getEnv('string', $name, function () {}); + }); + $this->assertNull($result); + + $this->assertSame(2, $index); + } } diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 76c10b015b..3fa4d6bb51 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -206,11 +206,13 @@ class Filesystem /** * Change the owner of an array of files or directories. * - * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner + * @param string|int $user A user name or number + * @param bool $recursive Whether change the owner recursively or not * * @throws IOException When the change fails */ - public function chown($files, string $user, bool $recursive = false) + public function chown($files, $user, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { if ($recursive && is_dir($file) && !is_link($file)) { @@ -231,11 +233,13 @@ class Filesystem /** * Change the group of an array of files or directories. * - * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group + * @param string|int $group A group name or number + * @param bool $recursive Whether change the group recursively or not * * @throws IOException When the change fails */ - public function chgrp($files, string $group, bool $recursive = false) + public function chgrp($files, $group, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { if ($recursive && is_dir($file) && !is_link($file)) { diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index d4299da4d9..dcd3c61406 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -521,7 +521,7 @@ class FilesystemTest extends FilesystemTestCase $this->assertFilePermissions(753, $subdirectory); } - public function testChown() + public function testChownByName() { $this->markAsSkippedIfPosixIsMissing(); @@ -534,7 +534,20 @@ class FilesystemTest extends FilesystemTestCase $this->assertSame($owner, $this->getFileOwner($dir)); } - public function testChownRecursive() + public function testChownById() + { + $this->markAsSkippedIfPosixIsMissing(); + + $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + + $ownerId = $this->getFileOwnerId($dir); + $this->filesystem->chown($dir, $ownerId); + + $this->assertSame($ownerId, $this->getFileOwnerId($dir)); + } + + public function testChownRecursiveByName() { $this->markAsSkippedIfPosixIsMissing(); @@ -549,6 +562,21 @@ class FilesystemTest extends FilesystemTestCase $this->assertSame($owner, $this->getFileOwner($file)); } + public function testChownRecursiveById() + { + $this->markAsSkippedIfPosixIsMissing(); + + $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + $file = $dir.\DIRECTORY_SEPARATOR.'file'; + touch($file); + + $ownerId = $this->getFileOwnerId($dir); + $this->filesystem->chown($dir, $ownerId, true); + + $this->assertSame($ownerId, $this->getFileOwnerId($file)); + } + public function testChownSymlink() { $this->markAsSkippedIfSymlinkIsMissing(); @@ -624,7 +652,7 @@ class FilesystemTest extends FilesystemTestCase $this->filesystem->chown($dir, 'user'.time().mt_rand(1000, 9999)); } - public function testChgrp() + public function testChgrpByName() { $this->markAsSkippedIfPosixIsMissing(); @@ -637,6 +665,19 @@ class FilesystemTest extends FilesystemTestCase $this->assertSame($group, $this->getFileGroup($dir)); } + public function testChgrpById() + { + $this->markAsSkippedIfPosixIsMissing(); + + $dir = $this->workspace.\DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + + $groupId = $this->getFileGroupId($dir); + $this->filesystem->chgrp($dir, $groupId); + + $this->assertSame($groupId, $this->getFileGroupId($dir)); + } + public function testChgrpRecursive() { $this->markAsSkippedIfPosixIsMissing(); @@ -652,7 +693,7 @@ class FilesystemTest extends FilesystemTestCase $this->assertSame($group, $this->getFileGroup($file)); } - public function testChgrpSymlink() + public function testChgrpSymlinkByName() { $this->markAsSkippedIfSymlinkIsMissing(); @@ -669,6 +710,23 @@ class FilesystemTest extends FilesystemTestCase $this->assertSame($group, $this->getFileGroup($link)); } + public function testChgrpSymlinkById() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $file = $this->workspace.\DIRECTORY_SEPARATOR.'file'; + $link = $this->workspace.\DIRECTORY_SEPARATOR.'link'; + + touch($file); + + $this->filesystem->symlink($file, $link); + + $groupId = $this->getFileGroupId($link); + $this->filesystem->chgrp($link, $groupId); + + $this->assertSame($groupId, $this->getFileGroupId($link)); + } + public function testChgrpLink() { $this->markAsSkippedIfLinkIsMissing(); diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php index 08afefbb24..396a3ab2e8 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php @@ -105,21 +105,36 @@ class FilesystemTestCase extends TestCase ); } - protected function getFileOwner($filepath) + protected function getFileOwnerId($filepath) { $this->markAsSkippedIfPosixIsMissing(); $infos = stat($filepath); - return ($datas = posix_getpwuid($infos['uid'])) ? $datas['name'] : null; + return $infos['uid']; + } + + protected function getFileOwner($filepath) + { + $this->markAsSkippedIfPosixIsMissing(); + + return ($datas = posix_getpwuid($this->getFileOwnerId($filepath))) ? $datas['name'] : null; + } + + protected function getFileGroupId($filepath) + { + $this->markAsSkippedIfPosixIsMissing(); + + $infos = stat($filepath); + + return $infos['gid']; } protected function getFileGroup($filepath) { $this->markAsSkippedIfPosixIsMissing(); - $infos = stat($filepath); - if ($datas = posix_getgrgid($infos['gid'])) { + if ($datas = posix_getgrgid($this->getFileGroupId($filepath))) { return $datas['name']; } diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php index 158bcef04e..b7b576e564 100644 --- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php @@ -253,7 +253,7 @@ trait ResponseTrait private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers, string &$debug = ''): void { foreach ($responseHeaders as $h) { - if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([12345]\d\d) .*#', $h, $m)) { + if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([12345]\d\d)(?: |$)#', $h, $m)) { if ($headers) { $debug .= "< \r\n"; $headers = []; diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 698af72ce2..443d77d672 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -350,6 +350,10 @@ class HttpCache implements HttpKernelInterface, TerminableInterface return $this->validate($request, $entry, $catch); } + if ($entry->headers->hasCacheControlDirective('no-cache')) { + return $this->validate($request, $entry, $catch); + } + $this->record($request, 'fresh'); $entry->headers->set('Age', $entry->getAge()); diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php index aa2c611455..34f1dc7f3b 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php @@ -443,6 +443,22 @@ class HttpCacheTest extends HttpCacheTestCase $this->assertTrue($this->response->headers->has('Age')); } + public function testRevalidatesResponsesWithNoCacheDirectiveEvenIfFresh() + { + $this->setNextResponse(200, ['Cache-Control' => 'public, no-cache, max-age=10', 'ETag' => 'some-etag'], 'OK'); + $this->request('GET', '/'); // warm the cache + + sleep(5); + + $this->setNextResponse(304, ['Cache-Control' => 'public, no-cache, max-age=10', 'ETag' => 'some-etag']); + $this->request('GET', '/'); + + $this->assertHttpKernelIsCalled(); // no-cache -> MUST have revalidated at origin + $this->assertTraceContains('valid'); + $this->assertEquals('OK', $this->response->getContent()); + $this->assertEquals(0, $this->response->getAge()); + } + public function testCachesResponsesWithAnExpirationHeader() { $time = \DateTime::createFromFormat('U', time() + 5); diff --git a/src/Symfony/Component/Translation/Dumper/FileDumper.php b/src/Symfony/Component/Translation/Dumper/FileDumper.php index 463a08c75b..e257e72246 100644 --- a/src/Symfony/Component/Translation/Dumper/FileDumper.php +++ b/src/Symfony/Component/Translation/Dumper/FileDumper.php @@ -51,37 +51,36 @@ abstract class FileDumper implements DumperInterface throw new InvalidArgumentException('The file dumper needs a path option.'); } - $hasMessageFormatter = class_exists(\MessageFormatter::class); - // save a file for each domain foreach ($messages->getDomains() as $domain) { - if ($hasMessageFormatter) { - $defaultDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX; - $altDomain = $domain; - } else { - $defaultDomain = $domain; - $altDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX; - } - $defaultPath = $options['path'].'/'.$this->getRelativePath($defaultDomain, $messages->getLocale()); - $altPath = $options['path'].'/'.$this->getRelativePath($altDomain, $messages->getLocale()); - - if (!file_exists($defaultPath) && file_exists($altPath)) { - [$defaultPath, $altPath] = [$altPath, $defaultPath]; - } - - if (!file_exists($defaultPath)) { - $directory = \dirname($defaultPath); + $fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale()); + if (!file_exists($fullpath)) { + $directory = \dirname($fullpath); if (!file_exists($directory) && !@mkdir($directory, 0777, true)) { throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory)); } } - if (file_exists($altPath)) { - // clear alternative translation file - file_put_contents($altPath, $this->formatCatalogue(new MessageCatalogue($messages->getLocale()), $altDomain, $options)); + $intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX; + $intlMessages = $messages->all($intlDomain); + + if ($intlMessages) { + $intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale()); + file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options)); + + $messages->replace([], $intlDomain); + + try { + if ($messages->all($domain)) { + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } + continue; + } finally { + $messages->replace($intlMessages, $intlDomain); + } } - file_put_contents($defaultPath, $this->formatCatalogue($messages, $domain, $options)); + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); } } diff --git a/src/Symfony/Component/Translation/Tests/Dumper/FileDumperTest.php b/src/Symfony/Component/Translation/Tests/Dumper/FileDumperTest.php index 9dd1377e49..6e42b1e568 100644 --- a/src/Symfony/Component/Translation/Tests/Dumper/FileDumperTest.php +++ b/src/Symfony/Component/Translation/Tests/Dumper/FileDumperTest.php @@ -27,15 +27,11 @@ class FileDumperTest extends TestCase $dumper = new ConcreteFileDumper(); $dumper->dump($catalogue, ['path' => $tempDir]); - $suffix = class_exists(\MessageFormatter::class) ? '+intl-icu' : ''; - $this->assertFileExists($tempDir."/messages$suffix.en.concrete"); + $this->assertFileExists($tempDir.'/messages.en.concrete'); - @unlink($tempDir."/messages$suffix.en.concrete"); + @unlink($tempDir.'/messages.en.concrete'); } - /** - * @requires extension intl - */ public function testDumpIntl() { $tempDir = sys_get_temp_dir(); @@ -46,11 +42,13 @@ class FileDumperTest extends TestCase $catalogue->add(['bar' => 'foo'], 'd2+intl-icu'); $dumper = new ConcreteFileDumper(); + @unlink($tempDir.'/d2.en.concrete'); $dumper->dump($catalogue, ['path' => $tempDir]); - $this->assertFileNotExists($tempDir.'/d1.en.concrete'); + $this->assertStringEqualsFile($tempDir.'/d1.en.concrete', 'foo=bar'); + @unlink($tempDir.'/d1.en.concrete'); - $this->assertStringEqualsFile($tempDir.'/d1+intl-icu.en.concrete', 'bar=foo&foo=bar'); + $this->assertStringEqualsFile($tempDir.'/d1+intl-icu.en.concrete', 'bar=foo'); @unlink($tempDir.'/d1+intl-icu.en.concrete'); $this->assertFileNotExists($tempDir.'/d2.en.concrete'); @@ -62,8 +60,7 @@ class FileDumperTest extends TestCase { $tempDir = sys_get_temp_dir(); $translationsDir = $tempDir.'/test/translations'; - $suffix = class_exists(\MessageFormatter::class) ? '+intl-icu' : ''; - $file = $translationsDir."/messages$suffix.en.concrete"; + $file = $translationsDir.'/messages.en.concrete'; $catalogue = new MessageCatalogue('en'); $catalogue->add(['foo' => 'bar']); diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf index 018dd1233a..20dff43c6d 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf @@ -24,11 +24,11 @@ You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. - Morate odabrati bar {{ limit }} mogućnost.|Morate odabrati bar {{ limit }} mogućnosti. + Morate odabrati bar {{ limit }} mogućnost.|Morate odabrati bar {{ limit }} mogućnosti.|Morate odabrati bar {{ limit }} mogućnosti. You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. - Morate odabrati najviše {{ limit }} mogućnost.|Morate odabrati najviše {{ limit }} mogućnosti. + Morate odabrati najviše {{ limit }} mogućnost.|Morate odabrati najviše {{ limit }} mogućnosti.|Morate odabrati najviše {{ limit }} mogućnosti. One or more of the given values is invalid. @@ -76,7 +76,7 @@ This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. - Vrednost je predugačka. Trebalo bi da ima {{ limit }} karakter ili manje.|Vrednost je predugačka. Trebalo bi da ima {{ limit }} karaktera ili manje. + Vrednost je predugačka. Trebalo bi da ima {{ limit }} karakter ili manje.|Vrednost je predugačka. Trebalo bi da ima {{ limit }} karaktera ili manje.|Vrednost je predugačka. Trebalo bi da ima {{ limit }} karaktera ili manje. This value should be {{ limit }} or more. @@ -84,7 +84,7 @@ This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. - Vrednost je prekratka. Trebalo bi da ima {{ limit }} karakter ili više.|Vrednost je prekratka. Trebalo bi da ima {{ limit }} karaktera ili više. + Vrednost je prekratka. Trebalo bi da ima {{ limit }} karakter ili više.|Vrednost je prekratka. Trebalo bi da ima {{ limit }} karaktera ili više.|Vrednost je prekratka. Trebalo bi da ima {{ limit }} karaktera ili više. This value should not be blank. @@ -180,7 +180,7 @@ This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. - Vrednost bi trebalo da ima tačno {{ limit }} karakter.|Vrednost bi trebalo da ima tačno {{ limit }} karaktera. + Vrednost bi trebalo da ima tačno {{ limit }} karakter.|Vrednost bi trebalo da ima tačno {{ limit }} karaktera.|Vrednost bi trebalo da ima tačno {{ limit }} karaktera. The file was only partially uploaded. @@ -204,15 +204,15 @@ This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. - Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata. + Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata. This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. - Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata. + Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata. This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. - Ova kolekcija bi trebalo da sadrži tačno {{ limit }} element.|Ova kolekcija bi trebalo da sadrži tačno {{ limit }} elemenata. + Ova kolekcija bi trebalo da sadrži tačno {{ limit }} element.|Ova kolekcija bi trebalo da sadrži tačno {{ limit }} elementa.|Ova kolekcija bi trebalo da sadrži tačno {{ limit }} elemenata. Invalid card number. diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index fc662b5d43..40f2bfc8db 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -439,6 +439,11 @@ class Inline throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); } + if ('!php/const' === $key) { + $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false, []); + $key = self::evaluateScalar($key, $flags); + } + if (false === $i = strpos($mapping, ':', $i)) { break; } diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index 9b07f8725d..0f8f8f8b94 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -59,6 +59,7 @@ class InlineTest extends TestCase ['!php/const PHP_INT_MAX', PHP_INT_MAX], ['[!php/const PHP_INT_MAX]', [PHP_INT_MAX]], ['{ foo: !php/const PHP_INT_MAX }', ['foo' => PHP_INT_MAX]], + ['{ !php/const PHP_INT_MAX: foo }', [PHP_INT_MAX => 'foo']], ['!php/const NULL', null], ]; }