diff --git a/.travis.yml b/.travis.yml index 79b484ee7ee95..372d685608963 100644 --- a/.travis.yml +++ b/.travis.yml @@ -161,7 +161,7 @@ before_install: fi tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI - tfold ext.mongodb tpecl mongodb-1.5.2 mongodb.so $INI + tfold ext.mongodb tpecl mongodb-1.6.0alpha1 mongodb.so $INI tfold ext.amqp tpecl amqp-1.9.3 amqp.so $INI tfold ext.igbinary tpecl igbinary-2.0.6 igbinary.so $INI tfold ext.zookeeper tpecl zookeeper-0.5.0 zookeeper.so $INI @@ -226,7 +226,7 @@ install: break fi phpenv global $PHP - ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.5.2; composer require --dev --no-update mongodb/mongodb) + ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.6.0; composer require --dev --no-update mongodb/mongodb) tfold 'composer update' $COMPOSER_UP tfold 'phpunit install' ./phpunit install if [[ $deps = high ]]; then diff --git a/CHANGELOG-4.2.md b/CHANGELOG-4.2.md index 5e748a7913537..40c53e2040d3a 100644 --- a/CHANGELOG-4.2.md +++ b/CHANGELOG-4.2.md @@ -7,6 +7,33 @@ in 4.2 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.2.0...v4.2.1 +* 4.2.1 (2018-12-06) + + * security #cve-2018-19790 [Security\Http] detect bad redirect targets using backslashes (xabbuh) + * security #cve-2018-19789 [Form] Filter file uploads out of regular form types (nicolas-grekas) + * bug #29481 [TwigBridge] Deprecating legacy Twig paths in DebugCommand and simplifications (yceruto) + * bug #29436 [Cache] Fixed Memcached adapter doClear()to call flush() (raitocz) + * bug #29482 Fixes sprintf(): Too few arguments in MessageFormatter::choiceFormat (stephanedelprat) + * bug #29461 [Contracts] extract LocaleAwareInterface out of TranslatorInterface (nicolas-grekas) + * bug #29446 [VarExporter] fix dumping private properties from abstract classes (nicolas-grekas) + * bug #29441 [Routing] ignore trailing slash for non-GET requests (nicolas-grekas) + * bug #29445 [FrameworkBundle] Fix empty output for debug:autowiring when reflection-docblock is not installed (chalasr) + * bug #29444 [Workflow] Fixed BC break for Workflow metadata (lyrixx) + * bug #29432 [DI] dont inline when lazy edges are found (nicolas-grekas) + * bug #29413 [Serializer] fixed DateTimeNormalizer to maintain microseconds when a different timezone required (rvitaliy) + * bug #29424 [Routing] fix taking verb into account when redirecting (nicolas-grekas) + * bug #29418 [VarExporter] fix dumping protected property from abstract classes (nicolas-grekas) + * bug #29414 [DI] Fix dumping expressions accessing single-use private services (chalasr) + * bug #28853 [LDAP] Add TIMEOUT Option to LDAP Connection Options (lmatte7) + * bug #29399 [FrameworkBundle] define doctrine as default_pdo_provider only if the package is installed (nicolas-grekas) + * bug #29375 [Validator] Allow `ConstraintViolation::__toString()` to expose codes that are not null or emtpy strings (phansys) + * bug #29376 [EventDispatcher] Fix eventListener wrapper loop in TraceableEventDispatcher (jderusse) + * bug #29386 undeprecate the single-colon notation for controllers (fbourigault) + * bug #29393 [DI] fix edge case in InlineServiceDefinitionsPass (nicolas-grekas) + * bug #29394 [Config] fix path exclusion during glob discovery (nicolas-grekas) + * bug #29395 [FrameworkBundle][Messenger] Restore check for messenger serializer default id (ogizanagi) + * bug #29380 [Routing] fix greediness of trailing slash (nicolas-grekas) + * 4.2.0 (2018-11-30) * bug #29343 [Form] Handle all case variants of "nan" when parsing a number (mwhudson, xabbuh) diff --git a/UPGRADE-4.2.md b/UPGRADE-4.2.md index a2fadabc95b14..90cd04d01e69a 100644 --- a/UPGRADE-4.2.md +++ b/UPGRADE-4.2.md @@ -68,6 +68,7 @@ Finder Form ---- + * The `symfony/translation` dependency has been removed - run `composer require symfony/translation` if you need the component * The `getExtendedType()` method of the `FormTypeExtensionInterface` is deprecated and will be removed in 5.0. Type extensions must implement the static `getExtendedTypes()` method instead and return an iterable of extended types. @@ -285,6 +286,23 @@ Messenger ``` * The `EncoderInterface` and `DecoderInterface` interfaces have been replaced by a unified `Symfony\Component\Messenger\Transport\Serialization\SerializerInterface`. Each interface method have been merged untouched into the `Serializer` interface, so you can simply merge your two implementations together and implement the new interface. + * The `HandlerLocator` class was replaced with `Symfony\Component\Messenger\Handler\HandlersLocator`. + + Before: + ```php + new HandlerLocator([ + YourMessage::class => $handlerCallable, + ]); + ``` + + After: + ```php + new HandlersLocator([ + YourMessage::class => [ + $handlerCallable, + ] + ]); + ``` Monolog ------- @@ -364,6 +382,7 @@ TwigBundle Validator --------- + * The `symfony/translation` dependency has been removed - run `composer require symfony/translation` if you need the component * The `checkMX` and `checkHost` options of the `Email` constraint are deprecated * The component is now decoupled from `symfony/translation` and uses `Symfony\Contracts\Translation\TranslatorInterface` instead * The `ValidatorBuilderInterface` has been deprecated and `ValidatorBuilder` made final diff --git a/composer.json b/composer.json index 839e21780b88d..b0fcede7bf746 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "psr/link": "^1.0", "psr/log": "~1.0", "psr/simple-cache": "^1.0", - "symfony/contracts": "^1.0", + "symfony/contracts": "^1.0.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-mbstring": "~1.0", diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index bc828b43e21b6..db7c8ce7a341e 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -46,7 +46,8 @@ }, "conflict": { "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/dependency-injection": "<3.4" + "symfony/dependency-injection": "<3.4", + "symfony/messenger": "<4.2" }, "suggest": { "symfony/form": "", diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index 072022c0a417e..e9d9eda30e05b 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -208,8 +208,8 @@ private function displayGeneralText(SymfonyStyle $io, string $filter = null) $io->table(array('Namespace', 'Paths'), $this->buildTableRows($paths)); } - if ($wronBundles = $this->findWrongBundleOverrides()) { - foreach ($this->buildWarningMessages($wronBundles) as $message) { + if ($wrongBundles = $this->findWrongBundleOverrides()) { + foreach ($this->buildWarningMessages($wrongBundles) as $message) { $io->warning($message); } } @@ -253,13 +253,7 @@ private function getLoaderPaths(string $name = null): array } foreach ($namespaces as $namespace) { - $paths = array_map(function ($path) { - if (null !== $this->projectDir && 0 === strpos($path, $this->projectDir)) { - $path = ltrim(substr($path, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); - } - - return $path; - }, $loader->getPaths($namespace)); + $paths = array_map(array($this, 'getRelativePath'), $loader->getPaths($namespace)); if (FilesystemLoader::MAIN_NAMESPACE === $namespace) { $namespace = '(None)'; @@ -368,53 +362,38 @@ private function findWrongBundleOverrides(): array if ($this->rootDir && $this->projectDir) { $folders = glob($this->rootDir.'/Resources/*/views', GLOB_ONLYDIR); - $relativePath = ltrim(substr($this->rootDir.'/Resources/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); - $bundleNames = array_reduce( - $folders, - function ($carry, $absolutePath) use ($relativePath) { - if (0 === strpos($absolutePath, $this->projectDir)) { - $name = basename(\dirname($absolutePath)); - $path = $relativePath.$name; - $carry[$name] = $path; - } + $relativePath = ltrim(substr($this->rootDir.\DIRECTORY_SEPARATOR.'Resources/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + $bundleNames = array_reduce($folders, function ($carry, $absolutePath) use ($relativePath) { + if (0 === strpos($absolutePath, $this->projectDir)) { + $name = basename(\dirname($absolutePath)); + $path = ltrim($relativePath.$name, \DIRECTORY_SEPARATOR); + $carry[$name] = $path; + + @trigger_error(sprintf('Templates directory "%s" is deprecated since Symfony 4.2, use "%s" instead.', $absolutePath, $this->twigDefaultPath.'/bundles/'.$name), E_USER_DEPRECATED); + } - return $carry; - }, - $bundleNames - ); + return $carry; + }, $bundleNames); } if ($this->twigDefaultPath && $this->projectDir) { $folders = glob($this->twigDefaultPath.'/bundles/*', GLOB_ONLYDIR); - $relativePath = ltrim(substr($this->twigDefaultPath.'/bundles', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); - $bundleNames = array_reduce( - $folders, - function ($carry, $absolutePath) use ($relativePath) { - if (0 === strpos($absolutePath, $this->projectDir)) { - $path = ltrim(substr($absolutePath, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); - $name = ltrim(substr($path, \strlen($relativePath)), \DIRECTORY_SEPARATOR); - $carry[$name] = $path; - } - - return $carry; - }, - $bundleNames - ); - } - - if (\count($bundleNames)) { - $notFoundBundles = array_diff_key($bundleNames, $this->bundlesMetadata); - if (\count($notFoundBundles)) { - $alternatives = array(); - foreach ($notFoundBundles as $notFoundBundle => $path) { - $alternatives[$path] = array(); - foreach ($this->bundlesMetadata as $name => $bundle) { - $lev = levenshtein($notFoundBundle, $name); - if ($lev <= \strlen($notFoundBundle) / 3 || false !== strpos($name, $notFoundBundle)) { - $alternatives[$path][] = $name; - } - } + $relativePath = ltrim(substr($this->twigDefaultPath.'/bundles/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + $bundleNames = array_reduce($folders, function ($carry, $absolutePath) use ($relativePath) { + if (0 === strpos($absolutePath, $this->projectDir)) { + $name = basename($absolutePath); + $path = ltrim($relativePath.$name, \DIRECTORY_SEPARATOR); + $carry[$name] = $path; } + + return $carry; + }, $bundleNames); + } + + if ($notFoundBundles = array_diff_key($bundleNames, $this->bundlesMetadata)) { + $alternatives = array(); + foreach ($notFoundBundles as $notFoundBundle => $path) { + $alternatives[$path] = $this->findAlternatives($notFoundBundle, array_keys($this->bundlesMetadata)); } } diff --git a/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php index ed3fea871e68f..e648a52d84a73 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/DebugCommandTest.php @@ -42,6 +42,52 @@ public function testFilterAndJsonFormatOptions() $this->assertEquals($expected, json_decode($tester->getDisplay(true), true)); } + public function testWarningsWrongBundleOverriding() + { + $bundleMetadata = array( + 'TwigBundle' => 'vendor/twig-bundle/', + 'WebProfilerBundle' => 'vendor/web-profiler-bundle/', + ); + $defaultPath = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'templates'; + + $tester = $this->createCommandTester(array(), $bundleMetadata, $defaultPath); + $ret = $tester->execute(array('--filter' => 'unknown', '--format' => 'json'), array('decorated' => false)); + + $expected = array('warnings' => array( + 'Path "templates/bundles/UnknownBundle" not matching any bundle found', + 'Path "templates/bundles/WebProfileBundle" not matching any bundle found, did you mean "WebProfilerBundle"?', + )); + + $this->assertEquals(0, $ret, 'Returns 0 in case of success'); + $this->assertEquals($expected, json_decode($tester->getDisplay(true), true)); + } + + /** + * @group legacy + * @expectedDeprecation Templates directory "%sResources/BarBundle/views" is deprecated since Symfony 4.2, use "%stemplates/bundles/BarBundle" instead. + */ + public function testDeprecationForWrongBundleOverridingInLegacyPath() + { + $bundleMetadata = array( + 'TwigBundle' => 'vendor/twig-bundle/', + 'WebProfilerBundle' => 'vendor/web-profiler-bundle/', + ); + $defaultPath = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'templates'; + $rootDir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'; + + $tester = $this->createCommandTester(array(), $bundleMetadata, $defaultPath, $rootDir); + $ret = $tester->execute(array('--filter' => 'unknown', '--format' => 'json'), array('decorated' => false)); + + $expected = array('warnings' => array( + 'Path "Resources/BarBundle" not matching any bundle found', + 'Path "templates/bundles/UnknownBundle" not matching any bundle found', + 'Path "templates/bundles/WebProfileBundle" not matching any bundle found, did you mean "WebProfilerBundle"?', + )); + + $this->assertEquals(0, $ret, 'Returns 0 in case of success'); + $this->assertEquals($expected, json_decode($tester->getDisplay(true), true)); + } + /** * @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException * @expectedExceptionMessage Malformed namespaced template name "@foo" (expecting "@namespace/template_name"). @@ -233,7 +279,7 @@ public function getDebugTemplateNameTestData() ); } - private function createCommandTester(array $paths = array()): CommandTester + private function createCommandTester(array $paths = array(), array $bundleMetadata = array(), string $defaultPath = null, string $rootDir = null): CommandTester { $projectDir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'; $loader = new FilesystemLoader(array(), $projectDir); @@ -246,7 +292,7 @@ private function createCommandTester(array $paths = array()): CommandTester } $application = new Application(); - $application->add(new DebugCommand(new Environment($loader), $projectDir)); + $application->add(new DebugCommand(new Environment($loader), $projectDir, $bundleMetadata, $defaultPath, $rootDir)); $command = $application->find('debug:twig'); return new CommandTester($command); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/StubTranslator.php b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/StubTranslator.php index 083124d6af70d..bd9191161a7c4 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/StubTranslator.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/StubTranslator.php @@ -19,17 +19,4 @@ public function trans($id, array $parameters = array(), $domain = null, $locale { return '[trans]'.$id.'[/trans]'; } - - public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) - { - return '[trans]'.$id.'[/trans]'; - } - - public function setLocale($locale) - { - } - - public function getLocale() - { - } } diff --git a/src/Symfony/Bridge/Twig/Tests/Fixtures/Resources/BarBundle/views/base.html.twig b/src/Symfony/Bridge/Twig/Tests/Fixtures/Resources/BarBundle/views/base.html.twig new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Symfony/Bridge/Twig/Tests/Fixtures/templates/bundles/UnknownBundle/base.html.twig b/src/Symfony/Bridge/Twig/Tests/Fixtures/templates/bundles/UnknownBundle/base.html.twig new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Symfony/Bridge/Twig/Tests/Fixtures/templates/bundles/WebProfileBundle/Profiler/base.html.twig b/src/Symfony/Bridge/Twig/Tests/Fixtures/templates/bundles/WebProfileBundle/Profiler/base.html.twig new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Symfony/Bridge/Twig/TokenParser/TransChoiceTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/TransChoiceTokenParser.php index 490fb14ff45e5..ea605111cac1c 100644 --- a/src/Symfony/Bridge/Twig/TokenParser/TransChoiceTokenParser.php +++ b/src/Symfony/Bridge/Twig/TokenParser/TransChoiceTokenParser.php @@ -40,7 +40,7 @@ public function parse(Token $token) $lineno = $token->getLine(); $stream = $this->parser->getStream(); - @trigger_error(sprintf('The "transchoice" tag is deprecated since Symfony 4.2, use the "trans" one instead with a "%count%" parameter in %s line %d.', $stream->getSourceContext()->getName(), $lineno), E_USER_DEPRECATED); + @trigger_error(sprintf('The "transchoice" tag is deprecated since Symfony 4.2, use the "trans" one instead with a "%%count%%" parameter in %s line %d.', $stream->getSourceContext()->getName(), $lineno), E_USER_DEPRECATED); $vars = new ArrayExpression(array(), $lineno); diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 2441cbf9faa3d..ec0d36a2b99e3 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", + "symfony/contracts": "^1.0.2", "twig/twig": "^1.35|^2.4.4" }, "require-dev": { diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php index 3cfca24983347..22f2d41d68161 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php @@ -292,7 +292,7 @@ protected function sortServiceIds(array $serviceIds) */ public static function getClassDescription(string $class, string &$resolvedClass = null): string { - $resolvedClass = null; + $resolvedClass = $class; if (!interface_exists(DocBlockFactoryInterface::class)) { return ''; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 17f67a0159cc3..12f2092320685 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -13,6 +13,7 @@ use Doctrine\Common\Annotations\Annotation; use Doctrine\Common\Cache\Cache; +use Doctrine\DBAL\Connection; use Symfony\Bundle\FullStack; use Symfony\Component\Asset\Package; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; @@ -882,7 +883,7 @@ private function addCacheSection(ArrayNodeDefinition $rootNode) ->scalarNode('default_psr6_provider')->end() ->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end() ->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end() - ->scalarNode('default_pdo_provider')->defaultValue('doctrine.dbal.default_connection')->end() + ->scalarNode('default_pdo_provider')->defaultValue(class_exists(Connection::class) ? 'database_connection' : null)->end() ->arrayNode('pools') ->useAttributeAsKey('name') ->prototype('array') @@ -1059,7 +1060,7 @@ function ($a) { }) ->end() ->children() - ->scalarNode('id')->defaultValue('messenger.transport.symfony_serializer')->end() + ->scalarNode('id')->defaultValue(!class_exists(FullStack::class) && class_exists(Serializer::class) ? 'messenger.transport.symfony_serializer' : null)->end() ->scalarNode('format')->defaultValue('json')->end() ->arrayNode('context') ->normalizeKeys(false) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index f4b20ee92d0a7..faaebada1592d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -547,7 +547,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ } if ($transition['metadata']) { $transitionsMetadataDefinition->addMethodCall('attach', array( - $transitionDefinition, + new Reference($transitionId), $transition['metadata'], )); } @@ -569,7 +569,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ } if ($transition['metadata']) { $transitionsMetadataDefinition->addMethodCall('attach', array( - $transitionDefinition, + new Reference($transitionId), $transition['metadata'], )); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php index 54f20bcfa175f..84e237626ce2a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php @@ -100,7 +100,7 @@ public function load($resource, $type = null) if (1 === substr_count($controller, ':')) { $nonDeprecatedNotation = str_replace(':', '::', $controller); - @trigger_error(sprintf('Referencing controllers with a single colon is deprecated since Symfony 4.1, use "%s" instead.', $nonDeprecatedNotation), E_USER_DEPRECATED); + // TODO deprecate this in 5.1 } $route->setDefault('_controller', $controller); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php index f3e4ce96032a1..481500c2cb986 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/DataCollectorTranslatorPassTest.php @@ -111,16 +111,4 @@ class TranslatorWithTranslatorBag implements TranslatorInterface public function trans($id, array $parameters = array(), $domain = null, $locale = null) { } - - public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) - { - } - - public function setLocale($locale) - { - } - - public function getLocale() - { - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index d7e8324b55b5e..9a3b2183e12db 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; +use Doctrine\DBAL\Connection; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Configuration; use Symfony\Bundle\FullStack; @@ -18,6 +19,7 @@ use Symfony\Component\Config\Definition\Processor; use Symfony\Component\Lock\Store\SemaphoreStore; use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Serializer\Serializer; class ConfigurationTest extends TestCase { @@ -267,7 +269,7 @@ protected static function getBundleDefaultConfig() 'directory' => '%kernel.cache_dir%/pools', 'default_redis_provider' => 'redis://localhost', 'default_memcached_provider' => 'memcached://localhost', - 'default_pdo_provider' => 'doctrine.dbal.default_connection', + 'default_pdo_provider' => class_exists(Connection::class) ? 'database_connection' : null, ), 'workflows' => array( 'enabled' => false, @@ -293,7 +295,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'routing' => array(), 'transports' => array(), 'serializer' => array( - 'id' => 'messenger.transport.symfony_serializer', + 'id' => !class_exists(FullStack::class) && class_exists(Serializer::class) ? 'messenger.transport.symfony_serializer' : null, 'format' => 'json', 'context' => array(), ), diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php index 020068d6247be..60b68f1fdb3e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_routing.php @@ -3,6 +3,7 @@ $container->loadFromExtension('framework', array( 'serializer' => true, 'messenger' => array( + 'serializer' => 'messenger.transport.symfony_serializer', 'routing' => array( 'Symfony\Component\Messenger\Tests\Fixtures\DummyMessage' => array('amqp', 'audit'), 'Symfony\Component\Messenger\Tests\Fixtures\SecondMessage' => array( diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transport.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transport.php index 76d9894df75f2..728a1a910c143 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transport.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transport.php @@ -4,6 +4,7 @@ 'serializer' => true, 'messenger' => array( 'serializer' => array( + 'id' => 'messenger.transport.symfony_serializer', 'format' => 'csv', 'context' => array('enable_max_depth' => true), ), diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transport_no_serializer.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transport_no_serializer.php index ebddbc4bc6b28..f861e25690805 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transport_no_serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transport_no_serializer.php @@ -5,6 +5,7 @@ 'enabled' => false, ), 'messenger' => array( + 'serializer' => 'messenger.transport.symfony_serializer', 'transports' => array( 'default' => 'amqp://localhost/%2f/messages', ), diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php index 3f90bda7f3777..4edc0d880a250 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_transports.php @@ -3,6 +3,7 @@ $container->loadFromExtension('framework', array( 'serializer' => true, 'messenger' => array( + 'serializer' => 'messenger.transport.symfony_serializer', 'transports' => array( 'default' => 'amqp://localhost/%2f/messages', 'customised' => array( diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml index e3d95fc83f496..2ddc776f479fd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_routing.xml @@ -8,6 +8,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transport.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transport.xml index 135e6f1b78fb1..36d1ada9b8ec1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transport.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transport.xml @@ -8,7 +8,7 @@ - + true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transport_no_serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transport_no_serializer.xml index 38550f08a749a..290fbcf14e47d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transport_no_serializer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transport_no_serializer.xml @@ -8,6 +8,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml index 211135570c232..4760368448ff7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_transports.xml @@ -8,6 +8,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml index 38e1fb431c7d9..1eedbbd03d0e6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_routing.yml @@ -1,6 +1,7 @@ framework: serializer: true messenger: + serializer: messenger.transport.symfony_serializer routing: 'Symfony\Component\Messenger\Tests\Fixtures\DummyMessage': [amqp, audit] 'Symfony\Component\Messenger\Tests\Fixtures\SecondMessage': diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transport.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transport.yml index 7433097eb8ea8..05b2083e0abdc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transport.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transport.yml @@ -2,6 +2,7 @@ framework: serializer: true messenger: serializer: + id: messenger.transport.symfony_serializer format: csv context: enable_max_depth: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transport_no_serializer.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transport_no_serializer.yml index b4ff40db66f03..bde0d3537d57b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transport_no_serializer.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transport_no_serializer.yml @@ -2,5 +2,6 @@ framework: serializer: enabled: false messenger: + serializer: messenger.transport.symfony_serializer transports: default: 'amqp://localhost/%2f/messages' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml index 0c38638679598..6d2c535b72f80 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_transports.yml @@ -1,6 +1,7 @@ framework: serializer: true messenger: + serializer: messenger.transport.symfony_serializer transports: default: 'amqp://localhost/%2f/messages' customised: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index d5690d7628273..e52b2c7c2e0a6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -258,10 +258,8 @@ public function testWorkflows() $this->assertSame('attach', $transitionsMetadataCall[0]); $params = $transitionsMetadataCall[1]; $this->assertCount(2, $params); - $this->assertInstanceOf(Definition::class, $params[0]); - $this->assertSame(Workflow\Transition::class, $params[0]->getClass()); - $this->assertSame(array('submit', 'start', 'travis'), $params[0]->getArguments()); - $this->assertSame(array('title' => 'transition submit title'), $params[1]); + $this->assertInstanceOf(Reference::class, $params[0]); + $this->assertSame('state_machine.pull_request.transition.0', (string) $params[0]); $serviceMarkingStoreWorkflowDefinition = $container->getDefinition('workflow.service_marking_store_workflow'); /** @var Reference $markingStoreRef */ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php index 2cef381f7272c..f7f4272fed7f9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php @@ -68,7 +68,6 @@ public function testLoadDefaultOptions() /** * @group legacy * @expectedDeprecation Referencing controllers with foo:bar:baz is deprecated since Symfony 4.1, use "some_parsed::controller" instead. - * @expectedDeprecation Referencing controllers with a single colon is deprecated since Symfony 4.1, use "foo::baz" instead. */ public function testLoad() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Fixtures/StubTranslator.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Fixtures/StubTranslator.php index 08c0a476b434a..377a67d6be832 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Fixtures/StubTranslator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Fixtures/StubTranslator.php @@ -19,17 +19,4 @@ public function trans($id, array $parameters = array(), $domain = null, $locale { return '[trans]'.$id.'[/trans]'; } - - public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) - { - return '[trans]'.$id.'[/trans]'; - } - - public function setLocale($locale) - { - } - - public function getLocale() - { - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index f2b85c5f0c84a..fa14dcc25edf0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -19,8 +19,9 @@ "php": "^7.1.3", "ext-xml": "*", "symfony/cache": "~4.2", - "symfony/dependency-injection": "^4.2", "symfony/config": "~4.2", + "symfony/contracts": "^1.0.2", + "symfony/dependency-injection": "^4.2", "symfony/event-dispatcher": "^4.1", "symfony/http-foundation": "^4.1.2", "symfony/http-kernel": "^4.2", diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig index 4556749de8688..a2091e85aa67a 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig @@ -84,16 +84,15 @@ thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-vis .sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; } .tab-navigation { margin: 0 0 1em 0; padding: 0; } -.tab-navigation li { background: #FFF; border: 1px solid #DDD; color: #444; cursor: pointer; display: inline-block; font-size: 16px; margin: 0 0 0 -1px; padding: .5em .75em; z-index: 1; } -.tab-navigation li:hover { background: #EEE; } -.tab-navigation li.disabled { background: #F5F5F5; color: #999; } -.tab-navigation li.active { background: #666; border-color: #666; color: #FAFAFA; z-index: 1100; } -.tab-navigation li .badge { background-color: #F5F5F5; color: #777; display: inline-block; font-size: 14px; font-weight: bold; margin-left: 8px; min-width: 10px; padding: 1px 6px; text-align: center; } -.tab-navigation li:hover .badge { background: #FAFAFA; color: #777; } -.tab-navigation li.active .badge { background-color: #444; color: #FFF; } -.tab-navigation li .badge.status-warning { background: #A46A1F; color: #FFF; } -.tab-navigation li .badge.status-error { background: #B0413E; color: #FFF; } +.tab-navigation li { background: var(--tab-background); border: 1px solid var(--table-border); color: var(--tab-color); cursor: pointer; display: inline-block; font-size: 16px; margin: 0 0 0 -1px; padding: .5em .75em; z-index: 1; } +.tab-navigation li .badge { background-color: var(--base-1); color: var(--base-4); display: inline-block; font-size: 14px; font-weight: bold; margin-left: 8px; min-width: 10px; padding: 1px 6px; text-align: center; white-space: nowrap; } +.tab-navigation li.disabled { background: var(--tab-disabled-background); color: var(--tab-disabled-color); } +.tab-navigation li.active { background: var(--tab-active-background); color: var(--tab-active-color); z-index: 1100; } +.tab-navigation li.active .badge { background-color: var(--base-5); color: var(--base-2); } .tab-content > *:first-child { margin-top: 0; } +.tab-navigation li .badge.status-warning { background: var(--color-warning); color: #FFF; } +.tab-navigation li .badge.status-error { background: var(--color-error); color: #FFF; } +.sf-tabs .tab:not(:first-child) { display: none; } [data-filters] { position: relative; } [data-filtered] { cursor: pointer; } @@ -126,7 +125,7 @@ thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-vis .filter-list-level li.active { cursor: n-resize; } .filter-list-level li.last-active { cursor: default; } .filter-list-level li.last-active:before { content: '\2714\00a0'; } -.filter-list-choice li:before { content: '\2714\00a0'; color: var(--tab-background); } +.filter-list-choice li:before { content: '\2714\00a0'; color: transparent; } .filter-list-choice li.active:before { color: unset; } .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig index e0de7b570af4d..c0be48a377032 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig @@ -47,7 +47,7 @@
-

Orphaned events {{ collector.orphanedEvents|length }}

+

Orphaned Events {{ collector.orphanedEvents|length }}

{% if collector.orphanedEvents is empty %}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig index 02b77319ac249..720da85750526 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -177,6 +177,12 @@ color: inherit; text-decoration: inherit; } + h2 + h3.form-data-type { + margin-top: 0; + } + h3.form-data-type + h3 { + margin-top: 1em; + } {% endblock %} @@ -455,9 +461,10 @@ {% macro form_tree_details(name, data, forms_by_hash, show) %} {% import _self as tree %}
-

- {{ name|default('(no name)') }} {% if data.type_class is defined %}({{ profiler_dump(data.type_class) }}){% endif %} -

+

{{ name|default('(no name)') }}

+ {% if data.type_class is defined %} +

{{ profiler_dump(data.type_class) }}

+ {% endif %} {% if data.errors is defined and data.errors|length > 0 %}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig index 5f6582ed38677..dd5b970148e68 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig @@ -957,7 +957,7 @@ tr.status-warning td { .filter-list-level li.active { cursor: n-resize; } .filter-list-level li.last-active { cursor: default; } .filter-list-level li.last-active:before { content: '\2714\00a0'; } -.filter-list-choice li:before { content: '\2714\00a0'; color: var(--tab-background); } +.filter-list-choice li:before { content: '\2714\00a0'; color: transparent; } .filter-list-choice li.active:before { color: unset; } {# Twig panel diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index 0f4d40879d22d..79f4ddb596cc6 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -32,6 +32,7 @@ "conflict": { "symfony/dependency-injection": "<3.4", "symfony/event-dispatcher": "<3.4", + "symfony/messenger": "<4.2", "symfony/var-dumper": "<3.4" }, "autoload": { diff --git a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php index 85292994fe0a0..4ebe4c879891e 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php @@ -193,6 +193,11 @@ public function provideDsnWithOptions() ); } + public function testClear() + { + $this->assertTrue($this->createCachePool()->clear()); + } + public function testMultiServerDsn() { $dsn = 'memcached:?host[localhost]&host[localhost:12345]&host[/some/memcached.sock:]=3'; diff --git a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php index 534de598e3230..1e1718a4127ef 100644 --- a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php +++ b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php @@ -292,7 +292,7 @@ protected function doDelete(array $ids) */ protected function doClear($namespace) { - return false; + return '' === $namespace && $this->getClient()->flush(); } private function checkResultCode($result) diff --git a/src/Symfony/Component/Config/Resource/GlobResource.php b/src/Symfony/Component/Config/Resource/GlobResource.php index 323352058f701..bd110d72f5d94 100644 --- a/src/Symfony/Component/Config/Resource/GlobResource.php +++ b/src/Symfony/Component/Config/Resource/GlobResource.php @@ -96,9 +96,19 @@ public function getIterator() if (!file_exists($this->prefix) || (!$this->recursive && '' === $this->pattern)) { return; } + $prefix = str_replace('\\', '/', $this->prefix); if (0 !== strpos($this->prefix, 'phar://') && false === strpos($this->pattern, '/**/') && (\defined('GLOB_BRACE') || false === strpos($this->pattern, '{'))) { foreach (glob($this->prefix.$this->pattern, \defined('GLOB_BRACE') ? GLOB_BRACE : 0) as $path) { + if ($this->excludedPrefixes) { + $normalizedPath = str_replace('\\', '/', $path); + do { + if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) { + continue 2; + } + } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath)); + } + if (is_file($path)) { yield $path => new \SplFileInfo($path); } @@ -145,9 +155,19 @@ function (\SplFileInfo $file, $path) { $prefixLen = \strlen($this->prefix); foreach ($finder->followLinks()->sortByName()->in($this->prefix) as $path => $info) { - if (preg_match($regex, substr(str_replace('\\', '/', $path), $prefixLen)) && $info->isFile()) { - yield $path => $info; + $normalizedPath = str_replace('\\', '/', $path); + if (!preg_match($regex, substr($normalizedPath, $prefixLen)) || !$info->isFile()) { + continue; } + if ($this->excludedPrefixes) { + do { + if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) { + continue 2; + } + } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath)); + } + + yield $path => $info; } } diff --git a/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php index ac7ae5bd7507f..188b5572ecb42 100644 --- a/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/GlobResourceTest.php @@ -73,6 +73,20 @@ public function testIteratorSkipsFoldersForGivenExcludedPrefixes() $this->assertArrayNotHasKey($file, $paths); } + public function testIteratorSkipsSubfoldersForGivenExcludedPrefixes() + { + $dir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'; + $resource = new GlobResource($dir, '/*Exclude/*', true, false, array($dir.\DIRECTORY_SEPARATOR.'Exclude' => true)); + + $paths = iterator_to_array($resource); + + $file = $dir.\DIRECTORY_SEPARATOR.'Exclude'.\DIRECTORY_SEPARATOR.'AnExcludedFile.txt'; + $this->assertArrayNotHasKey($file, $paths); + + $file = $dir.\DIRECTORY_SEPARATOR.'Exclude'.\DIRECTORY_SEPARATOR.'ExcludeToo'.\DIRECTORY_SEPARATOR.'AnotheExcludedFile.txt'; + $this->assertArrayNotHasKey($file, $paths); + } + public function testIteratorSkipsFoldersWithForwardSlashForGivenExcludedPrefixes() { $dir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index d4766a16ec0b2..8681c56f69fcc 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -165,10 +165,14 @@ private function isInlineableDefinition($id, Definition $definition) } if (!$definition->isShared()) { + if (!$this->graph->hasNode($id)) { + return true; + } + foreach ($this->graph->getNode($id)->getInEdges() as $edge) { $srcId = $edge->getSourceNode()->getId(); $this->connectedIds[$srcId] = true; - if ($edge->isWeak()) { + if ($edge->isWeak() || $edge->isLazy()) { return false; } } @@ -196,7 +200,7 @@ private function isInlineableDefinition($id, Definition $definition) $isReferencedByConstructor = $isReferencedByConstructor || $edge->isReferencedByConstructor(); $srcId = $edge->getSourceNode()->getId(); $this->connectedIds[$srcId] = true; - if ($edge->isWeak()) { + if ($edge->isWeak() || $edge->isLazy()) { return false; } $srcIds[$srcId] = true; diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 204a991850b74..71accf4f8c9a4 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -597,9 +597,7 @@ private function addServiceConfigurator(Definition $definition, string $variable if ($callable[0] instanceof Reference || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0])) ) { - $callable[0] = $this->dumpValue($callable[0]); - - return sprintf(' '.('$' === $callable[0][0] ? '%s' : '(%s)')."->%s(\$%s);\n", $callable[0], $callable[1], $variableName); + return sprintf(" %s->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); } $class = $this->dumpValue($callable[0]); @@ -1639,6 +1637,7 @@ private function getServiceCall(string $id, Reference $reference = null): string if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { $code = sprintf('$this->%s[\'%s\'] = %s', $definition->isPublic() ? 'services' : 'privates', $id, $code); } + $code = "($code)"; } elseif ($this->asFiles && !$this->isHotPath($definition)) { $code = sprintf("\$this->load('%s.php')", $this->generateMethodName($id)); if (!$definition->isShared()) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 9798d849fa999..cb41a6d33fb3f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -799,7 +799,7 @@ public function testExpressionReferencingPrivateService() ->setPublic(false); $container->register('public_foo', 'stdClass') ->setPublic(true) - ->addArgument(new Expression('service("private_foo")')); + ->addArgument(new Expression('service("private_foo").bar')); $container->compile(); $dumper = new PhpDumper($container); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt index 126d5fba32cc7..6d305e570f008 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt @@ -338,7 +338,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; return $this->services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () { yield 0 => ($this->services['foo'] ?? $this->load('getFooService.php')); - yield 1 => ($this->privates['tagged_iterator_foo'] ?? $this->privates['tagged_iterator_foo'] = new \Bar()); + yield 1 => ($this->privates['tagged_iterator_foo'] ?? ($this->privates['tagged_iterator_foo'] = new \Bar())); }, 2)); [Container%s/getThrowingOneService.php] => services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () { yield 0 => ($this->services['foo'] ?? $this->getFooService()); - yield 1 => ($this->privates['tagged_iterator_foo'] ?? $this->privates['tagged_iterator_foo'] = new \Bar()); + yield 1 => ($this->privates['tagged_iterator_foo'] ?? ($this->privates['tagged_iterator_foo'] = new \Bar())); }, 2)); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index 7d2970de589a0..1f03714232cbe 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -103,7 +103,7 @@ protected function getBar3Service() { $this->services['bar3'] = $instance = new \BarCircular(); - $a = ($this->services['foobar3'] ?? $this->services['foobar3'] = new \FoobarCircular()); + $a = ($this->services['foobar3'] ?? ($this->services['foobar3'] = new \FoobarCircular())); $instance->addFoobar($a, $a); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php index de90f200d6bfe..969137450a9da 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php @@ -59,7 +59,7 @@ public function getRemovedIds() */ protected function getBarService() { - return $this->services['bar'] = new \stdClass(($this->privates['bar_%env(BAR)%'] ?? $this->privates['bar_%env(BAR)%'] = new \stdClass())); + return $this->services['bar'] = new \stdClass(($this->privates['bar_%env(BAR)%'] ?? ($this->privates['bar_%env(BAR)%'] = new \stdClass()))); } /** @@ -69,7 +69,7 @@ protected function getBarService() */ protected function getFooService() { - return $this->services['foo'] = new \stdClass(($this->privates['bar_%env(BAR)%'] ?? $this->privates['bar_%env(BAR)%'] = new \stdClass()), array('baz_'.$this->getEnv('string:BAR') => new \stdClass())); + return $this->services['foo'] = new \stdClass(($this->privates['bar_%env(BAR)%'] ?? ($this->privates['bar_%env(BAR)%'] = new \stdClass())), array('baz_'.$this->getEnv('string:BAR') => new \stdClass())); } public function getParameter($name) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php index 9620bb599174f..22d6ebbf13ec3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php @@ -391,7 +391,7 @@ protected function getTaggedIteratorService() { return $this->services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () { yield 0 => ($this->services['foo'] ?? $this->getFooService()); - yield 1 => ($this->privates['tagged_iterator_foo'] ?? $this->privates['tagged_iterator_foo'] = new \Bar()); + yield 1 => ($this->privates['tagged_iterator_foo'] ?? ($this->privates['tagged_iterator_foo'] = new \Bar())); }, 2)); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php index 4edbb3d7cd55c..4ea482ee2236f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php @@ -65,7 +65,7 @@ public function getRemovedIds() */ protected function getBarServiceService() { - return $this->services['bar_service'] = new \stdClass(($this->privates['baz_service'] ?? $this->privates['baz_service'] = new \stdClass())); + return $this->services['bar_service'] = new \stdClass(($this->privates['baz_service'] ?? ($this->privates['baz_service'] = new \stdClass()))); } /** @@ -78,7 +78,7 @@ protected function getFooServiceService() return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\ServiceLocator(array('bar' => function () { return ($this->services['bar_service'] ?? $this->getBarServiceService()); }, 'baz' => function (): \stdClass { - return ($this->privates['baz_service'] ?? $this->privates['baz_service'] = new \stdClass()); + return ($this->privates['baz_service'] ?? ($this->privates['baz_service'] = new \stdClass())); }, 'nil' => function () { return NULL; })); @@ -122,7 +122,7 @@ protected function getTranslator_Loader3Service() protected function getTranslator1Service() { return $this->services['translator_1'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_1' => function () { - return ($this->services['translator.loader_1'] ?? $this->services['translator.loader_1'] = new \stdClass()); + return ($this->services['translator.loader_1'] ?? ($this->services['translator.loader_1'] = new \stdClass())); }))); } @@ -134,10 +134,10 @@ protected function getTranslator1Service() protected function getTranslator2Service() { $this->services['translator_2'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_2' => function () { - return ($this->services['translator.loader_2'] ?? $this->services['translator.loader_2'] = new \stdClass()); + return ($this->services['translator.loader_2'] ?? ($this->services['translator.loader_2'] = new \stdClass())); }))); - $instance->addResource('db', ($this->services['translator.loader_2'] ?? $this->services['translator.loader_2'] = new \stdClass()), 'nl'); + $instance->addResource('db', ($this->services['translator.loader_2'] ?? ($this->services['translator.loader_2'] = new \stdClass())), 'nl'); return $instance; } @@ -150,10 +150,10 @@ protected function getTranslator2Service() protected function getTranslator3Service() { $this->services['translator_3'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_3' => function () { - return ($this->services['translator.loader_3'] ?? $this->services['translator.loader_3'] = new \stdClass()); + return ($this->services['translator.loader_3'] ?? ($this->services['translator.loader_3'] = new \stdClass())); }))); - $a = ($this->services['translator.loader_3'] ?? $this->services['translator.loader_3'] = new \stdClass()); + $a = ($this->services['translator.loader_3'] ?? ($this->services['translator.loader_3'] = new \stdClass())); $instance->addResource('db', $a, 'nl'); $instance->addResource('db', $a, 'en'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php index 1fe2259fa85d9..559a3ecebfa30 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php @@ -56,7 +56,7 @@ public function getRemovedIds() */ protected function getBarServiceService() { - return $this->services['bar_service'] = new \stdClass(($this->privates['baz_service'] ?? $this->privates['baz_service'] = new \stdClass())); + return $this->services['bar_service'] = new \stdClass(($this->privates['baz_service'] ?? ($this->privates['baz_service'] = new \stdClass()))); } /** @@ -66,6 +66,6 @@ protected function getBarServiceService() */ protected function getFooServiceService() { - return $this->services['foo_service'] = new \stdClass(($this->privates['baz_service'] ?? $this->privates['baz_service'] = new \stdClass())); + return $this->services['foo_service'] = new \stdClass(($this->privates['baz_service'] ?? ($this->privates['baz_service'] = new \stdClass()))); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php index 1d2a3b21b8bbf..db3a5a28153e8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php @@ -56,6 +56,6 @@ public function getRemovedIds() */ protected function getPublicFooService() { - return $this->services['public_foo'] = new \stdClass(new \stdClass()); + return $this->services['public_foo'] = new \stdClass((new \stdClass())->bar); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php index 828266d6bc2e0..f18de622b2951 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php @@ -96,7 +96,7 @@ protected function getBazService() { $this->services['baz'] = $instance = new \stdClass(); - $instance->foo3 = ($this->privates['foo3'] ?? $this->privates['foo3'] = new \stdClass()); + $instance->foo3 = ($this->privates['foo3'] ?? ($this->privates['foo3'] = new \stdClass())); return $instance; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index 32e404cc5b149..87971eadd8369 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -18,7 +18,6 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Loader\FileLoader; use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; @@ -217,32 +216,20 @@ public function testRegisterClassesWithBadPrefix() } /** - * @dataProvider getIncompatibleExcludeTests + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid "exclude" pattern when importing classes for "Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\": make sure your "exclude" pattern (yaml/*) is a subset of the "resource" pattern (Prototype/*) */ - public function testRegisterClassesWithIncompatibleExclude($resourcePattern, $excludePattern) + public function testRegisterClassesWithIncompatibleExclude() { $container = new ContainerBuilder(); $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); - try { - $loader->registerClasses( - new Definition(), - 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', - $resourcePattern, - $excludePattern - ); - } catch (InvalidArgumentException $e) { - $this->assertEquals( - sprintf('Invalid "exclude" pattern when importing classes for "Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\": make sure your "exclude" pattern (%s) is a subset of the "resource" pattern (%s)', $excludePattern, $resourcePattern), - $e->getMessage() - ); - } - } - - public function getIncompatibleExcludeTests() - { - yield array('Prototype/*', 'yaml/*', false); - yield array('Prototype/OtherDir/*', 'Prototype/*', false); + $loader->registerClasses( + new Definition(), + 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', + 'Prototype/*', + 'yaml/*' + ); } } diff --git a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php index 083bd39e96d93..92000b6bf5b28 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php @@ -134,19 +134,24 @@ public function dispatch($eventName, Event $event = null) } $this->preProcess($eventName); - $this->preDispatch($eventName, $event); - - $e = $this->stopwatch->start($eventName, 'section'); - - $this->dispatcher->dispatch($eventName, $event); - - if ($e->isStarted()) { - $e->stop(); + try { + $this->preDispatch($eventName, $event); + try { + $e = $this->stopwatch->start($eventName, 'section'); + try { + $this->dispatcher->dispatch($eventName, $event); + } finally { + if ($e->isStarted()) { + $e->stop(); + } + } + } finally { + $this->postDispatch($eventName, $event); + } + } finally { + $this->postProcess($eventName); } - $this->postDispatch($eventName, $event); - $this->postProcess($eventName); - return $event; } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 1b699d51e84ab..56fcfe17e9bf7 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -105,6 +105,7 @@ public function configureOptions(OptionsResolver $resolver) 'data_class' => $dataClass, 'empty_data' => $emptyData, 'multiple' => false, + 'allow_file_upload' => true, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index b52f382bf71fa..decc9e380db7a 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -180,6 +180,7 @@ public function configureOptions(OptionsResolver $resolver) 'attr' => array(), 'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.', 'upload_max_size_message' => $uploadMaxSizeMessage, // internal + 'allow_file_upload' => false, 'help' => null, 'help_attr' => array(), )); diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 848025dff3279..26b966c72e027 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -532,6 +532,11 @@ public function submit($submittedData, $clearMissing = true) $submittedData = null; } elseif (is_scalar($submittedData)) { $submittedData = (string) $submittedData; + } elseif ($this->config->getOption('allow_file_upload')) { + // no-op + } elseif ($this->config->getRequestHandler()->isFileUpload($submittedData)) { + $submittedData = null; + $this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, file upload given.'); } $dispatcher = $this->config->getEventDispatcher(); @@ -541,6 +546,10 @@ public function submit($submittedData, $clearMissing = true) $viewData = null; try { + if (null !== $this->transformationFailure) { + throw $this->transformationFailure; + } + // Hook to change content of the data submitted by the browser if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) { $event = new FormEvent($this, $submittedData); diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index b631ee01e2c47..da527125ef3d1 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -709,7 +709,7 @@ public function testSubmitPostOrPutRequestWithSingleChildForm($method) 'REQUEST_METHOD' => $method, )); - $form = $this->getBuilder('image') + $form = $this->getBuilder('image', null, null, array('allow_file_upload' => true)) ->setMethod($method) ->setRequestHandler(new HttpFoundationRequestHandler()) ->getForm(); @@ -1081,6 +1081,21 @@ public function testDisabledButtonIsNotSubmitted() $this->assertFalse($submit->isSubmitted()); } + public function testFileUpload() + { + $reqHandler = new HttpFoundationRequestHandler(); + $this->form->add($this->getBuilder('foo')->setRequestHandler($reqHandler)->getForm()); + $this->form->add($this->getBuilder('bar')->setRequestHandler($reqHandler)->getForm()); + + $this->form->submit(array( + 'foo' => 'Foo', + 'bar' => new UploadedFile(__FILE__, 'upload.png', 'image/png', UPLOAD_ERR_OK), + )); + + $this->assertSame('Submitted data was expected to be text or number, file upload given.', $this->form->get('bar')->getTransformationFailure()->getMessage()); + $this->assertNull($this->form->get('bar')->getData()); + } + protected function createForm() { return $this->getBuilder() diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json index 3baeb416d41e3..eb7f21af962e4 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json @@ -28,6 +28,7 @@ "parent": { "Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType": [ "action", + "allow_file_upload", "attr", "auto_initialize", "block_name", diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt index 2da8d91ed5987..500a6e0dd2b2f 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt @@ -8,15 +8,16 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice") choice_attr FormType FormType FormTypeCsrfExtension choice_label -------------------- ------------------------- ----------------------- choice_loader compound action csrf_field_name - choice_name data_class attr csrf_message - choice_translation_domain empty_data auto_initialize csrf_protection - choice_value error_bubbling block_name csrf_token_id - choices trim by_reference csrf_token_manager - expanded data - group_by disabled - multiple help - placeholder help_attr - preferred_choices inherit_data + choice_name data_class allow_file_upload csrf_message + choice_translation_domain empty_data attr csrf_protection + choice_value error_bubbling auto_initialize csrf_token_id + choices trim block_name csrf_token_manager + expanded by_reference + group_by data + multiple disabled + placeholder help + preferred_choices help_attr + inherit_data label label_attr label_format diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json index af7911f67f909..eb93423adf651 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json @@ -4,6 +4,7 @@ "options": { "own": [ "action", + "allow_file_upload", "attr", "auto_initialize", "block_name", diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt index aad03640e48a1..5b97c854c7b06 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt @@ -6,6 +6,7 @@ Symfony\Component\Form\Extension\Core\Type\FormType (Block prefix: "form") Options ------------------------- action + allow_file_upload attr auto_initialize block_name diff --git a/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php b/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php index 7cf059886df3b..e0b344e4a8379 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php @@ -17,8 +17,8 @@ use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; -use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; /** * Synchronizes the locale between the request and the translator. @@ -31,12 +31,12 @@ class TranslatorListener implements EventSubscriberInterface private $requestStack; /** - * @param TranslatorInterface $translator + * @param LocaleAwareInterface $translator */ public function __construct($translator, RequestStack $requestStack) { - if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) { - throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); + if (!$translator instanceof TranslatorInterface && !$translator instanceof LocaleAwareInterface) { + throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, LocaleAwareInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); } $this->translator = $translator; $this->requestStack = $requestStack; diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 25a0423854b3e..7ed4118ea4beb 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -250,6 +250,9 @@ private function handleException(\Exception $e, Request $request, int $type): Re } } + /** + * Returns a human-readable string for the specified variable. + */ private function varToString($var): string { if (\is_object($var)) { diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 225a047d141f3..d34beba4198bb 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -73,11 +73,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '4.2.0'; - const VERSION_ID = 40200; + const VERSION = '4.2.1'; + const VERSION_ID = 40201; const MAJOR_VERSION = 4; const MINOR_VERSION = 2; - const RELEASE_VERSION = 0; + const RELEASE_VERSION = 1; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '07/2019'; diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php index c4cb9d053b93f..b448f3bfd16f1 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php @@ -17,7 +17,7 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\EventListener\TranslatorListener; use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; class TranslatorListenerTest extends TestCase { @@ -27,7 +27,7 @@ class TranslatorListenerTest extends TestCase protected function setUp() { - $this->translator = $this->getMockBuilder(TranslatorInterface::class)->getMock(); + $this->translator = $this->getMockBuilder(LocaleAwareInterface::class)->getMock(); $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); $this->listener = new TranslatorListener($this->translator, $this->requestStack); } diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 8228377e0419b..1e5684d579901 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", + "symfony/contracts": "^1.0.2", "symfony/event-dispatcher": "~4.1", "symfony/http-foundation": "^4.1.1", "symfony/debug": "~3.4|~4.0", diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php index d8a274d4a812e..574425eb457f3 100644 --- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php +++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php @@ -638,8 +638,8 @@ public function setSymbol($attr, $value) /** * Not supported. Set a text attribute. * - * @param int $attr An attribute specifier, one of the text attribute constants - * @param int $value The attribute value + * @param int $attr An attribute specifier, one of the text attribute constants + * @param string $value The attribute value * * @return bool true on success or false on failure * diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php index c03171eb66fff..304ba7801b5e0 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php @@ -38,6 +38,7 @@ final class ConnectionOptions const ERROR_STRING = 0x32; const MATCHED_DN = 0x33; const DEBUG_LEVEL = 0x5001; + const TIMEOUT = 0x5002; const NETWORK_TIMEOUT = 0x5005; const X_SASL_MECH = 0x6100; const X_SASL_REALM = 0x6101; diff --git a/src/Symfony/Component/Ldap/CHANGELOG.md b/src/Symfony/Component/Ldap/CHANGELOG.md index b3f367d9ef445..7dc0c81b3dfda 100644 --- a/src/Symfony/Component/Ldap/CHANGELOG.md +++ b/src/Symfony/Component/Ldap/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * added `EntryManager::applyOperations` + * Added timeout option to `ConnectionOptions` 4.1.0 ----- diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherTrait.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherTrait.php index 774f503ac7f13..85c8cba8b1776 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherTrait.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherTrait.php @@ -67,7 +67,7 @@ public function match($pathinfo) throw new ResourceNotFoundException(); } - private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array + private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): array { $allow = $allowSchemes = array(); $pathinfo = rawurldecode($rawPathinfo) ?: '/'; @@ -90,8 +90,8 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a if ('/' === $pathinfo || $hasTrailingSlash === ('/' === $pathinfo[-1])) { // no-op - } elseif ($this instanceof RedirectableUrlMatcherInterface) { - return null; + } elseif ($this instanceof RedirectableUrlMatcherInterface && (!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) { + return $allow = $allowSchemes = array(); } else { continue; } @@ -130,12 +130,20 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a continue; } - if ('/' === $pathinfo || (!$hasTrailingSlash ? '/' !== $pathinfo[-1] || !preg_match($regex, substr($pathinfo, 0, -1), $n) || $m !== (int) $n['MARK'] : '/' === $pathinfo[-1])) { - // no-op - } elseif ($this instanceof RedirectableUrlMatcherInterface) { - return null; - } else { - continue; + if ('/' !== $pathinfo) { + if ('/' === $pathinfo[-1]) { + if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) { + $matches = $n; + } else { + $hasTrailingSlash = true; + } + } + if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) { + if ($this instanceof RedirectableUrlMatcherInterface && (!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) { + return $allow = $allowSchemes = array(); + } + continue; + } } foreach ($vars as $i => $v) { @@ -168,6 +176,6 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a throw new NoConfigurationException(); } - return null; + return array(); } } diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php index 32bf4ed82d187..0d5d14da5eae3 100644 --- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php @@ -130,21 +130,26 @@ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterfac */ protected function matchCollection($pathinfo, RouteCollection $routes) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } $supportsTrailingSlash = '/' !== $pathinfo && '' !== $pathinfo && $this instanceof RedirectableUrlMatcherInterface; foreach ($routes as $name => $route) { $compiledRoute = $route->compile(); $staticPrefix = $compiledRoute->getStaticPrefix(); + $requiredMethods = $route->getMethods(); // check the static prefix of the URL first. Only use the more expensive preg_match when it matches if ('' === $staticPrefix || 0 === strpos($pathinfo, $staticPrefix)) { // no-op - } elseif (!$supportsTrailingSlash) { + } elseif (!$supportsTrailingSlash || ($requiredMethods && !\in_array('GET', $requiredMethods)) || 'GET' !== $method) { continue; } elseif ('/' === $staticPrefix[-1] && substr($staticPrefix, 0, -1) === $pathinfo) { - return; + return $this->allow = $this->allowSchemes = array(); } elseif ('/' === $pathinfo[-1] && substr($pathinfo, 0, -1) === $staticPrefix) { - return; + return $this->allow = $this->allowSchemes = array(); } else { continue; } @@ -161,11 +166,18 @@ protected function matchCollection($pathinfo, RouteCollection $routes) } if ($supportsTrailingSlash) { - if (!$hasTrailingSlash && '/' === $pathinfo[-1] && preg_match($regex, substr($pathinfo, 0, -1))) { - return; + if ('/' === $pathinfo[-1]) { + if (preg_match($regex, substr($pathinfo, 0, -1), $m)) { + $matches = $m; + } else { + $hasTrailingSlash = true; + } } - if ($hasTrailingSlash && '/' !== $pathinfo[-1]) { - return; + if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) { + if ((!$requiredMethods || \in_array('GET', $requiredMethods)) && 'GET' === $method) { + return $this->allow = $this->allowSchemes = array(); + } + continue; } } @@ -181,12 +193,7 @@ protected function matchCollection($pathinfo, RouteCollection $routes) } $hasRequiredScheme = !$route->getSchemes() || $route->hasScheme($this->context->getScheme()); - if ($requiredMethods = $route->getMethods()) { - // HEAD and GET are equivalent as per RFC - if ('HEAD' === $method = $this->context->getMethod()) { - $method = 'GET'; - } - + if ($requiredMethods) { if (!\in_array($method, $requiredMethods)) { if ($hasRequiredScheme) { $this->allow = array_merge($this->allow, $requiredMethods); @@ -204,6 +211,8 @@ protected function matchCollection($pathinfo, RouteCollection $routes) return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : array())); } + + return array(); } /** diff --git a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php index eb1703353b21e..f1d6b5325d0a5 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -170,6 +170,23 @@ public function testMissingTrailingSlashAndScheme() $matcher->match('/foo'); } + public function testSlashAndVerbPrecedenceWithRedirection() + { + $coll = new RouteCollection(); + $coll->add('a', new Route('/api/customers/{customerId}/contactpersons', array(), array(), array(), '', array(), array('post'))); + $coll->add('b', new Route('/api/customers/{customerId}/contactpersons/', array(), array(), array(), '', array(), array('get'))); + + $matcher = $this->getUrlMatcher($coll); + $expected = array( + '_route' => 'b', + 'customerId' => '123', + ); + $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons/')); + + $matcher->expects($this->once())->method('redirect')->with('/api/customers/123/contactpersons/')->willReturn(array()); + $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons')); + } + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) { return $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($routes, $context ?: new RequestContext())); diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 5f24e5f794ce3..bcaf3dee7218a 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -692,6 +692,50 @@ public function testSlashVariant() $this->assertEquals('a', $matcher->match('/foo/')['_route']); } + public function testSlashVariant2() + { + $coll = new RouteCollection(); + $coll->add('a', new Route('/foo/{bar}/', array(), array('bar' => '.*'))); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals(array('_route' => 'a', 'bar' => 'bar'), $matcher->match('/foo/bar/')); + } + + public function testSlashWithVerb() + { + $coll = new RouteCollection(); + $coll->add('a', new Route('/{foo}', array(), array(), array(), '', array(), array('put', 'delete'))); + $coll->add('b', new Route('/bar/')); + + $matcher = $this->getUrlMatcher($coll); + $this->assertSame(array('_route' => 'b'), $matcher->match('/bar/')); + } + + public function testSlashAndVerbPrecedence() + { + $coll = new RouteCollection(); + $coll->add('a', new Route('/api/customers/{customerId}/contactpersons/', array(), array(), array(), '', array(), array('post'))); + $coll->add('b', new Route('/api/customers/{customerId}/contactpersons', array(), array(), array(), '', array(), array('get'))); + + $matcher = $this->getUrlMatcher($coll); + $expected = array( + '_route' => 'b', + 'customerId' => '123', + ); + $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons')); + + $coll = new RouteCollection(); + $coll->add('a', new Route('/api/customers/{customerId}/contactpersons/', array(), array(), array(), '', array(), array('get'))); + $coll->add('b', new Route('/api/customers/{customerId}/contactpersons', array(), array(), array(), '', array(), array('post'))); + + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'POST')); + $expected = array( + '_route' => 'b', + 'customerId' => '123', + ); + $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons')); + } + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) { return new UrlMatcher($routes, $context ?: new RequestContext()); diff --git a/src/Symfony/Component/Security/Http/HttpUtils.php b/src/Symfony/Component/Security/Http/HttpUtils.php index 6dbd14e986653..850e371c95429 100644 --- a/src/Symfony/Component/Security/Http/HttpUtils.php +++ b/src/Symfony/Component/Security/Http/HttpUtils.php @@ -62,10 +62,10 @@ public function __construct(UrlGeneratorInterface $urlGenerator = null, $urlMatc */ public function createRedirectResponse(Request $request, $path, $status = 302) { - if (null !== $this->secureDomainRegexp && 'https' === $this->urlMatcher->getContext()->getScheme() && preg_match('#^https?://[^/]++#i', $path, $host) && !preg_match(sprintf($this->secureDomainRegexp, preg_quote($request->getHttpHost())), $host[0])) { + if (null !== $this->secureDomainRegexp && 'https' === $this->urlMatcher->getContext()->getScheme() && preg_match('#^https?:[/\\\\]{2,}+[^/]++#i', $path, $host) && !preg_match(sprintf($this->secureDomainRegexp, preg_quote($request->getHttpHost())), $host[0])) { $path = '/'; } - if (null !== $this->domainRegexp && preg_match('#^https?://[^/]++#i', $path, $host) && !preg_match(sprintf($this->domainRegexp, preg_quote($request->getHttpHost())), $host[0])) { + if (null !== $this->domainRegexp && preg_match('#^https?:[/\\\\]{2,}+[^/]++#i', $path, $host) && !preg_match(sprintf($this->domainRegexp, preg_quote($request->getHttpHost())), $host[0])) { $path = '/'; } diff --git a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php index 28b242995d4bf..a0d6f79714414 100644 --- a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php +++ b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php @@ -54,14 +54,28 @@ public function testCreateRedirectResponseWithRequestsDomain() $this->assertTrue($response->isRedirect('http://localhost/blog')); } - public function testCreateRedirectResponseWithBadRequestsDomain() + /** + * @dataProvider badRequestDomainUrls + */ + public function testCreateRedirectResponseWithBadRequestsDomain($url) { $utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i'); - $response = $utils->createRedirectResponse($this->getRequest(), 'http://pirate.net/foo'); + $response = $utils->createRedirectResponse($this->getRequest(), $url); $this->assertTrue($response->isRedirect('http://localhost/')); } + public function badRequestDomainUrls() + { + return array( + array('http://pirate.net/foo'), + array('http:\\\\pirate.net/foo'), + array('http:/\\pirate.net/foo'), + array('http:\\/pirate.net/foo'), + array('http://////pirate.net/foo'), + ); + } + public function testCreateRedirectResponseWithProtocolRelativeTarget() { $utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i'); diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php index 26c48e5fd49fd..02ae517f25990 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php @@ -68,7 +68,8 @@ public function normalize($object, $format = null, array $context = array()) $timezone = $this->getTimezone($context); if (null !== $timezone) { - $object = (new \DateTimeImmutable('@'.$object->getTimestamp()))->setTimezone($timezone); + $object = clone $object; + $object = $object->setTimezone($timezone); } return $object->format($dateTimeFormat); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php index 87edb8df4c993..91b6909f17baa 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php @@ -103,6 +103,82 @@ public function normalizeUsingTimeZonePassedInContextProvider() yield array('2016-12-01T09:00:00+09:00', new \DateTimeImmutable('2016/12/01', new \DateTimeZone('UTC')), new \DateTimeZone('Japan')); } + /** + * @dataProvider normalizeUsingTimeZonePassedInContextAndExpectedFormatWithMicrosecondsProvider + */ + public function testNormalizeUsingTimeZonePassedInContextAndFormattedWithMicroseconds($expected, $expectedFormat, $input, $timezone) + { + $this->assertSame( + $expected, + $this->normalizer->normalize( + $input, + null, + array( + DateTimeNormalizer::TIMEZONE_KEY => $timezone, + DateTimeNormalizer::FORMAT_KEY => $expectedFormat, + ) + ) + ); + } + + public function normalizeUsingTimeZonePassedInContextAndExpectedFormatWithMicrosecondsProvider() + { + yield array( + '2018-12-01T18:03:06.067634', + 'Y-m-d\TH:i:s.u', + \DateTime::createFromFormat( + 'Y-m-d\TH:i:s.u', + '2018-12-01T18:03:06.067634', + new \DateTimeZone('UTC') + ), + null, + ); + + yield array( + '2018-12-01T18:03:06.067634', + 'Y-m-d\TH:i:s.u', + \DateTime::createFromFormat( + 'Y-m-d\TH:i:s.u', + '2018-12-01T18:03:06.067634', + new \DateTimeZone('UTC') + ), + new \DateTimeZone('UTC'), + ); + + yield array( + '2018-12-01T19:03:06.067634+01:00', + 'Y-m-d\TH:i:s.uP', + \DateTimeImmutable::createFromFormat( + 'Y-m-d\TH:i:s.u', + '2018-12-01T18:03:06.067634', + new \DateTimeZone('UTC') + ), + new \DateTimeZone('Europe/Rome'), + ); + + yield array( + '2018-12-01T20:03:06.067634+02:00', + 'Y-m-d\TH:i:s.uP', + \DateTime::createFromFormat( + 'Y-m-d\TH:i:s.u', + '2018-12-01T18:03:06.067634', + new \DateTimeZone('UTC') + ), + new \DateTimeZone('Europe/Kiev'), + ); + + yield array( + '2018-12-01T21:03:06.067634', + 'Y-m-d\TH:i:s.u', + \DateTime::createFromFormat( + 'Y-m-d\TH:i:s.u', + '2018-12-01T18:03:06.067634', + new \DateTimeZone('UTC') + ), + new \DateTimeZone('Europe/Moscow'), + ); + } + /** * @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException * @expectedExceptionMessage The object must implement the "\DateTimeInterface". diff --git a/src/Symfony/Component/Translation/DataCollectorTranslator.php b/src/Symfony/Component/Translation/DataCollectorTranslator.php index 5e547163a3a03..ca6bedc71bfa6 100644 --- a/src/Symfony/Component/Translation/DataCollectorTranslator.php +++ b/src/Symfony/Component/Translation/DataCollectorTranslator.php @@ -13,6 +13,7 @@ use Symfony\Component\Translation\Exception\InvalidArgumentException; use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -39,8 +40,8 @@ public function __construct($translator) if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) { throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); } - if (!$translator instanceof TranslatorBagInterface) { - throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', \get_class($translator))); + if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { + throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', \get_class($translator))); } $this->translator = $translator; diff --git a/src/Symfony/Component/Translation/Formatter/MessageFormatter.php b/src/Symfony/Component/Translation/Formatter/MessageFormatter.php index 11f766c8cd216..e36f242c89e42 100644 --- a/src/Symfony/Component/Translation/Formatter/MessageFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/MessageFormatter.php @@ -66,7 +66,7 @@ public function formatIntl(string $message, string $locale, array $parameters = */ public function choiceFormat($message, $number, $locale, array $parameters = array()) { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the format() one instead with a %count% parameter.', __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the format() one instead with a %%count%% parameter.', __METHOD__), E_USER_DEPRECATED); $parameters = array('%count%' => $number) + $parameters; diff --git a/src/Symfony/Component/Translation/LoggingTranslator.php b/src/Symfony/Component/Translation/LoggingTranslator.php index 2746fcfe3007c..4fe756d7a7fbc 100644 --- a/src/Symfony/Component/Translation/LoggingTranslator.php +++ b/src/Symfony/Component/Translation/LoggingTranslator.php @@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Translation\Exception\InvalidArgumentException; use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -37,8 +38,8 @@ public function __construct($translator, LoggerInterface $logger) if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) { throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); } - if (!$translator instanceof TranslatorBagInterface) { - throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', \get_class($translator))); + if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { + throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', \get_class($translator))); } $this->translator = $translator; diff --git a/src/Symfony/Component/Translation/TranslatorInterface.php b/src/Symfony/Component/Translation/TranslatorInterface.php index 02cb5027b862b..3ecef054b1cee 100644 --- a/src/Symfony/Component/Translation/TranslatorInterface.php +++ b/src/Symfony/Component/Translation/TranslatorInterface.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Translation; use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Contracts\Translation\LocaleAwareInterface; /** * TranslatorInterface. @@ -20,7 +21,7 @@ * * @deprecated since Symfony 4.2, use Symfony\Contracts\Translation\TranslatorInterface instead */ -interface TranslatorInterface +interface TranslatorInterface extends LocaleAwareInterface { /** * Translates the given message. diff --git a/src/Symfony/Component/Translation/composer.json b/src/Symfony/Component/Translation/composer.json index 29ba62ab15eab..7d5df73025304 100644 --- a/src/Symfony/Component/Translation/composer.json +++ b/src/Symfony/Component/Translation/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", + "symfony/contracts": "^1.0.2", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { diff --git a/src/Symfony/Component/Validator/ConstraintViolation.php b/src/Symfony/Component/Validator/ConstraintViolation.php index 57ece5ba37259..8651913c3e7f3 100644 --- a/src/Symfony/Component/Validator/ConstraintViolation.php +++ b/src/Symfony/Component/Validator/ConstraintViolation.php @@ -79,13 +79,13 @@ public function __toString() } $propertyPath = (string) $this->propertyPath; - $code = $this->code; + $code = (string) $this->code; if ('' !== $propertyPath && '[' !== $propertyPath[0] && '' !== $class) { $class .= '.'; } - if (!empty($code)) { + if ('' !== $code) { $code = ' (code '.$code.')'; } diff --git a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php index cef4782e0f82d..edaa7fa50d6b0 100644 --- a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php +++ b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php @@ -53,4 +53,59 @@ public function testToStringHandlesArrayRoots() $this->assertSame($expected, (string) $violation); } + + public function testToStringHandlesCodes() + { + $violation = new ConstraintViolation( + '42 cannot be used here', + 'this is the message template', + array(), + array('some_value' => 42), + 'some_value', + null, + null, + 0 + ); + + $expected = <<<'EOF' +Array.some_value: + 42 cannot be used here (code 0) +EOF; + + $this->assertSame($expected, (string) $violation); + } + + public function testToStringOmitsEmptyCodes() + { + $expected = <<<'EOF' +Array.some_value: + 42 cannot be used here +EOF; + + $violation = new ConstraintViolation( + '42 cannot be used here', + 'this is the message template', + array(), + array('some_value' => 42), + 'some_value', + null, + null, + null + ); + + $this->assertSame($expected, (string) $violation); + + $violation = new ConstraintViolation( + '42 cannot be used here', + 'this is the message template', + array(), + array('some_value' => 42), + 'some_value', + null, + null, + '' + ); + + $this->assertSame($expected, (string) $violation); + } } diff --git a/src/Symfony/Component/Validator/Tests/DependencyInjection/AddValidatorInitializersPassTest.php b/src/Symfony/Component/Validator/Tests/DependencyInjection/AddValidatorInitializersPassTest.php index 26d5e6646d541..ff7e28a29e576 100644 --- a/src/Symfony/Component/Validator/Tests/DependencyInjection/AddValidatorInitializersPassTest.php +++ b/src/Symfony/Component/Validator/Tests/DependencyInjection/AddValidatorInitializersPassTest.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass; use Symfony\Component\Validator\Util\LegacyTranslatorProxy; +use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorTrait; @@ -72,7 +73,7 @@ public function testLegacyTranslatorProxy() } } -class TestTranslator implements TranslatorInterface +class TestTranslator implements TranslatorInterface, LocaleAwareInterface { use TranslatorTrait; } diff --git a/src/Symfony/Component/Validator/Util/LegacyTranslatorProxy.php b/src/Symfony/Component/Validator/Util/LegacyTranslatorProxy.php index 12f487bf2f896..f87c1fbbb3eae 100644 --- a/src/Symfony/Component/Validator/Util/LegacyTranslatorProxy.php +++ b/src/Symfony/Component/Validator/Util/LegacyTranslatorProxy.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Validator\Util; use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -23,6 +24,9 @@ class LegacyTranslatorProxy implements LegacyTranslatorInterface, TranslatorInte public function __construct(TranslatorInterface $translator) { + if (!$translator instanceof LocaleAwareInterface) { + throw new \InvalidArgumentException(sprintf('The translator passed to "%s()" must implement "%s".', __METHOD__, LocaleAwareInterface::class)); + } $this->translator = $translator; } diff --git a/src/Symfony/Component/Validator/ValidatorBuilder.php b/src/Symfony/Component/Validator/ValidatorBuilder.php index 7a4b52164aec0..8e4de01267e04 100644 --- a/src/Symfony/Component/Validator/ValidatorBuilder.php +++ b/src/Symfony/Component/Validator/ValidatorBuilder.php @@ -30,6 +30,7 @@ use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; use Symfony\Component\Validator\Util\LegacyTranslatorProxy; use Symfony\Component\Validator\Validator\RecursiveValidator; +use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorTrait; @@ -332,7 +333,7 @@ public function getValidator() $translator = $this->translator; if (null === $translator) { - $translator = new class() implements TranslatorInterface { + $translator = new class() implements TranslatorInterface, LocaleAwareInterface { use TranslatorTrait; }; // Force the locale to be 'en' when no translator is provided rather than relying on the Intl default locale diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index 442d7c67b4411..88a29241f57c7 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", + "symfony/contracts": "^1.0.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, diff --git a/src/Symfony/Component/VarExporter/Internal/Hydrator.php b/src/Symfony/Component/VarExporter/Internal/Hydrator.php index 0de27743d98dd..a1a60112d9a92 100644 --- a/src/Symfony/Component/VarExporter/Internal/Hydrator.php +++ b/src/Symfony/Component/VarExporter/Internal/Hydrator.php @@ -11,6 +11,8 @@ namespace Symfony\Component\VarExporter\Internal; +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; + /** * @author Nicolas Grekas * @@ -59,7 +61,10 @@ public static function getHydrator($class) }; } - $classReflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); + if (!\class_exists($class) && !\interface_exists($class, false) && !\trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); if (!$classReflector->isInternal()) { return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class); diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/abstract-parent.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/abstract-parent.php new file mode 100644 index 0000000000000..df6a067087609 --- /dev/null +++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/abstract-parent.php @@ -0,0 +1,20 @@ + [ + 'foo' => [ + 123, + ], + 'bar' => [ + 234, + ], + ], + ], + $o[0], + [] +); diff --git a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php index f5096176868ad..ea0b6a45851d3 100644 --- a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php +++ b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php @@ -192,6 +192,8 @@ public function provideExport() $value->bis = new \ReflectionClass($value); yield array('wakeup-refl', $value); + + yield array('abstract-parent', new ConcreteClass()); } } @@ -320,3 +322,23 @@ public function __clone() throw new \BadMethodCallException('Should not be called.'); } } + +abstract class AbstractClass +{ + protected $foo; + private $bar; + + protected function setBar($bar) + { + $this->bar = $bar; + } +} + +class ConcreteClass extends AbstractClass +{ + public function __construct() + { + $this->foo = 123; + $this->setBar(234); + } +} diff --git a/src/Symfony/Contracts/Translation/LocaleAwareInterface.php b/src/Symfony/Contracts/Translation/LocaleAwareInterface.php new file mode 100644 index 0000000000000..dbd8894fe7899 --- /dev/null +++ b/src/Symfony/Contracts/Translation/LocaleAwareInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +interface LocaleAwareInterface +{ + /** + * Sets the current locale. + * + * @param string $locale The locale + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale($locale); + + /** + * Returns the current locale. + * + * @return string The locale + */ + public function getLocale(); +} diff --git a/src/Symfony/Contracts/Translation/TranslatorInterface.php b/src/Symfony/Contracts/Translation/TranslatorInterface.php index 2130c1b2cf0a6..2bdc415cbdcf2 100644 --- a/src/Symfony/Contracts/Translation/TranslatorInterface.php +++ b/src/Symfony/Contracts/Translation/TranslatorInterface.php @@ -62,20 +62,4 @@ interface TranslatorInterface * @throws \InvalidArgumentException If the locale contains invalid characters */ public function trans($id, array $parameters = array(), $domain = null, $locale = null); - - /** - * Sets the current locale. - * - * @param string $locale The locale - * - * @throws \InvalidArgumentException If the locale contains invalid characters - */ - public function setLocale($locale); - - /** - * Returns the current locale. - * - * @return string The locale - */ - public function getLocale(); } diff --git a/src/Symfony/Contracts/Translation/TranslatorTrait.php b/src/Symfony/Contracts/Translation/TranslatorTrait.php index e19e37cfe5208..4e53fbd42c4c8 100644 --- a/src/Symfony/Contracts/Translation/TranslatorTrait.php +++ b/src/Symfony/Contracts/Translation/TranslatorTrait.php @@ -14,7 +14,7 @@ use Symfony\Component\Translation\Exception\InvalidArgumentException; /** - * A trait to help implement TranslatorInterface. + * A trait to help implement TranslatorInterface and LocaleAwareInterface. * * @author Fabien Potencier */