diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..db1c2a8a6ff44 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,14 @@ +# LDAP +/src/Symfony/Component/Ldap/* @csarrazi +# Lock +/src/Symfony/Component/Lock/* @jderusse +# Messenger +/src/Symfony/Bridge/Doctrine/Messenger/* @sroze +/src/Symfony/Component/Messenger/* @sroze +# Workflow +/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @lyrixx +/src/Symfony/Bridge/Twig/Tests/Extension/WorkflowExtensionTest.php @lyrixx +/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @lyrixx +/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php @lyrixx +/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php @lyrixx +/src/Symfony/Component/Workflow/* @lyrixx diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000..16e2603b76a1d --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,8 @@ +# Code of Conduct + +This project follows a [Code of Conduct][code_of_conduct] in order to ensure an open and welcoming environment. +Please read the full text for understanding the accepted and unaccepted behavior. +Please read also the [reporting guidelines][guidelines], in case you encountered or witnessed any misbehavior. + +[code_of_conduct]: https://symfony.com/doc/current/contributing/code_of_conduct/index.html +[guidelines]: https://symfony.com/doc/current/contributing/code_of_conduct/reporting_guidelines.html diff --git a/CHANGELOG-4.0.md b/CHANGELOG-4.0.md index 9abb4a61a6634..906a472b9fe36 100644 --- a/CHANGELOG-4.0.md +++ b/CHANGELOG-4.0.md @@ -7,6 +7,34 @@ in 4.0 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.0.0...v4.0.1 +* 4.0.10 (2018-05-21) + + * bug #27264 [Validator] Use strict type in URL validator (mimol91) + * bug #27267 [DependencyInjection] resolve array env vars (jamesthomasonjr) + * bug #26781 [Form] Fix precision of MoneyToLocalizedStringTransformer's divisions on transform() (syastrebov) + * bug #27286 [Translation] Add Occitan plural rule (kylekatarnls) + * bug #27271 [DI] Allow defining bindings on ChildDefinition (nicolas-grekas) + * bug #27246 Disallow invalid characters in session.name (ostrolucky) + * bug #27287 [PropertyInfo] fix resolving parent|self type hints (nicolas-grekas) + * bug #27281 [HttpKernel] Fix dealing with self/parent in ArgumentMetadataFactory (fabpot) + * bug #24805 [Security] Fix logout (MatTheCat) + * bug #27265 [DI] Shared services should not be inlined in non-shared ones (nicolas-grekas) + * bug #27141 [Process] Suppress warnings when open_basedir is non-empty (cbj4074) + * bug #27250 [Session] limiting :key for GET_LOCK to 64 chars (oleg-andreyev) + * bug #27237 [Debug] Fix populating error_get_last() for handled silent errors (nicolas-grekas) + * bug #27232 [Cache][Lock] Fix usages of error_get_last() (nicolas-grekas) + * bug #27236 [Filesystem] Fix usages of error_get_last() (nicolas-grekas) + * bug #27191 [DI] Display previous error messages when throwing unused bindings (nicolas-grekas) + * bug #27231 [FrameworkBundle] Fix cache:clear on vagrant (nicolas-grekas) + * bug #27222 [WebProfilerBundle][Cache] Fix misses calculation when calling getItems (fsevestre) + * bug #27227 [HttpKernel] Handle NoConfigurationException "onKernelException()" (nicolas-grekas) + * bug #27152 [HttpFoundation] use brace-style regex delimiters (xabbuh) + * bug #27158 [Cache] fix logic for fetching tag versions on TagAwareAdapter (dmaicher) + * bug #27143 [Console] By default hide the short exception trace line from exception messages in Symfony's commands (yceruto) + * bug #27133 [Doctrine Bridge] fix priority for doctrine event listeners (dmaicher) + * bug #27135 [FrameworkBundle] Use the correct service id for CachePoolPruneCommand in its compiler pass (DemonTPx) + * feature #24896 Add CODE_OF_CONDUCT.md (egircys) + * 4.0.9 (2018-04-30) * bug #27074 [Debug][WebProfilerBundle] Fix setting file link format (lyrixx, nicolas-grekas) diff --git a/composer.json b/composer.json index 98e83021b7c07..b389ddf3fb4ed 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "psr/link": "^1.0", "psr/log": "~1.0", "psr/simple-cache": "^1.0", + "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php72": "~1.5" diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index 9611e6b6f5998..c4b8202e744c5 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -11,22 +11,22 @@ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; /** * Registers event listeners and subscribers to the available doctrine connections. * * @author Jeremy Mikola * @author Alexander + * @author David Maicher */ class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface { private $connections; - private $container; private $eventManagers; private $managerTemplate; private $tagPrefix; @@ -53,97 +53,97 @@ public function process(ContainerBuilder $container) return; } - $taggedSubscribers = $container->findTaggedServiceIds($this->tagPrefix.'.event_subscriber', true); - $taggedListeners = $container->findTaggedServiceIds($this->tagPrefix.'.event_listener', true); - - if (empty($taggedSubscribers) && empty($taggedListeners)) { - return; - } - - $this->container = $container; $this->connections = $container->getParameter($this->connections); - $sortFunc = function ($a, $b) { - $a = isset($a['priority']) ? $a['priority'] : 0; - $b = isset($b['priority']) ? $b['priority'] : 0; - - return $a > $b ? -1 : 1; - }; - - if (!empty($taggedSubscribers)) { - $subscribersPerCon = $this->groupByConnection($taggedSubscribers); - foreach ($subscribersPerCon as $con => $subscribers) { - $em = $this->getEventManager($con); + $this->addTaggedSubscribers($container); + $this->addTaggedListeners($container); + } - uasort($subscribers, $sortFunc); - foreach ($subscribers as $id => $instance) { - $em->addMethodCall('addEventSubscriber', array(new Reference($id))); + private function addTaggedSubscribers(ContainerBuilder $container) + { + $subscriberTag = $this->tagPrefix.'.event_subscriber'; + $taggedSubscribers = $this->findAndSortTags($subscriberTag, $container); + + foreach ($taggedSubscribers as $taggedSubscriber) { + list($id, $tag) = $taggedSubscriber; + $connections = isset($tag['connection']) ? array($tag['connection']) : array_keys($this->connections); + foreach ($connections as $con) { + if (!isset($this->connections[$con])) { + throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $taggedSubscriber, implode(', ', array_keys($this->connections)))); } - } - } - if (!empty($taggedListeners)) { - $listenersPerCon = $this->groupByConnection($taggedListeners, true); - foreach ($listenersPerCon as $con => $listeners) { - $em = $this->getEventManager($con); - - uasort($listeners, $sortFunc); - foreach ($listeners as $id => $instance) { - $em->addMethodCall('addEventListener', array( - array_unique($instance['event']), - isset($instance['lazy']) && $instance['lazy'] ? $id : new Reference($id), - )); - } + $this->getEventManagerDef($container, $con)->addMethodCall('addEventSubscriber', array(new Reference($id))); } } } - private function groupByConnection(array $services, $isListener = false) + private function addTaggedListeners(ContainerBuilder $container) { - $grouped = array(); - foreach ($allCons = array_keys($this->connections) as $con) { - $grouped[$con] = array(); - } + $listenerTag = $this->tagPrefix.'.event_listener'; + $taggedListeners = $this->findAndSortTags($listenerTag, $container); + + foreach ($taggedListeners as $taggedListener) { + list($id, $tag) = $taggedListener; + $taggedListenerDef = $container->getDefinition($id); + if (!isset($tag['event'])) { + throw new InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id)); + } - foreach ($services as $id => $instances) { - foreach ($instances as $instance) { - if ($isListener) { - if (!isset($instance['event'])) { - throw new InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id)); - } - $instance['event'] = array($instance['event']); - - if (!empty($instance['lazy'])) { - $this->container->getDefinition($id)->setPublic(true); - } + $connections = isset($tag['connection']) ? array($tag['connection']) : array_keys($this->connections); + foreach ($connections as $con) { + if (!isset($this->connections[$con])) { + throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections)))); } - $cons = isset($instance['connection']) ? array($instance['connection']) : $allCons; - foreach ($cons as $con) { - if (!isset($grouped[$con])) { - throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections)))); - } - - if ($isListener && isset($grouped[$con][$id])) { - $grouped[$con][$id]['event'] = array_merge($grouped[$con][$id]['event'], $instance['event']); - } else { - $grouped[$con][$id] = $instance; - } + if ($lazy = !empty($tag['lazy'])) { + $taggedListenerDef->setPublic(true); } + + // we add one call per event per service so we have the correct order + $this->getEventManagerDef($container, $con)->addMethodCall('addEventListener', array(array($tag['event']), $lazy ? $id : new Reference($id))); } } + } + + private function getEventManagerDef(ContainerBuilder $container, $name) + { + if (!isset($this->eventManagers[$name])) { + $this->eventManagers[$name] = $container->getDefinition(sprintf($this->managerTemplate, $name)); + } - return $grouped; + return $this->eventManagers[$name]; } - private function getEventManager($name) + /** + * Finds and orders all service tags with the given name by their priority. + * + * The order of additions must be respected for services having the same priority, + * and knowing that the \SplPriorityQueue class does not respect the FIFO method, + * we should not use this class. + * + * @see https://bugs.php.net/bug.php?id=53710 + * @see https://bugs.php.net/bug.php?id=60926 + * + * @param string $tagName + * @param ContainerBuilder $container + * + * @return array + */ + private function findAndSortTags($tagName, ContainerBuilder $container) { - if (null === $this->eventManagers) { - $this->eventManagers = array(); - foreach ($this->connections as $n => $id) { - $this->eventManagers[$n] = $this->container->getDefinition(sprintf($this->managerTemplate, $n)); + $sortedTags = array(); + + foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $tags) { + foreach ($tags as $attributes) { + $priority = isset($attributes['priority']) ? $attributes['priority'] : 0; + $sortedTags[$priority][] = array($serviceId, $attributes); } } - return $this->eventManagers[$name]; + if ($sortedTags) { + krsort($sortedTags); + $sortedTags = call_user_func_array('array_merge', $sortedTags); + } + + return $sortedTags; } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php index 25776f86695af..7e99a7d9356c2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; class RegisterEventListenersAndSubscribersPassTest extends TestCase { @@ -56,12 +57,18 @@ public function testProcessEventListenersWithPriorities() $container ->register('a', 'stdClass') + ->setPublic(false) + ->addTag('doctrine.event_listener', array( + 'event' => 'bar', + )) ->addTag('doctrine.event_listener', array( 'event' => 'foo', 'priority' => -5, )) ->addTag('doctrine.event_listener', array( - 'event' => 'bar', + 'event' => 'foo_bar', + 'priority' => 3, + 'lazy' => true, )) ; $container @@ -70,12 +77,34 @@ public function testProcessEventListenersWithPriorities() 'event' => 'foo', )) ; + $container + ->register('c', 'stdClass') + ->addTag('doctrine.event_listener', array( + 'event' => 'foo_bar', + 'priority' => 4, + )) + ; $this->process($container); - $this->assertEquals(array('b', 'a'), $this->getServiceOrder($container, 'addEventListener')); - - $calls = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls(); - $this->assertEquals(array('foo', 'bar'), $calls[1][1][0]); + $methodCalls = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls(); + + $this->assertEquals( + array( + array('addEventListener', array(array('foo_bar'), new Reference('c'))), + array('addEventListener', array(array('foo_bar'), new Reference('a'))), + array('addEventListener', array(array('bar'), new Reference('a'))), + array('addEventListener', array(array('foo'), new Reference('b'))), + array('addEventListener', array(array('foo'), new Reference('a'))), + ), + $methodCalls + ); + + // not lazy so must be reference + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $methodCalls[0][1][1]); + + // lazy so id instead of reference and must mark service public + $this->assertSame('a', $methodCalls[1][1][1]); + $this->assertTrue($container->getDefinition('a')->isPublic()); } public function testProcessEventListenersWithMultipleConnections() @@ -88,15 +117,86 @@ public function testProcessEventListenersWithMultipleConnections() 'event' => 'onFlush', )) ; + + $container + ->register('b', 'stdClass') + ->addTag('doctrine.event_listener', array( + 'event' => 'onFlush', + 'connection' => 'default', + )) + ; + + $container + ->register('c', 'stdClass') + ->addTag('doctrine.event_listener', array( + 'event' => 'onFlush', + 'connection' => 'second', + )) + ; + $this->process($container); - $callsDefault = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls(); + $this->assertEquals( + array( + array('addEventListener', array(array('onFlush'), new Reference('a'))), + array('addEventListener', array(array('onFlush'), new Reference('b'))), + ), + $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() + ); + + $this->assertEquals( + array( + array('addEventListener', array(array('onFlush'), new Reference('a'))), + array('addEventListener', array(array('onFlush'), new Reference('c'))), + ), + $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls() + ); + } - $this->assertEquals('addEventListener', $callsDefault[0][0]); - $this->assertEquals(array('onFlush'), $callsDefault[0][1][0]); + public function testProcessEventSubscribersWithMultipleConnections() + { + $container = $this->createBuilder(true); - $callsSecond = $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls(); - $this->assertEquals($callsDefault, $callsSecond); + $container + ->register('a', 'stdClass') + ->addTag('doctrine.event_subscriber', array( + 'event' => 'onFlush', + )) + ; + + $container + ->register('b', 'stdClass') + ->addTag('doctrine.event_subscriber', array( + 'event' => 'onFlush', + 'connection' => 'default', + )) + ; + + $container + ->register('c', 'stdClass') + ->addTag('doctrine.event_subscriber', array( + 'event' => 'onFlush', + 'connection' => 'second', + )) + ; + + $this->process($container); + + $this->assertEquals( + array( + array('addEventSubscriber', array(new Reference('a'))), + array('addEventSubscriber', array(new Reference('b'))), + ), + $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() + ); + + $this->assertEquals( + array( + array('addEventSubscriber', array(new Reference('a'))), + array('addEventSubscriber', array(new Reference('c'))), + ), + $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls() + ); } public function testProcessEventSubscribersWithPriorities() @@ -133,11 +233,17 @@ public function testProcessEventSubscribersWithPriorities() ; $this->process($container); - $serviceOrder = $this->getServiceOrder($container, 'addEventSubscriber'); - $unordered = array_splice($serviceOrder, 0, 3); - sort($unordered); - $this->assertEquals(array('c', 'd', 'e'), $unordered); - $this->assertEquals(array('b', 'a'), $serviceOrder); + + $this->assertEquals( + array( + array('addEventSubscriber', array(new Reference('c'))), + array('addEventSubscriber', array(new Reference('d'))), + array('addEventSubscriber', array(new Reference('e'))), + array('addEventSubscriber', array(new Reference('b'))), + array('addEventSubscriber', array(new Reference('a'))), + ), + $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() + ); } public function testProcessNoTaggedServices() @@ -157,25 +263,6 @@ private function process(ContainerBuilder $container) $pass->process($container); } - private function getServiceOrder(ContainerBuilder $container, $method) - { - $order = array(); - foreach ($container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() as list($name, $arguments)) { - if ($method !== $name) { - continue; - } - - if ('addEventListener' === $name) { - $order[] = (string) $arguments[1]; - continue; - } - - $order[] = (string) $arguments[0]; - } - - return $order; - } - private function createBuilder($multipleConnections = false) { $container = new ContainerBuilder(); diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 952e6aa639cbc..9ad37c23e3615 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -18,6 +18,7 @@ "require": { "php": "^7.1.3", "doctrine/common": "~2.4", + "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { diff --git a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php index 413b476f2938d..596fcdd84d2c5 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php +++ b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php @@ -42,7 +42,7 @@ public function isHandlerActivated(array $record) $isActivated && isset($record['context']['exception']) && $record['context']['exception'] instanceof HttpException - && $record['context']['exception']->getStatusCode() == 404 + && 404 == $record['context']['exception']->getStatusCode() && ($request = $this->requestStack->getMasterRequest()) ) { return !preg_match($this->blacklist, $request->getPathInfo()); diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php index 1c84ff32c0b74..ab1cda6702be5 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerTrait.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\PhpUnit\Legacy; -use PHPUnit\Framework\Test; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\Warning; diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_eval_d_deprecation.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_eval_d_deprecation.phpt index 8fa436e20178b..8390d16332fa1 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_eval_d_deprecation.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_eval_d_deprecation.phpt @@ -15,7 +15,7 @@ while (!file_exists($vendor.'/vendor')) { define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php'); require PHPUNIT_COMPOSER_INSTALL; require_once __DIR__.'/../../bootstrap.php'; -eval("@trigger_error('who knows where I come from?', E_USER_DEPRECATED);") +eval("@trigger_error('who knows where I come from?', E_USER_DEPRECATED);"); ?> --EXPECTF-- diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index c5e415bd0b9ec..84a62ae933955 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -12,6 +12,8 @@ namespace Symfony\Bridge\Twig\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -77,7 +79,7 @@ protected function execute(InputInterface $input, OutputInterface $output) if (0 === count($filenames)) { if (0 !== ftell(STDIN)) { - throw new \RuntimeException('Please provide a filename or pipe template content to STDIN.'); + throw new RuntimeException('Please provide a filename or pipe template content to STDIN.'); } $template = ''; @@ -113,7 +115,7 @@ protected function findFiles($filename) return Finder::create()->files()->in($filename)->name('*.twig'); } - throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename)); + throw new RuntimeException(sprintf('File or directory "%s" is not readable', $filename)); } private function validate($template, $file) @@ -142,7 +144,7 @@ private function display(InputInterface $input, OutputInterface $output, Symfony case 'json': return $this->displayJson($output, $files); default: - throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); + throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); } } diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php index 1c3451bbebf46..59497dc961984 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php @@ -20,7 +20,7 @@ class Scope private $data = array(); private $left = false; - public function __construct(Scope $parent = null) + public function __construct(self $parent = null) { $this->parent = $parent; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php index 5244b7ef331e5..edd3e64556622 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Style\StyleInterface; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; @@ -98,7 +99,7 @@ protected function findExtension($name) $message .= sprintf("\n\nDid you mean \"%s\"?", $guess); } - throw new \LogicException($message); + throw new LogicException($message); } public function validateConfiguration(ExtensionInterface $extension, $configuration) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index ac3262baac35d..9bd9d03c95713 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -94,7 +95,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $targetArg = $kernel->getContainer()->getParameter('kernel.project_dir').'/'.$targetArg; if (!is_dir($targetArg)) { - throw new \InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $input->getArgument('target'))); + throw new InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $input->getArgument('target'))); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index fd739745af852..8d7189b80a1fb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -84,7 +85,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $fs->remove($oldCacheDir); if (!is_writable($realCacheDir)) { - throw new \RuntimeException(sprintf('Unable to write in the "%s" directory', $realCacheDir)); + throw new RuntimeException(sprintf('Unable to write in the "%s" directory', $realCacheDir)); } $io->comment(sprintf('Clearing the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); @@ -113,15 +114,33 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers')); } - $containerDir = $fs->exists($warmupDir.'/'.$containerDir) ? false : $containerDir; + if (!$fs->exists($warmupDir.'/'.$containerDir)) { + $fs->rename($realCacheDir.'/'.$containerDir, $warmupDir.'/'.$containerDir); + touch($warmupDir.'/'.$containerDir.'.legacy'); + } - $fs->rename($realCacheDir, $oldCacheDir); - $fs->rename($warmupDir, $realCacheDir); + if ('/' === \DIRECTORY_SEPARATOR && $mounts = @file('/proc/mounts')) { + foreach ($mounts as $mount) { + $mount = array_slice(explode(' ', $mount), 1, -3); + if (!\in_array(array_pop($mount), array('vboxfs', 'nfs'))) { + continue; + } + $mount = implode(' ', $mount).'/'; + + if (0 === strpos($realCacheDir, $mount)) { + $io->note('For better performances, you should move the cache and log directories to a non-shared folder of the VM.'); + $oldCacheDir = false; + break; + } + } + } - if ($containerDir) { - $fs->rename($oldCacheDir.'/'.$containerDir, $realCacheDir.'/'.$containerDir); - touch($realCacheDir.'/'.$containerDir.'.legacy'); + if ($oldCacheDir) { + $fs->rename($realCacheDir, $oldCacheDir); + } else { + $fs->remove($realCacheDir); } + $fs->rename($warmupDir, $realCacheDir); if ($output->isVerbose()) { $io->comment('Removing old cache directory...'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php index aa17ad3a39908..a99457fee05fb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php @@ -13,6 +13,7 @@ use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; @@ -77,7 +78,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } elseif ($pool instanceof Psr6CacheClearer) { $clearers[$id] = $pool; } else { - throw new \InvalidArgumentException(sprintf('"%s" is not a cache pool nor a cache clearer.', $id)); + throw new InvalidArgumentException(sprintf('"%s" is not a cache pool nor a cache clearer.', $id)); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index 8850efa373da8..2c6f7de2c1b2e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -13,6 +13,7 @@ use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; @@ -124,7 +125,7 @@ protected function execute(InputInterface $input, OutputInterface $output) break; default: $io->writeln($message); - throw new \InvalidArgumentException('Only the yaml and xml formats are supported.'); + throw new InvalidArgumentException('Only the yaml and xml formats are supported.'); } $io->writeln(null === $path ? $dumper->dump($configuration, $extension->getNamespace()) : $dumper->dumpAtPath($configuration, $path)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index 9830609d969ad..9865d61e6e8ec 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -14,6 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; use Symfony\Component\Config\ConfigCache; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; @@ -168,9 +169,9 @@ protected function validateInput(InputInterface $input) $name = $input->getArgument('name'); if ((null !== $name) && ($optionsCount > 0)) { - throw new \InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined with the service name argument.'); + throw new InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined with the service name argument.'); } elseif ((null === $name) && $optionsCount > 1) { - throw new \InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined together.'); + throw new InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined together.'); } } @@ -209,7 +210,7 @@ private function findProperServiceName(InputInterface $input, SymfonyStyle $io, $matchingServices = $this->findServiceIdsContaining($builder, $name); if (empty($matchingServices)) { - throw new \InvalidArgumentException(sprintf('No services found that match "%s".', $name)); + throw new InvalidArgumentException(sprintf('No services found that match "%s".', $name)); } $default = 1 === count($matchingServices) ? $matchingServices[0] : null; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index ce932575335b9..15e27668094cd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -14,6 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -80,7 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output) if ($name) { if (!$route = $routes->get($name)) { - throw new \InvalidArgumentException(sprintf('The route "%s" does not exist.', $name)); + throw new InvalidArgumentException(sprintf('The route "%s" does not exist.', $name)); } $callable = $this->extractCallable($route); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index 50737aa75c645..225269d7f18e2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; @@ -151,7 +152,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $viewsPaths = array($input->getArgument('bundle').'/Resources/views'); if (!is_dir($transPaths[0])) { - throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); + throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); } } } elseif ($input->getOption('all')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index a29e3081f36e6..3322d32309697 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Translation\Catalogue\TargetOperation; @@ -151,7 +152,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $currentName = $transPaths[0]; if (!is_dir($transPaths[0])) { - throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); + throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php index e527da1d06e37..55f4880258ae4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -64,7 +65,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $workflow = $container->get('state_machine.'.$serviceId); $dumper = new StateMachineGraphvizDumper(); } else { - throw new \InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $serviceId)); + throw new InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $serviceId)); } $marking = new Marking(); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPrunerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPrunerPass.php index bd692e4261d5d..aa80b69d69710 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPrunerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPrunerPass.php @@ -11,7 +11,6 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; -use Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -27,7 +26,7 @@ class CachePoolPrunerPass implements CompilerPassInterface private $cacheCommandServiceId; private $cachePoolTag; - public function __construct(string $cacheCommandServiceId = CachePoolPruneCommand::class, string $cachePoolTag = 'cache.pool') + public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool') { $this->cacheCommandServiceId = $cacheCommandServiceId; $this->cachePoolTag = $cachePoolTag; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 4b8c8d6dcccd2..6e6b7d19f54be 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -412,7 +412,16 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->children() ->scalarNode('storage_id')->defaultValue('session.storage.native')->end() ->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end() - ->scalarNode('name')->end() + ->scalarNode('name') + ->validate() + ->ifTrue(function ($v) { + parse_str($v, $parsed); + + return implode('&', array_keys($parsed)) !== (string) $v; + }) + ->thenInvalid('Session name %s contains illegal character(s)') + ->end() + ->end() ->scalarNode('cookie_lifetime')->end() ->scalarNode('cookie_path')->end() ->scalarNode('cookie_domain')->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index f9f3ddf0406c9..43e75ceef2f44 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -97,11 +97,6 @@ class FrameworkExtension extends Extension private $annotationsConfigEnabled = false; private $validatorConfigEnabled = false; - /** - * @var string|null - */ - private $kernelRootHash; - /** * Responds to the app.config configuration parameter. * @@ -1391,20 +1386,6 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con } } - /** - * Gets a hash of the kernel root directory. - * - * @return string - */ - private function getKernelRootHash(ContainerBuilder $container) - { - if (!$this->kernelRootHash) { - $this->kernelRootHash = hash('sha256', $container->getParameter('kernel.root_dir')); - } - - return $this->kernelRootHash; - } - /** * Returns the base path for the XSD files. * diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php index 9fd20649289de..452845cea8e34 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php @@ -12,7 +12,6 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Symfony\Component\HttpFoundation\File\File; class ControllerTest extends ControllerTraitTest { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPrunerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPrunerPassTest.php index 105760641c34f..ec7e6200649de 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPrunerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPrunerPassTest.php @@ -12,7 +12,6 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; use PHPUnit\Framework\TestCase; -use Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPrunerPass; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\PhpFilesAdapter; @@ -25,7 +24,7 @@ class CachePoolPrunerPassTest extends TestCase public function testCompilerPassReplacesCommandArgument() { $container = new ContainerBuilder(); - $container->register(CachePoolPruneCommand::class)->addArgument(array()); + $container->register('console.command.cache_pool_prune')->addArgument(array()); $container->register('pool.foo', FilesystemAdapter::class)->addTag('cache.pool'); $container->register('pool.bar', PhpFilesAdapter::class)->addTag('cache.pool'); @@ -36,7 +35,7 @@ public function testCompilerPassReplacesCommandArgument() 'pool.foo' => new Reference('pool.foo'), 'pool.bar' => new Reference('pool.bar'), ); - $argument = $container->getDefinition(CachePoolPruneCommand::class)->getArgument(0); + $argument = $container->getDefinition('console.command.cache_pool_prune')->getArgument(0); $this->assertInstanceOf(IteratorArgument::class, $argument); $this->assertEquals($expected, $argument->getValues()); @@ -64,7 +63,7 @@ public function testCompilePassIsIgnoredIfCommandDoesNotExist() public function testCompilerPassThrowsOnInvalidDefinitionClass() { $container = new ContainerBuilder(); - $container->register(CachePoolPruneCommand::class)->addArgument(array()); + $container->register('console.command.cache_pool_prune')->addArgument(array()); $container->register('pool.not-found', NotFound::class)->addTag('cache.pool'); $pass = new CachePoolPrunerPass(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 8d26715a1c85a..70ad9f3c15fd9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -44,6 +44,41 @@ public function testDoNoDuplicateDefaultFormResources() $this->assertEquals(array('FrameworkBundle:Form'), $config['templating']['form']['resources']); } + public function getTestValidSessionName() + { + return array( + array(null), + array('PHPSESSID'), + array('a&b'), + array(',_-!@#$%^*(){}:<>/?'), + ); + } + + /** + * @dataProvider getTestInvalidSessionName + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + */ + public function testInvalidSessionName($sessionName) + { + $processor = new Processor(); + $processor->processConfiguration( + new Configuration(true), + array(array('session' => array('name' => $sessionName))) + ); + } + + public function getTestInvalidSessionName() + { + return array( + array('a.b'), + array('a['), + array('a[]'), + array('a[b]'), + array('a=b'), + array('a+b'), + ); + } + public function testAssetsCanBeEnabled() { $processor = new Processor(); diff --git a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php index 0ddbdb429d9e0..84227cf47e72e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php @@ -12,6 +12,8 @@ namespace Symfony\Bundle\SecurityBundle\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -171,7 +173,7 @@ private function createPasswordQuestion(): Question return $passwordQuestion->setValidator(function ($value) { if ('' === trim($value)) { - throw new \Exception('The password must not be empty.'); + throw new InvalidArgumentException('The password must not be empty.'); } return $value; @@ -190,7 +192,7 @@ private function getUserClass(InputInterface $input, SymfonyStyle $io) } if (empty($this->userClasses)) { - throw new \RuntimeException('There are no configured encoders for the "security" extension.'); + throw new RuntimeException('There are no configured encoders for the "security" extension.'); } if (!$input->isInteractive() || 1 === count($this->userClasses)) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index b5d08d596e7ec..2d165398f44e2 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -184,14 +184,15 @@ private function createFirewalls($config, ContainerBuilder $container) $configId = 'security.firewall.map.config.'.$name; - list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); + list($matcher, $listeners, $exceptionListener, $logoutListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); $contextId = 'security.firewall.map.context.'.$name; $context = $container->setDefinition($contextId, new ChildDefinition('security.firewall.context')); $context ->replaceArgument(0, new IteratorArgument($listeners)) ->replaceArgument(1, $exceptionListener) - ->replaceArgument(2, new Reference($configId)) + ->replaceArgument(2, $logoutListener) + ->replaceArgument(3, new Reference($configId)) ; $contextRefs[$contextId] = new Reference($contextId); @@ -237,7 +238,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a // Security disabled? if (false === $firewall['security']) { - return array($matcher, array(), null); + return array($matcher, array(), null, null); } $config->replaceArgument(4, $firewall['stateless']); @@ -276,16 +277,15 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $config->replaceArgument(6, $contextKey); // Logout listener + $logoutListenerId = null; if (isset($firewall['logout'])) { - $listenerKeys[] = 'logout'; - $listenerId = 'security.logout_listener.'.$id; - $listener = $container->setDefinition($listenerId, new ChildDefinition('security.logout_listener')); - $listener->replaceArgument(3, array( + $logoutListenerId = 'security.logout_listener.'.$id; + $logoutListener = $container->setDefinition($logoutListenerId, new ChildDefinition('security.logout_listener')); + $logoutListener->replaceArgument(3, array( 'csrf_parameter' => $firewall['logout']['csrf_parameter'], 'csrf_token_id' => $firewall['logout']['csrf_token_id'], 'logout_path' => $firewall['logout']['path'], )); - $listeners[] = new Reference($listenerId); // add logout success handler if (isset($firewall['logout']['success_handler'])) { @@ -295,16 +295,16 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $logoutSuccessHandler = $container->setDefinition($logoutSuccessHandlerId, new ChildDefinition('security.logout.success_handler')); $logoutSuccessHandler->replaceArgument(1, $firewall['logout']['target']); } - $listener->replaceArgument(2, new Reference($logoutSuccessHandlerId)); + $logoutListener->replaceArgument(2, new Reference($logoutSuccessHandlerId)); // add CSRF provider if (isset($firewall['logout']['csrf_token_generator'])) { - $listener->addArgument(new Reference($firewall['logout']['csrf_token_generator'])); + $logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_generator'])); } // add session logout handler if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) { - $listener->addMethodCall('addHandler', array(new Reference('security.logout.handler.session'))); + $logoutListener->addMethodCall('addHandler', array(new Reference('security.logout.handler.session'))); } // add cookie logout handler @@ -313,12 +313,12 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $cookieHandler = $container->setDefinition($cookieHandlerId, new ChildDefinition('security.logout.handler.cookie_clearing')); $cookieHandler->addArgument($firewall['logout']['delete_cookies']); - $listener->addMethodCall('addHandler', array(new Reference($cookieHandlerId))); + $logoutListener->addMethodCall('addHandler', array(new Reference($cookieHandlerId))); } // add custom handlers foreach ($firewall['logout']['handlers'] as $handlerId) { - $listener->addMethodCall('addHandler', array(new Reference($handlerId))); + $logoutListener->addMethodCall('addHandler', array(new Reference($handlerId))); } // register with LogoutUrlGenerator @@ -378,7 +378,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $config->replaceArgument(10, $listenerKeys); $config->replaceArgument(11, isset($firewall['switch_user']) ? $firewall['switch_user'] : null); - return array($matcher, $listeners, $exceptionListener); + return array($matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null); } private function createContextListener($container, $contextKey) diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 6a4c109af4508..515f56db6e8ca 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -138,6 +138,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php index 2375e6b0e3ab6..a3b7f15406919 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\LogoutListener; /** * This is a wrapper around the actual firewall configuration which allows us @@ -23,13 +24,24 @@ class FirewallContext { private $listeners; private $exceptionListener; + private $logoutListener; private $config; - public function __construct(iterable $listeners, ExceptionListener $exceptionListener = null, FirewallConfig $config = null) + /** + * @param LogoutListener|null $logoutListener + */ + public function __construct(iterable $listeners, ExceptionListener $exceptionListener = null, $logoutListener = null, FirewallConfig $config = null) { $this->listeners = $listeners; $this->exceptionListener = $exceptionListener; - $this->config = $config; + if ($logoutListener instanceof FirewallConfig) { + $this->config = $logoutListener; + } elseif (null === $logoutListener || $logoutListener instanceof LogoutListener) { + $this->logoutListener = $logoutListener; + $this->config = $config; + } else { + throw new \InvalidArgumentException(sprintf('Argument 3 passed to %s() must be instance of %s or null, %s given.', __METHOD__, LogoutListener::class, is_object($logoutListener) ? get_class($logoutListener) : gettype($logoutListener))); + } } public function getConfig() @@ -46,4 +58,9 @@ public function getExceptionListener() { return $this->exceptionListener; } + + public function getLogoutListener() + { + return $this->logoutListener; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php index 3b98b0e4cadd3..e11d5dc0f5863 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php @@ -40,10 +40,10 @@ public function getListeners(Request $request) $context = $this->getFirewallContext($request); if (null === $context) { - return array(array(), null); + return array(array(), null, null); } - return array($context->getListeners(), $context->getExceptionListener()); + return array($context->getListeners(), $context->getExceptionListener(), $context->getLogoutListener()); } /** diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index c1791beaf5fbf..17561d6fac93b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -71,7 +71,7 @@ public function testFirewalls() $arguments = $contextDef->getArguments(); $listeners[] = array_map('strval', $arguments['index_0']->getValues()); - $configDef = $container->getDefinition((string) $arguments['index_2']); + $configDef = $container->getDefinition((string) $arguments['index_3']); $configs[] = array_values($configDef->getArguments()); } @@ -99,7 +99,6 @@ public function testFirewalls() null, null, array( - 'logout', 'switch_user', 'x509', 'remote_user', @@ -171,7 +170,6 @@ public function testFirewalls() array(), array( 'security.channel_listener', - 'security.logout_listener.secure', 'security.authentication.listener.x509.secure', 'security.authentication.listener.remote_user.secure', 'security.authentication.listener.form.secure', diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php new file mode 100644 index 0000000000000..7eeb7c21171ce --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional; + +class LogoutTest extends WebTestCase +{ + public function testSessionLessRememberMeLogout() + { + $client = $this->createClient(array('test_case' => 'RememberMeLogout', 'root_config' => 'config.yml')); + + $client->request('POST', '/login', array( + '_username' => 'johannes', + '_password' => 'test', + )); + + $cookieJar = $client->getCookieJar(); + $cookieJar->expire(session_name()); + + $this->assertNotNull($cookieJar->get('REMEMBERME')); + + $client->request('GET', '/logout'); + + $this->assertNull($cookieJar->get('REMEMBERME')); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/bundles.php new file mode 100644 index 0000000000000..d90f774abde2b --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/bundles.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; + +return array( + new FrameworkBundle(), + new SecurityBundle(), +); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml new file mode 100644 index 0000000000000..60e9cb89a229a --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml @@ -0,0 +1,25 @@ +imports: + - { resource: ./../config/framework.yml } + +security: + encoders: + Symfony\Component\Security\Core\User\User: plaintext + + providers: + in_memory: + memory: + users: + johannes: { password: test, roles: [ROLE_USER] } + + firewalls: + default: + form_login: + check_path: login + remember_me: true + require_previous_session: false + remember_me: + always_remember_me: true + secret: key + logout: ~ + anonymous: ~ + stateless: true diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/routing.yml new file mode 100644 index 0000000000000..1dddfca2f8154 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/routing.yml @@ -0,0 +1,5 @@ +login: + path: /login + +logout: + path: /logout diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php index 983e8288214a2..520a129716f4f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php @@ -16,6 +16,7 @@ use Symfony\Bundle\SecurityBundle\Security\FirewallContext; use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\ListenerInterface; +use Symfony\Component\Security\Http\Firewall\LogoutListener; class FirewallContextTest extends TestCase { @@ -23,6 +24,7 @@ public function testGetters() { $config = new FirewallConfig('main', 'user_checker', 'request_matcher'); $exceptionListener = $this->getExceptionListenerMock(); + $logoutListener = $this->getLogoutListenerMock(); $listeners = array( $this ->getMockBuilder(ListenerInterface::class) @@ -30,10 +32,11 @@ public function testGetters() ->getMock(), ); - $context = new FirewallContext($listeners, $exceptionListener, $config); + $context = new FirewallContext($listeners, $exceptionListener, $logoutListener, $config); $this->assertEquals($listeners, $context->getListeners()); $this->assertEquals($exceptionListener, $context->getExceptionListener()); + $this->assertEquals($logoutListener, $context->getLogoutListener()); $this->assertEquals($config, $context->getConfig()); } @@ -44,4 +47,12 @@ private function getExceptionListenerMock() ->disableOriginalConstructor() ->getMock(); } + + private function getLogoutListenerMock() + { + return $this + ->getMockBuilder(LogoutListener::class) + ->disableOriginalConstructor() + ->getMock(); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php index 3e6f0a1ac74c6..42162369cb15c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallMapTest.php @@ -20,6 +20,7 @@ use Symfony\Component\HttpFoundation\RequestMatcherInterface; use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\ListenerInterface; +use Symfony\Component\Security\Http\Firewall\LogoutListener; class FirewallMapTest extends TestCase { @@ -35,7 +36,7 @@ public function testGetListenersWithEmptyMap() $firewallMap = new FirewallMap($container, $map); - $this->assertEquals(array(array(), null), $firewallMap->getListeners($request)); + $this->assertEquals(array(array(), null, null), $firewallMap->getListeners($request)); $this->assertNull($firewallMap->getFirewallConfig($request)); $this->assertFalse($request->attributes->has(self::ATTRIBUTE_FIREWALL_CONTEXT)); } @@ -51,7 +52,7 @@ public function testGetListenersWithInvalidParameter() $firewallMap = new FirewallMap($container, $map); - $this->assertEquals(array(array(), null), $firewallMap->getListeners($request)); + $this->assertEquals(array(array(), null, null), $firewallMap->getListeners($request)); $this->assertNull($firewallMap->getFirewallConfig($request)); $this->assertFalse($request->attributes->has(self::ATTRIBUTE_FIREWALL_CONTEXT)); } @@ -71,6 +72,9 @@ public function testGetListeners() $exceptionListener = $this->getMockBuilder(ExceptionListener::class)->disableOriginalConstructor()->getMock(); $firewallContext->expects($this->once())->method('getExceptionListener')->willReturn($exceptionListener); + $logoutListener = $this->getMockBuilder(LogoutListener::class)->disableOriginalConstructor()->getMock(); + $firewallContext->expects($this->once())->method('getLogoutListener')->willReturn($logoutListener); + $matcher = $this->getMockBuilder(RequestMatcherInterface::class)->getMock(); $matcher->expects($this->once()) ->method('matches') @@ -82,7 +86,7 @@ public function testGetListeners() $firewallMap = new FirewallMap($container, array('security.firewall.map.context.foo' => $matcher)); - $this->assertEquals(array(array($listener), $exceptionListener), $firewallMap->getListeners($request)); + $this->assertEquals(array(array($listener), $exceptionListener, $logoutListener), $firewallMap->getListeners($request)); $this->assertEquals($firewallConfig, $firewallMap->getFirewallConfig($request)); $this->assertEquals('security.firewall.map.context.foo', $request->attributes->get(self::ATTRIBUTE_FIREWALL_CONTEXT)); } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index cd10d1dfb628d..7040e673298bb 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^7.1.3", "ext-xml": "*", - "symfony/security": "~3.4|~4.0", + "symfony/security": "~3.4.10|^4.0.10", "symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/http-kernel": "~3.4|~4.0" }, @@ -44,6 +44,7 @@ "twig/twig": "~1.34|~2.4" }, "conflict": { + "symfony/security": "4.1.0-beta1", "symfony/var-dumper": "<3.4", "symfony/event-dispatcher": "<3.4", "symfony/framework-bundle": "<3.4", diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 50ddf7e0c9ff5..05a09f44d3261 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -21,6 +21,7 @@ "symfony/twig-bridge": "^3.4.3|^4.0.3", "symfony/http-foundation": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8", "twig/twig": "~1.34|~2.4" }, "require-dev": { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig index be77f891034c6..88a5829905eed 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig @@ -7,10 +7,8 @@ 'default': '#999', 'section': '#444', 'event_listener': '#00B8F5', - 'event_listener_loading': '#00B8F5', 'template': '#66CC00', 'doctrine': '#FF6633', - 'propel': '#FF6633', } %} {% endif %} diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php index 6a31b596f67ae..a9c62c99c61d2 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php @@ -15,6 +15,8 @@ use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; use Symfony\Bridge\Monolog\Handler\ConsoleHandler; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -78,7 +80,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $filter = $input->getOption('filter'); if ($filter) { if (!class_exists(ExpressionLanguage::class)) { - throw new \LogicException('Package "symfony/expression-language" is required to use the "filter" option.'); + throw new LogicException('Package "symfony/expression-language" is required to use the "filter" option.'); } $this->el = new ExpressionLanguage(); } @@ -97,7 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!$socket = stream_socket_server($host, $errno, $errstr)) { - throw new \RuntimeException(sprintf('Server start failed on "%s": %s %s.', $host, $errstr, $errno)); + throw new RuntimeException(sprintf('Server start failed on "%s": %s %s.', $host, $errstr, $errno)); } foreach ($this->getLogs($socket) as $clientId => $message) { diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerStatusCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerStatusCommand.php index 8a9a1e92c525b..798095061d1d3 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerStatusCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerStatusCommand.php @@ -13,6 +13,7 @@ use Symfony\Bundle\WebServerBundle\WebServer; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -74,7 +75,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } elseif ('port' === $filter) { $output->write($port); } else { - throw new \InvalidArgumentException(sprintf('"%s" is not a valid filter.', $filter)); + throw new InvalidArgumentException(sprintf('"%s" is not a valid filter.', $filter)); } } else { return 1; diff --git a/src/Symfony/Bundle/WebServerBundle/composer.json b/src/Symfony/Bundle/WebServerBundle/composer.json index ac474b471464e..86b9dfda4928a 100644 --- a/src/Symfony/Bundle/WebServerBundle/composer.json +++ b/src/Symfony/Bundle/WebServerBundle/composer.json @@ -21,6 +21,7 @@ "symfony/console": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8", "symfony/process": "^3.4.2|^4.0.2" }, "autoload": { diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index 67f9477ae3632..62f815e0171ad 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -345,6 +345,7 @@ private function getTagVersions(array $tagsByKey, array &$invalidatedTags = arra foreach ($tagVersions as $tag => $version) { $tags[$tag.static::TAGS_PREFIX] = $tag; if ($fetchTagVersions || !isset($this->knownTagVersions[$tag])) { + $fetchTagVersions = true; continue; } $version -= $this->knownTagVersions[$tag][1]; diff --git a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php index e4b08790ade4c..91763e5a9f33b 100644 --- a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php +++ b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php @@ -119,31 +119,30 @@ private function calculateStatistics(): array ); /** @var TraceableAdapterEvent $call */ foreach ($calls as $call) { - $statistics[$name]['calls'] += 1; + ++$statistics[$name]['calls']; $statistics[$name]['time'] += $call->end - $call->start; if ('getItem' === $call->name) { - $statistics[$name]['reads'] += 1; + ++$statistics[$name]['reads']; if ($call->hits) { - $statistics[$name]['hits'] += 1; + ++$statistics[$name]['hits']; } else { - $statistics[$name]['misses'] += 1; + ++$statistics[$name]['misses']; } } elseif ('getItems' === $call->name) { - $count = $call->hits + $call->misses; - $statistics[$name]['reads'] += $count; + $statistics[$name]['reads'] += $call->hits + $call->misses; $statistics[$name]['hits'] += $call->hits; - $statistics[$name]['misses'] += $count - $call->misses; + $statistics[$name]['misses'] += $call->misses; } elseif ('hasItem' === $call->name) { - $statistics[$name]['reads'] += 1; + ++$statistics[$name]['reads']; if (false === $call->result) { - $statistics[$name]['misses'] += 1; + ++$statistics[$name]['misses']; } else { - $statistics[$name]['hits'] += 1; + ++$statistics[$name]['hits']; } } elseif ('save' === $call->name) { - $statistics[$name]['writes'] += 1; + ++$statistics[$name]['writes']; } elseif ('deleteItem' === $call->name) { - $statistics[$name]['deletes'] += 1; + ++$statistics[$name]['deletes']; } } if ($statistics[$name]['reads']) { diff --git a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php index 094fa9558bfd5..7074299e7ac34 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php @@ -69,6 +69,12 @@ public function testInvalidateTags() $this->assertFalse($pool->getItem('i1')->isHit()); $this->assertFalse($pool->getItem('i3')->isHit()); $this->assertTrue($pool->getItem('foo')->isHit()); + + $anotherPoolInstance = $this->createCachePool(); + + $this->assertFalse($anotherPoolInstance->getItem('i1')->isHit()); + $this->assertFalse($anotherPoolInstance->getItem('i3')->isHit()); + $this->assertTrue($anotherPoolInstance->getItem('foo')->isHit()); } public function testInvalidateCommits() diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index b8e05d9e417c9..9fc6177c6bd78 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -126,9 +126,12 @@ public static function createConnection($dsn, array $options = array()) throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e->getMessage(), $dsn)); } - if (@!$redis->isConnected()) { - $e = ($e = error_get_last()) && preg_match('/^Redis::p?connect\(\): (.*)/', $e['message'], $e) ? sprintf(' (%s)', $e[1]) : ''; - throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $e, $dsn)); + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $isConnected = $redis->isConnected(); + restore_error_handler(); + if (!$isConnected) { + $error = preg_match('/^Redis::p?connect\(\): (.*)/', $error, $error) ? sprintf(' (%s)', $error[1]) : ''; + throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $error, $dsn)); } if ((null !== $auth && !$redis->auth($auth)) diff --git a/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php b/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php index 9d61c9cd83284..10b4a7a9b4dc7 100644 --- a/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php +++ b/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php @@ -55,7 +55,7 @@ public function testLoadFile() XmlUtils::loadFile($fixtures.'valid.xml', array($mock, 'validate')); $this->fail(); } catch (\InvalidArgumentException $e) { - $this->assertRegExp('/The XML file "[\w:\/\\\.~+-]+" is not valid\./', $e->getMessage()); + $this->assertRegExp('/The XML file ".+" is not valid\./', $e->getMessage()); } $this->assertInstanceOf('DOMDocument', XmlUtils::loadFile($fixtures.'valid.xml', array($mock, 'validate'))); diff --git a/src/Symfony/Component/Config/composer.json b/src/Symfony/Component/Config/composer.json index ea00fa5e2f8cc..c940c7e19600a 100644 --- a/src/Symfony/Component/Config/composer.json +++ b/src/Symfony/Component/Config/composer.json @@ -17,7 +17,8 @@ ], "require": { "php": "^7.1.3", - "symfony/filesystem": "~3.4|~4.0" + "symfony/filesystem": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8" }, "require-dev": { "symfony/dependency-injection": "~3.4|~4.0", diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index f42ee945f5111..b64d9af361e55 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -218,12 +218,11 @@ public function run(InputInterface $input, OutputInterface $output) if (null !== $this->processTitle) { if (function_exists('cli_set_process_title')) { - if (false === @cli_set_process_title($this->processTitle)) { + if (!@cli_set_process_title($this->processTitle)) { if ('Darwin' === PHP_OS) { $output->writeln('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.'); } else { - $error = error_get_last(); - trigger_error($error['message'], E_USER_WARNING); + cli_set_process_title($this->processTitle); } } } elseif (function_exists('setproctitle')) { diff --git a/src/Symfony/Component/CssSelector/Node/Specificity.php b/src/Symfony/Component/CssSelector/Node/Specificity.php index 11228f7246d3a..9b35cb411632c 100644 --- a/src/Symfony/Component/CssSelector/Node/Specificity.php +++ b/src/Symfony/Component/CssSelector/Node/Specificity.php @@ -40,7 +40,7 @@ public function __construct(int $a, int $b, int $c) $this->c = $c; } - public function plus(Specificity $specificity): Specificity + public function plus(self $specificity): self { return new self($this->a + $specificity->a, $this->b + $specificity->b, $this->c + $specificity->c); } @@ -56,7 +56,7 @@ public function getValue(): int * * @return int */ - public function compareTo(Specificity $specificity) + public function compareTo(self $specificity) { if ($this->a !== $specificity->a) { return $this->a > $specificity->a ? 1 : -1; diff --git a/src/Symfony/Component/CssSelector/XPath/Translator.php b/src/Symfony/Component/CssSelector/XPath/Translator.php index 28478b3db7e47..73b548215d671 100644 --- a/src/Symfony/Component/CssSelector/XPath/Translator.php +++ b/src/Symfony/Component/CssSelector/XPath/Translator.php @@ -114,7 +114,7 @@ public function selectorToXPath(SelectorNode $selector, string $prefix = 'descen return ($prefix ?: '').$this->nodeToXPath($selector); } - public function registerExtension(Extension\ExtensionInterface $extension): Translator + public function registerExtension(Extension\ExtensionInterface $extension): self { $this->extensions[$extension->getName()] = $extension; @@ -139,7 +139,7 @@ public function getExtension(string $name): Extension\ExtensionInterface return $this->extensions[$name]; } - public function registerParserShortcut(ParserInterface $shortcut): Translator + public function registerParserShortcut(ParserInterface $shortcut): self { $this->shortcutParsers[] = $shortcut; diff --git a/src/Symfony/Component/CssSelector/XPath/XPathExpr.php b/src/Symfony/Component/CssSelector/XPath/XPathExpr.php index 8090df99075fa..638cbd0fe0619 100644 --- a/src/Symfony/Component/CssSelector/XPath/XPathExpr.php +++ b/src/Symfony/Component/CssSelector/XPath/XPathExpr.php @@ -43,7 +43,7 @@ public function getElement(): string return $this->element; } - public function addCondition(string $condition): XPathExpr + public function addCondition(string $condition): self { $this->condition = $this->condition ? sprintf('(%s) and (%s)', $this->condition, $condition) : $condition; @@ -55,7 +55,7 @@ public function getCondition(): string return $this->condition; } - public function addNameTest(): XPathExpr + public function addNameTest(): self { if ('*' !== $this->element) { $this->addCondition('name() = '.Translator::getXpathLiteral($this->element)); @@ -65,7 +65,7 @@ public function addNameTest(): XPathExpr return $this; } - public function addStarPrefix(): XPathExpr + public function addStarPrefix(): self { $this->path .= '*/'; diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php index 5dbdd6933898e..2702b4157f6df 100644 --- a/src/Symfony/Component/Debug/ErrorHandler.php +++ b/src/Symfony/Component/Debug/ErrorHandler.php @@ -380,14 +380,16 @@ private function reRegister($prev) public function handleError($type, $message, $file, $line) { // Level is the current error reporting level to manage silent error. + $level = error_reporting(); + $silenced = 0 === ($level & $type); // Strong errors are not authorized to be silenced. - $level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; + $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; $log = $this->loggedErrors & $type; $throw = $this->thrownErrors & $type & $level; $type &= $level | $this->screamedErrors; if (!$type || (!$log && !$throw)) { - return $type && $log; + return !$silenced && $type && $log; } $scope = $this->scopedErrors & $type; @@ -493,7 +495,7 @@ public function handleError($type, $message, $file, $line) } } - return $type && $log; + return !$silenced && $type && $log; } /** diff --git a/src/Symfony/Component/Debug/Exception/FlattenException.php b/src/Symfony/Component/Debug/Exception/FlattenException.php index 24679dcaab242..f491bf2ac4c92 100644 --- a/src/Symfony/Component/Debug/Exception/FlattenException.php +++ b/src/Symfony/Component/Debug/Exception/FlattenException.php @@ -157,7 +157,7 @@ public function getPrevious() return $this->previous; } - public function setPrevious(FlattenException $previous) + public function setPrevious(self $previous) { $this->previous = $previous; } diff --git a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php index afe16ea4f759c..eee8e9dc57539 100644 --- a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -65,6 +65,30 @@ public function testRegister() } } + public function testErrorGetLast() + { + $handler = ErrorHandler::register(); + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $handler->setDefaultLogger($logger); + $handler->screamAt(E_ALL); + + try { + @trigger_error('Hello', E_USER_WARNING); + $expected = array( + 'type' => E_USER_WARNING, + 'message' => 'Hello', + 'file' => __FILE__, + 'line' => __LINE__ - 5, + ); + $this->assertSame($expected, error_get_last()); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + public function testNotice() { ErrorHandler::register(); diff --git a/src/Symfony/Component/DependencyInjection/ChildDefinition.php b/src/Symfony/Component/DependencyInjection/ChildDefinition.php index bcb7ff4838f64..0f67e960dffce 100644 --- a/src/Symfony/Component/DependencyInjection/ChildDefinition.php +++ b/src/Symfony/Component/DependencyInjection/ChildDefinition.php @@ -121,12 +121,4 @@ public function setInstanceofConditionals(array $instanceof) { throw new BadMethodCallException('A ChildDefinition cannot have instanceof conditionals set on it.'); } - - /** - * @internal - */ - public function setBindings(array $bindings) - { - throw new BadMethodCallException('A ChildDefinition cannot have bindings set on it.'); - } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php index 5d2d4429e4a97..901dc06ffaee5 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -118,8 +118,12 @@ protected function getConstructor(Definition $definition, $required) $class = $definition->getClass(); - if (!$r = $this->container->getReflectionClass($class)) { - throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class)); + try { + if (!$r = $this->container->getReflectionClass($class)) { + throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class)); + } + } catch (\ReflectionException $e) { + throw new RuntimeException(sprintf('Invalid service "%s": %s.', $this->currentId, lcfirst(rtrim($e->getMessage(), '.')))); } if (!$r = $r->getConstructor()) { if ($required) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index 00a1e1f99fced..4567ae13c72fc 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -120,6 +120,6 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe return false; } - return true; + return $this->container->getDefinition($ids[0])->isShared(); } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php index c82a974360674..0051fb5ac3df0 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php @@ -27,6 +27,7 @@ class ResolveBindingsPass extends AbstractRecursivePass { private $usedBindings = array(); private $unusedBindings = array(); + private $errorMessages = array(); /** * {@inheritdoc} @@ -37,11 +38,19 @@ public function process(ContainerBuilder $container) parent::process($container); foreach ($this->unusedBindings as list($key, $serviceId)) { - throw new InvalidArgumentException(sprintf('Unused binding "%s" in service "%s".', $key, $serviceId)); + $message = sprintf('Unused binding "%s" in service "%s".', $key, $serviceId); + if ($this->errorMessages) { + $message .= sprintf("\nCould be related to%s:", 1 < \count($this->errorMessages) ? ' one of' : ''); + } + foreach ($this->errorMessages as $m) { + $message .= "\n - ".$m; + } + throw new InvalidArgumentException($message); } } finally { $this->usedBindings = array(); $this->unusedBindings = array(); + $this->errorMessages = array(); } } @@ -94,6 +103,7 @@ protected function processValue($value, $isRoot = false) $calls[] = array($constructor, $value->getArguments()); } } catch (RuntimeException $e) { + $this->errorMessages[] = $e->getMessage(); $this->container->getDefinition($this->currentId)->addError($e->getMessage()); return parent::processValue($value, $isRoot); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php index 0ad3fe1dcd509..660385b88e5cd 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php @@ -100,7 +100,7 @@ private function doResolveDefinition(ChildDefinition $definition) $def->setAutowired($parentDef->isAutowired()); $def->setChanges($parentDef->getChanges()); - $def->setBindings($parentDef->getBindings()); + $def->setBindings($definition->getBindings() + $parentDef->getBindings()); // overwrite with values specified in the decorator $changes = $definition->getChanges(); diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index d9657ce479d31..b0775477d8fe4 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -1359,6 +1359,7 @@ public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs } $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; + $completed = false; foreach ($envPlaceholders as $env => $placeholders) { foreach ($placeholders as $placeholder) { if (false !== stripos($value, $placeholder)) { @@ -1369,14 +1370,19 @@ public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs } if ($placeholder === $value) { $value = $resolved; + $completed = true; } else { if (!is_string($resolved) && !is_numeric($resolved)) { - throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type %s inside string value "%s".', $env, gettype($resolved), $value)); + throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type %s inside string value "%s".', $env, gettype($resolved), $this->resolveEnvPlaceholders($value))); } $value = str_ireplace($placeholder, $resolved, $value); } $usedEnvs[$env] = $env; $this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1; + + if ($completed) { + break 2; + } } } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php index 9ecb2a21c3deb..78a8e3c327f85 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InstanceofConfigurator.php @@ -30,7 +30,7 @@ class InstanceofConfigurator extends AbstractServiceConfigurator /** * Defines an instanceof-conditional to be applied to following service definitions. */ - final public function instanceof(string $fqcn): InstanceofConfigurator + final public function instanceof(string $fqcn): self { return $this->parent->instanceof($fqcn); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php index bb60af4d19e64..78556b2ed21cd 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php @@ -92,7 +92,7 @@ public function testProcessDoesInlineNonSharedService() $this->assertNotSame($container->getDefinition('bar'), $arguments[2]); } - public function testProcessInlinesMixedServicesLoop() + public function testProcessDoesNotInlineMixedServicesLoop() { $container = new ContainerBuilder(); $container @@ -108,7 +108,7 @@ public function testProcessInlinesMixedServicesLoop() $this->process($container); - $this->assertEquals($container->getDefinition('foo')->getArgument(0), $container->getDefinition('bar')); + $this->assertEquals(new Reference('bar'), $container->getDefinition('foo')->getArgument(0)); } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php index 16e486afafe2e..65f5ceb80fd0c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php @@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy; +use Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists; use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\TypedReference; @@ -61,6 +62,21 @@ public function testUnusedBinding() $pass->process($container); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessageRegexp Unused binding "$quz" in service [\s\S]+ Invalid service ".*\\ParentNotExists": class NotExists not found\. + */ + public function testMissingParent() + { + $container = new ContainerBuilder(); + + $definition = $container->register(ParentNotExists::class, ParentNotExists::class); + $definition->setBindings(array('$quz' => '123')); + + $pass = new ResolveBindingsPass(); + $pass->process($container); + } + public function testTypedReferenceSupport() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php index d15be74ecd969..e633f843198f7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php @@ -355,6 +355,27 @@ public function testProcessSetsArguments() $this->assertSame(array(2, 1, 'foo' => 3), $def->getArguments()); } + public function testBindings() + { + $container = new ContainerBuilder(); + + $container->register('parent', 'stdClass') + ->setBindings(array('a' => '1', 'b' => '2')) + ; + + $child = $container->setDefinition('child', new ChildDefinition('parent')) + ->setBindings(array('b' => 'B', 'c' => 'C')) + ; + + $this->process($container); + + $bindings = array(); + foreach ($container->getDefinition('child')->getBindings() as $k => $v) { + $bindings[$k] = $v->getValues()[0]; + } + $this->assertEquals(array('b' => 'B', 'c' => 'C', 'a' => '1'), $bindings); + } + public function testSetAutoconfiguredOnServiceIsParent() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 1f808d1ca9a4b..c9b5f9da01d5e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -665,17 +665,49 @@ public function testCompileWithResolveEnv() putenv('DUMMY_ENV_VAR'); } + public function testCompileWithArrayResolveEnv() + { + putenv('ARRAY={"foo":"bar"}'); + + $container = new ContainerBuilder(); + $container->setParameter('foo', '%env(json:ARRAY)%'); + $container->compile(true); + + $this->assertSame(array('foo' => 'bar'), $container->getParameter('foo')); + + putenv('ARRAY'); + } + + public function testCompileWithArrayAndAnotherResolveEnv() + { + putenv('DUMMY_ENV_VAR=abc'); + putenv('ARRAY={"foo":"bar"}'); + + $container = new ContainerBuilder(); + $container->setParameter('foo', '%env(json:ARRAY)%'); + $container->setParameter('bar', '%env(DUMMY_ENV_VAR)%'); + $container->compile(true); + + $this->assertSame(array('foo' => 'bar'), $container->getParameter('foo')); + $this->assertSame('abc', $container->getParameter('bar')); + + putenv('DUMMY_ENV_VAR'); + putenv('ARRAY'); + } + /** * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException - * @expectedExceptionMessage A string value must be composed of strings and/or numbers, but found parameter "env(ARRAY)" of type array inside string value "ABC %env(ARRAY)%". + * @expectedExceptionMessage A string value must be composed of strings and/or numbers, but found parameter "env(json:ARRAY)" of type array inside string value "ABC %env(json:ARRAY)%". */ - public function testCompileWithArrayResolveEnv() + public function testCompileWithArrayInStringResolveEnv() { - $bag = new TestingEnvPlaceholderParameterBag(); - $container = new ContainerBuilder($bag); - $container->setParameter('foo', '%env(ARRAY)%'); - $container->setParameter('bar', 'ABC %env(ARRAY)%'); + putenv('ARRAY={"foo":"bar"}'); + + $container = new ContainerBuilder(); + $container->setParameter('foo', 'ABC %env(json:ARRAY)%'); $container->compile(true); + + putenv('ARRAY'); } /** @@ -1400,11 +1432,3 @@ public function __construct(A $a) { } } - -class TestingEnvPlaceholderParameterBag extends EnvPlaceholderParameterBag -{ - public function get($name) - { - return 'env(array)' === strtolower($name) ? array(123) : parent::get($name); - } -} diff --git a/src/Symfony/Component/DomCrawler/composer.json b/src/Symfony/Component/DomCrawler/composer.json index 8c04c700168ae..51722fca48e52 100644 --- a/src/Symfony/Component/DomCrawler/composer.json +++ b/src/Symfony/Component/DomCrawler/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index db5df5cbeb472..046dbb940e391 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -22,6 +22,8 @@ */ class Filesystem { + private static $lastError; + /** * Copies a file. * @@ -96,12 +98,11 @@ public function mkdir($dirs, $mode = 0777) continue; } - if (true !== @mkdir($dir, $mode, true)) { - $error = error_get_last(); + if (!self::box('mkdir', $dir, $mode, true)) { if (!is_dir($dir)) { // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one - if ($error) { - throw new IOException(sprintf('Failed to create "%s": %s.', $dir, $error['message']), 0, null, $dir); + if (self::$lastError) { + throw new IOException(sprintf('Failed to create "%s": %s.', $dir, self::$lastError), 0, null, $dir); } throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir); } @@ -170,20 +171,17 @@ public function remove($files) foreach ($files as $file) { if (is_link($file)) { // See https://bugs.php.net/52176 - if (!@(unlink($file) || '\\' !== DIRECTORY_SEPARATOR || rmdir($file)) && file_exists($file)) { - $error = error_get_last(); - throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, $error['message'])); + if (!(self::box('unlink', $file) || '\\' !== DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) { + throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, self::$lastError)); } } elseif (is_dir($file)) { $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); - if (!@rmdir($file) && file_exists($file)) { - $error = error_get_last(); - throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, $error['message'])); + if (!self::box('rmdir', $file) && file_exists($file)) { + throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, self::$lastError)); } - } elseif (!@unlink($file) && file_exists($file)) { - $error = error_get_last(); - throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, $error['message'])); + } elseif (!self::box('unlink', $file) && file_exists($file)) { + throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, self::$lastError)); } } } @@ -337,16 +335,14 @@ public function symlink($originDir, $targetDir, $copyOnWindows = false) $this->mkdir(dirname($targetDir)); - $ok = false; if (is_link($targetDir)) { - if (readlink($targetDir) != $originDir) { - $this->remove($targetDir); - } else { - $ok = true; + if (readlink($targetDir) === $originDir) { + return; } + $this->remove($targetDir); } - if (!$ok && true !== @symlink($originDir, $targetDir)) { + if (!self::box('symlink', $originDir, $targetDir)) { $this->linkException($originDir, $targetDir, 'symbolic'); } } @@ -378,7 +374,7 @@ public function hardlink($originFile, $targetFiles) $this->remove($targetFile); } - if (true !== @link($originFile, $targetFile)) { + if (!self::box('link', $originFile, $targetFile)) { $this->linkException($originFile, $targetFile, 'hard'); } } @@ -391,9 +387,8 @@ public function hardlink($originFile, $targetFiles) */ private function linkException($origin, $target, $linkType) { - $report = error_get_last(); - if (is_array($report)) { - if ('\\' === DIRECTORY_SEPARATOR && false !== strpos($report['message'], 'error code(1314)')) { + if (self::$lastError) { + if ('\\' === DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) { throw new IOException(sprintf('Unable to create %s link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target); } } @@ -743,4 +738,29 @@ private function getSchemeAndHierarchy(string $filename): array return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]); } + + private static function box($func) + { + self::$lastError = null; + \set_error_handler(__CLASS__.'::handleError'); + try { + $result = \call_user_func_array($func, \array_slice(\func_get_args(), 1)); + \restore_error_handler(); + + return $result; + } catch (\Throwable $e) { + } catch (\Exception $e) { + } + \restore_error_handler(); + + throw $e; + } + + /** + * @internal + */ + public static function handleError($type, $msg) + { + self::$lastError = $msg; + } } diff --git a/src/Symfony/Component/Filesystem/composer.json b/src/Symfony/Component/Filesystem/composer.json index bee959429f90c..947af602b4a62 100644 --- a/src/Symfony/Component/Filesystem/composer.json +++ b/src/Symfony/Component/Filesystem/composer.json @@ -16,7 +16,8 @@ } ], "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, diff --git a/src/Symfony/Component/Finder/SplFileInfo.php b/src/Symfony/Component/Finder/SplFileInfo.php index 6516113a3ece4..b8143ed799162 100644 --- a/src/Symfony/Component/Finder/SplFileInfo.php +++ b/src/Symfony/Component/Finder/SplFileInfo.php @@ -66,12 +66,11 @@ public function getRelativePathname() */ public function getContents() { - $level = error_reporting(0); + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); $content = file_get_contents($this->getPathname()); - error_reporting($level); + restore_error_handler(); if (false === $content) { - $error = error_get_last(); - throw new \RuntimeException($error['message']); + throw new \RuntimeException($error); } return $content; diff --git a/src/Symfony/Component/Form/FormInterface.php b/src/Symfony/Component/Form/FormInterface.php index 921ae2829c5ff..a0e9b636c5f38 100644 --- a/src/Symfony/Component/Form/FormInterface.php +++ b/src/Symfony/Component/Form/FormInterface.php @@ -29,7 +29,7 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable * @throws Exception\LogicException when trying to set a parent for a form with * an empty name */ - public function setParent(FormInterface $parent = null); + public function setParent(self $parent = null); /** * Returns the parent form. diff --git a/src/Symfony/Component/Form/FormRegistry.php b/src/Symfony/Component/Form/FormRegistry.php index 6be83ab95de60..cb8facdac0767 100644 --- a/src/Symfony/Component/Form/FormRegistry.php +++ b/src/Symfony/Component/Form/FormRegistry.php @@ -31,7 +31,7 @@ class FormRegistry implements FormRegistryInterface private $extensions = array(); /** - * @var FormTypeInterface[] + * @var ResolvedFormTypeInterface[] */ private $types = array(); diff --git a/src/Symfony/Component/Form/FormView.php b/src/Symfony/Component/Form/FormView.php index 21a3d5036ad2b..9fbca4379688a 100644 --- a/src/Symfony/Component/Form/FormView.php +++ b/src/Symfony/Component/Form/FormView.php @@ -51,7 +51,7 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable private $methodRendered = false; - public function __construct(FormView $parent = null) + public function __construct(self $parent = null) { $this->parent = $parent; } diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php index 2cadba0ba9e67..f9c4613e591c7 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php @@ -11,6 +11,15 @@ namespace Symfony\Component\Form\Tests; +use Symfony\Component\Form\Extension\Core\Type\ButtonType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Form\Extension\Core\Type\MoneyType; +use Symfony\Component\Form\Extension\Core\Type\PercentType; +use Symfony\Component\Form\Extension\Core\Type\RadioType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormError; /** @@ -22,7 +31,7 @@ abstract class AbstractBootstrap4LayoutTest extends AbstractBootstrap3LayoutTest { public function testRow() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $form = $this->factory->createNamed('name', TextType::class); $form->addError(new FormError('[trans]Error![/trans]')); $view = $form->createView(); $html = $this->renderRow($view); @@ -47,7 +56,7 @@ public function testRow() public function testLabelOnForm() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); + $form = $this->factory->createNamed('name', DateType::class); $view = $form->createView(); $this->renderWidget($view, array('label' => 'foo')); $html = $this->renderLabel($view); @@ -62,7 +71,7 @@ public function testLabelOnForm() public function testLabelDoesNotRenderFieldAttributes() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $form = $this->factory->createNamed('name', TextType::class); $html = $this->renderLabel($form->createView(), null, array( 'attr' => array( 'class' => 'my&class', @@ -79,7 +88,7 @@ public function testLabelDoesNotRenderFieldAttributes() public function testLabelWithCustomAttributesPassedDirectly() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $form = $this->factory->createNamed('name', TextType::class); $html = $this->renderLabel($form->createView(), null, array( 'label_attr' => array( 'class' => 'my&class', @@ -96,7 +105,7 @@ public function testLabelWithCustomAttributesPassedDirectly() public function testLabelWithCustomTextAndCustomAttributesPassedDirectly() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $form = $this->factory->createNamed('name', TextType::class); $html = $this->renderLabel($form->createView(), 'Custom label', array( 'label_attr' => array( 'class' => 'my&class', @@ -114,7 +123,7 @@ public function testLabelWithCustomTextAndCustomAttributesPassedDirectly() public function testLabelWithCustomTextAsOptionAndCustomAttributesPassedDirectly() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, array( + $form = $this->factory->createNamed('name', TextType::class, null, array( 'label' => 'Custom label', )); $html = $this->renderLabel($form->createView(), null, array( @@ -134,7 +143,7 @@ public function testLabelWithCustomTextAsOptionAndCustomAttributesPassedDirectly public function testLegendOnExpandedType() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( + $form = $this->factory->createNamed('name', ChoiceType::class, null, array( 'label' => 'Custom label', 'expanded' => true, 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), @@ -153,7 +162,7 @@ public function testLegendOnExpandedType() public function testErrors() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $form = $this->factory->createNamed('name', TextType::class); $form->addError(new FormError('[trans]Error 1[/trans]')); $form->addError(new FormError('[trans]Error 2[/trans]')); $view = $form->createView(); @@ -178,7 +187,7 @@ public function testErrors() public function testErrorWithNoLabel() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', array('label'=>false)); + $form = $this->factory->createNamed('name', TextType::class, array('label' => false)); $form->addError(new FormError('[trans]Error 1[/trans]')); $view = $form->createView(); $html = $this->renderLabel($view); @@ -188,7 +197,7 @@ public function testErrorWithNoLabel() public function testCheckedCheckbox() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', true); + $form = $this->factory->createNamed('name', CheckboxType::class, true); $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')), '/div @@ -205,7 +214,7 @@ public function testCheckedCheckbox() public function testSingleChoiceAttributesWithMainAttributes() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'multiple' => false, 'expanded' => false, @@ -228,7 +237,7 @@ public function testSingleChoiceAttributesWithMainAttributes() public function testSingleExpandedChoiceAttributesWithMainAttributes() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'multiple' => false, 'expanded' => true, @@ -261,7 +270,7 @@ public function testSingleExpandedChoiceAttributesWithMainAttributes() public function testUncheckedCheckbox() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', false); + $form = $this->factory->createNamed('name', CheckboxType::class, false); $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')), '/div @@ -277,7 +286,7 @@ public function testUncheckedCheckbox() public function testCheckboxWithValue() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', false, array( + $form = $this->factory->createNamed('name', CheckboxType::class, false, array( 'value' => 'foo&bar', )); @@ -295,7 +304,7 @@ public function testCheckboxWithValue() public function testSingleChoiceExpanded() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'multiple' => false, 'expanded' => true, @@ -326,7 +335,7 @@ public function testSingleChoiceExpanded() public function testSingleChoiceExpandedWithLabelsAsFalse() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'choice_label' => false, 'multiple' => false, @@ -356,7 +365,7 @@ public function testSingleChoiceExpandedWithLabelsAsFalse() public function testSingleChoiceExpandedWithLabelsSetByCallable() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), 'choice_label' => function ($choice, $label, $value) { if ('&b' === $choice) { @@ -400,7 +409,7 @@ public function testSingleChoiceExpandedWithLabelsSetByCallable() public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'choice_label' => function () { return false; @@ -432,7 +441,7 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() public function testSingleChoiceExpandedWithoutTranslation() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'multiple' => false, 'expanded' => true, @@ -464,7 +473,7 @@ public function testSingleChoiceExpandedWithoutTranslation() public function testSingleChoiceExpandedAttributes() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, @@ -496,7 +505,7 @@ public function testSingleChoiceExpandedAttributes() public function testSingleChoiceExpandedWithPlaceholder() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'multiple' => false, 'expanded' => true, @@ -536,7 +545,7 @@ public function testSingleChoiceExpandedWithPlaceholder() public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + $form = $this->factory->createNamed('name', ChoiceType::class, '&a', array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'multiple' => false, 'expanded' => true, @@ -577,7 +586,7 @@ public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() public function testSingleChoiceExpandedWithBooleanValue() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', true, array( + $form = $this->factory->createNamed('name', ChoiceType::class, true, array( 'choices' => array('Choice&A' => '1', 'Choice&B' => '0'), 'multiple' => false, 'expanded' => true, @@ -608,7 +617,7 @@ public function testSingleChoiceExpandedWithBooleanValue() public function testMultipleChoiceExpanded() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array( + $form = $this->factory->createNamed('name', ChoiceType::class, array('&a', '&c'), array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), 'multiple' => true, 'expanded' => true, @@ -647,7 +656,7 @@ public function testMultipleChoiceExpanded() public function testMultipleChoiceExpandedWithLabelsAsFalse() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( + $form = $this->factory->createNamed('name', ChoiceType::class, array('&a'), array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'choice_label' => false, 'multiple' => true, @@ -677,7 +686,7 @@ public function testMultipleChoiceExpandedWithLabelsAsFalse() public function testMultipleChoiceExpandedWithLabelsSetByCallable() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( + $form = $this->factory->createNamed('name', ChoiceType::class, array('&a'), array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), 'choice_label' => function ($choice, $label, $value) { if ('&b' === $choice) { @@ -721,7 +730,7 @@ public function testMultipleChoiceExpandedWithLabelsSetByCallable() public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( + $form = $this->factory->createNamed('name', ChoiceType::class, array('&a'), array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), 'choice_label' => function () { return false; @@ -753,7 +762,7 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() public function testMultipleChoiceExpandedWithoutTranslation() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array( + $form = $this->factory->createNamed('name', ChoiceType::class, array('&a', '&c'), array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), 'multiple' => true, 'expanded' => true, @@ -793,7 +802,7 @@ public function testMultipleChoiceExpandedWithoutTranslation() public function testMultipleChoiceExpandedAttributes() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array( + $form = $this->factory->createNamed('name', ChoiceType::class, array('&a', '&c'), array( 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => true, @@ -833,7 +842,7 @@ public function testMultipleChoiceExpandedAttributes() public function testCheckedRadio() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', true); + $form = $this->factory->createNamed('name', RadioType::class, true); $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')), '/div @@ -855,7 +864,7 @@ public function testCheckedRadio() public function testUncheckedRadio() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false); + $form = $this->factory->createNamed('name', RadioType::class, false); $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')), '/div @@ -876,7 +885,7 @@ public function testUncheckedRadio() public function testRadioWithValue() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false, array( + $form = $this->factory->createNamed('name', RadioType::class, false, array( 'value' => 'foo&bar', )); @@ -900,7 +909,7 @@ public function testRadioWithValue() public function testButtonAttributeNameRepeatedIfTrue() { - $form = $this->factory->createNamed('button', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', null, array( + $form = $this->factory->createNamed('button', ButtonType::class, null, array( 'attr' => array('foo' => true), )); @@ -912,7 +921,7 @@ public function testButtonAttributeNameRepeatedIfTrue() public function testFile() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\FileType'); + $form = $this->factory->createNamed('name', FileType::class); $this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'my&class form-control-file')), '/input @@ -923,7 +932,7 @@ public function testFile() public function testMoney() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\MoneyType', 1234.56, array( + $form = $this->factory->createNamed('name', MoneyType::class, 1234.56, array( 'currency' => 'EUR', )); @@ -951,7 +960,7 @@ public function testMoney() public function testPercent() { - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PercentType', 0.1); + $form = $this->factory->createNamed('name', PercentType::class, 0.1); $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')), '/div diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformerTest.php index 1ad3aa1615c98..d9fafdff13a35 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformerTest.php @@ -78,6 +78,16 @@ public function testFloatToIntConversionMismatchOnReversTransform() $transformer = new MoneyToLocalizedStringTransformer(null, null, null, 100); IntlTestHelper::requireFullIntl($this, false); \Locale::setDefault('de_AT'); + $this->assertSame(3655, (int) $transformer->reverseTransform('36,55')); } + + public function testFloatToIntConversionMismatchOnTransform() + { + $transformer = new MoneyToLocalizedStringTransformer(null, null, MoneyToLocalizedStringTransformer::ROUND_DOWN, 100); + IntlTestHelper::requireFullIntl($this, false); + \Locale::setDefault('de_AT'); + + $this->assertSame('10,20', $transformer->transform(1020)); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 9cbe941581418..1f24323251790 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -2000,7 +2000,7 @@ public function testStripLeadingUnderscoresAndDigitsFromId() $this->assertEquals('_09name', $view->vars['full_name']); } - /** + /** * @dataProvider provideTrimCases */ public function testTrimIsDisabled($multiple, $expanded) diff --git a/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php b/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php index a320344997fa9..b48549c6e03a4 100644 --- a/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php +++ b/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php @@ -17,7 +17,7 @@ * Contrary to \ArrayIterator, this iterator recognizes changes in the original * array during iteration. * - * You can wrap the iterator into a {@link \RecursiveIterator} in order to + * You can wrap the iterator into a {@link \RecursiveIteratorIterator} in order to * enter any child form that inherits its parent's data and iterate the children * of that form as well. * diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 4905c73c68322..3485615344329 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -20,6 +20,7 @@ "symfony/event-dispatcher": "~3.4|~4.0", "symfony/intl": "~3.4|~4.0", "symfony/options-resolver": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", "symfony/property-access": "~3.4|~4.0" }, diff --git a/src/Symfony/Component/HttpFoundation/File/File.php b/src/Symfony/Component/HttpFoundation/File/File.php index 4e6887a52ad48..44ac3925114a9 100644 --- a/src/Symfony/Component/HttpFoundation/File/File.php +++ b/src/Symfony/Component/HttpFoundation/File/File.php @@ -93,9 +93,11 @@ public function move($directory, $name = null) { $target = $this->getTargetFile($directory, $name); - if (!@rename($this->getPathname(), $target)) { - $error = error_get_last(); - throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $renamed = rename($this->getPathname(), $target); + restore_error_handler(); + if (!$renamed) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); } @chmod($target, 0666 & ~umask()); diff --git a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php index 678c4f0afc6ef..e234537cb22db 100644 --- a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php +++ b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php @@ -192,9 +192,11 @@ public function move($directory, $name = null) $target = $this->getTargetFile($directory, $name); - if (!@move_uploaded_file($this->getPathname(), $target)) { - $error = error_get_last(); - throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $moved = move_uploaded_file($this->getPathname(), $target); + restore_error_handler(); + if (!$moved) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); } @chmod($target, 0666 & ~umask()); diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 72b07fc94c430..d2ae92c88a48a 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -597,7 +597,7 @@ public static function getTrustedHeaderSet() public static function setTrustedHosts(array $hostPatterns) { self::$trustedHostPatterns = array_map(function ($hostPattern) { - return sprintf('#%s#i', $hostPattern); + return sprintf('{%s}i', $hostPattern); }, $hostPatterns); // we need to reset trusted hosts on trusted host patterns change self::$trustedHosts = array(); @@ -1246,7 +1246,7 @@ public function getRealMethod() * * @param string $format The format * - * @return string The associated mime type (null if not found) + * @return string|null The associated mime type (null if not found) */ public function getMimeType($format) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 5ae3d52cc30b1..43f07f25de1dd 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -674,14 +674,16 @@ private function doAdvisoryLock(string $sessionId) { switch ($this->driver) { case 'mysql': + // MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced. + $lockId = \substr($sessionId, 0, 64); // should we handle the return value? 0 on timeout, null on error // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)'); - $stmt->bindValue(':key', $sessionId, \PDO::PARAM_STR); + $stmt->bindValue(':key', $lockId, \PDO::PARAM_STR); $stmt->execute(); $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)'); - $releaseStmt->bindValue(':key', $sessionId, \PDO::PARAM_STR); + $releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR); return $releaseStmt; case 'pgsql': @@ -833,7 +835,7 @@ private function getUpdateStatement($sessionId, $sessionData, $maxlifetime) /** * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. */ - private function getMergeStatement(string $sessionId, string $data, int$maxlifetime): ?\PDOStatement + private function getMergeStatement(string $sessionId, string $data, int $maxlifetime): ?\PDOStatement { switch (true) { case 'mysql' === $this->driver: diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index ff33797ef591d..0cce07cfaafc1 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -23,6 +23,7 @@ protected function tearDown() { // reset Request::setTrustedProxies(array(), -1); + Request::setTrustedHosts(array()); } public function testInitialize() @@ -1924,9 +1925,15 @@ public function testTrustedHosts() $request->headers->set('host', 'subdomain.trusted.com'); $this->assertEquals('subdomain.trusted.com', $request->getHost()); + } - // reset request for following tests - Request::setTrustedHosts(array()); + public function testSetTrustedHostsDoesNotBreakOnSpecialCharacters() + { + Request::setTrustedHosts(array('localhost(\.local){0,1}#,example.com', 'localhost')); + + $request = Request::create('/'); + $request->headers->set('host', 'localhost'); + $this->assertSame('localhost', $request->getHost()); } public function testFactory() diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php index b35727962eacd..b4af82ddf68de 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php @@ -300,7 +300,7 @@ public function testGetMaxAge() $response = new Response(); $response->headers->set('Cache-Control', 'must-revalidate'); $response->headers->set('Expires', -1); - $this->assertLessThanOrEqual(time() - 2*86400, $response->getExpires()->format('U')); + $this->assertLessThanOrEqual(time() - 2 * 86400, $response->getExpires()->format('U')); $response = new Response(); $this->assertNull($response->getMaxAge(), '->getMaxAge() returns null if no freshness information available'); diff --git a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php index 646adc074bf6a..86683d55de3d1 100644 --- a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php +++ b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -34,7 +34,7 @@ public function createArgumentMetadata($controller) } foreach ($reflection->getParameters() as $param) { - $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull()); + $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $reflection), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull()); } return $arguments; @@ -47,12 +47,25 @@ public function createArgumentMetadata($controller) * * @return null|string */ - private function getType(\ReflectionParameter $parameter) + private function getType(\ReflectionParameter $parameter, \ReflectionFunctionAbstract $function) { if (!$type = $parameter->getType()) { return; } + $name = $type->getName(); + $lcName = strtolower($name); - return $type->getName(); + if ('self' !== $lcName && 'parent' !== $lcName) { + return $name; + } + if (!$function instanceof \ReflectionMethod) { + return; + } + if ('self' === $lcName) { + return $function->getDeclaringClass()->name; + } + if ($parent = $function->getDeclaringClass()->getParentClass()) { + return $parent->name; + } } } diff --git a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php b/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php index 98a397a2e6841..f7cf446e52fae 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php @@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\KernelEvents; @@ -129,12 +130,6 @@ public function onKernelRequest(GetResponseEvent $event) unset($parameters['_route'], $parameters['_controller']); $request->attributes->set('_route_params', $parameters); } catch (ResourceNotFoundException $e) { - if ($this->debug && $e instanceof NoConfigurationException) { - $event->setResponse($this->createWelcomeResponse()); - - return; - } - $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo()); if ($referer = $request->headers->get('referer')) { @@ -149,11 +144,23 @@ public function onKernelRequest(GetResponseEvent $event) } } + public function onKernelException(GetResponseForExceptionEvent $event) + { + if (!$this->debug || !($e = $event->getException()) instanceof NotFoundHttpException) { + return; + } + + if ($e->getPrevious() instanceof NoConfigurationException) { + $event->setResponse($this->createWelcomeResponse()); + } + } + public static function getSubscribedEvents() { return array( KernelEvents::REQUEST => array(array('onKernelRequest', 32)), KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + KernelEvents::EXCEPTION => array('onKernelException', -64), ); } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 24cce648ef443..79e96ffb1abf4 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -63,11 +63,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '4.0.9'; - const VERSION_ID = 40009; + const VERSION = '4.0.10'; + const VERSION_ID = 40010; const MAJOR_VERSION = 4; const MINOR_VERSION = 0; - const RELEASE_VERSION = 9; + const RELEASE_VERSION = 10; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '07/2018'; @@ -457,7 +457,7 @@ protected function initializeContainer() $errorLevel = error_reporting(\E_ALL ^ \E_WARNING); $fresh = $oldContainer = false; try { - if (\is_object($this->container = include $cache->getPath())) { + if (file_exists($cache->getPath()) && \is_object($this->container = include $cache->getPath())) { $this->container->set('kernel', $this); $oldContainer = $this->container; $fresh = true; @@ -520,7 +520,7 @@ protected function initializeContainer() } } - if (null === $oldContainer) { + if (null === $oldContainer && file_exists($cache->getPath())) { $errorLevel = error_reporting(\E_ALL ^ \E_WARNING); try { $oldContainer = include $cache->getPath(); diff --git a/src/Symfony/Component/HttpKernel/Profiler/Profile.php b/src/Symfony/Component/HttpKernel/Profiler/Profile.php index e91a0b0c70e49..cc7efc8487475 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/Profile.php +++ b/src/Symfony/Component/HttpKernel/Profiler/Profile.php @@ -71,7 +71,7 @@ public function getToken() /** * Sets the parent token. */ - public function setParent(Profile $parent) + public function setParent(self $parent) { $this->parent = $parent; } @@ -210,7 +210,7 @@ public function setChildren(array $children) /** * Adds the child token. */ - public function addChild(Profile $child) + public function addChild(self $child) { $this->children[] = $child; $child->setParent($this); diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php index 6f203d3a987c3..1602de4c261f9 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php @@ -13,7 +13,6 @@ use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver; diff --git a/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php b/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php index 2ddc6e7a5ee10..a667705f50813 100644 --- a/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php @@ -117,11 +117,11 @@ public function testNullableTypesSignature() ), $arguments); } - private function signature1(ArgumentMetadataFactoryTest $foo, array $bar, callable $baz) + private function signature1(self $foo, array $bar, callable $baz) { } - private function signature2(ArgumentMetadataFactoryTest $foo = null, FakeClassThatDoesNotExist $bar = null, ImportedAndFake $baz = null) + private function signature2(self $foo = null, FakeClassThatDoesNotExist $bar = null, ImportedAndFake $baz = null) { } diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php index 760366c6943a1..05d8d787aa5c5 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php @@ -6,21 +6,6 @@ class UnprocessableEntityHttpExceptionTest extends HttpExceptionTest { - /** - * Test that setting the headers using the setter function - * is working as expected. - * - * @param array $headers The headers to set - * - * @dataProvider headerDataProvider - */ - public function testHeadersSetter($headers) - { - $exception = new UnprocessableEntityHttpException(10); - $exception->setHeaders($headers); - $this->assertSame($headers, $exception->getHeaders()); - } - protected function createException() { return new UnprocessableEntityHttpException(); diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php index d47287a1fbc69..4dae039c11fc1 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php @@ -6,17 +6,7 @@ class UnsupportedMediaTypeHttpExceptionTest extends HttpExceptionTest { - /** - * @dataProvider headerDataProvider - */ - public function testHeadersSetter($headers) - { - $exception = new UnsupportedMediaTypeHttpException(10); - $exception->setHeaders($headers); - $this->assertSame($headers, $exception->getHeaders()); - } - - protected function createException($headers = array()) + protected function createException() { return new UnsupportedMediaTypeHttpException(); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php b/src/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php index 243c3c5c5a7cb..2d5f0ca5ad59e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php @@ -44,7 +44,7 @@ public function testCollect() public function testReset() { $collector = $this->getMockBuilder(DataCollectorInterface::class) - ->setMethods(['collect', 'getName', 'reset']) + ->setMethods(array('collect', 'getName', 'reset')) ->getMock(); $collector->expects($this->any())->method('getName')->willReturn('mock'); $collector->expects($this->once())->method('reset'); diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index e96c53f978ad8..2088a19c03495 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -20,6 +20,7 @@ "symfony/event-dispatcher": "~3.4|~4.0", "symfony/http-foundation": "~3.4.4|~4.0.4", "symfony/debug": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8", "psr/log": "~1.0" }, "require-dev": { diff --git a/src/Symfony/Component/Inflector/composer.json b/src/Symfony/Component/Inflector/composer.json index ebec32fadd30f..f67d583e64ebb 100644 --- a/src/Symfony/Component/Inflector/composer.json +++ b/src/Symfony/Component/Inflector/composer.json @@ -23,7 +23,8 @@ } ], "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "autoload": { "psr-4": { "Symfony\\Component\\Inflector\\": "" }, diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php index 874a7e290b776..6503e09bfce46 100644 --- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php +++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php @@ -710,6 +710,7 @@ private function round($value, $precision) } elseif (isset(self::$customRoundingList[$roundingModeAttribute])) { $roundingCoef = pow(10, $precision); $value *= $roundingCoef; + $value = (float) (string) $value; switch ($roundingModeAttribute) { case self::ROUND_CEILING: diff --git a/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php b/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php index ace8db70afff5..3f1b29870357e 100644 --- a/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php @@ -428,6 +428,7 @@ public function formatRoundingModeRoundHalfUpProvider() // array(1.125, '1.13'), array(1.127, '1.13'), array(1.129, '1.13'), + array(1020 / 100, '10.20'), ); } @@ -451,6 +452,7 @@ public function formatRoundingModeRoundHalfDownProvider() array(1.125, '1.12'), array(1.127, '1.13'), array(1.129, '1.13'), + array(1020 / 100, '10.20'), ); } @@ -474,6 +476,7 @@ public function formatRoundingModeRoundHalfEvenProvider() array(1.125, '1.12'), array(1.127, '1.13'), array(1.129, '1.13'), + array(1020 / 100, '10.20'), ); } @@ -498,6 +501,7 @@ public function formatRoundingModeRoundCeilingProvider() array(-1.123, '-1.12'), array(-1.125, '-1.12'), array(-1.127, '-1.12'), + array(1020 / 100, '10.20'), ); } @@ -522,6 +526,7 @@ public function formatRoundingModeRoundFloorProvider() array(-1.123, '-1.13'), array(-1.125, '-1.13'), array(-1.127, '-1.13'), + array(1020 / 100, '10.20'), ); } @@ -546,6 +551,7 @@ public function formatRoundingModeRoundDownProvider() array(-1.123, '-1.12'), array(-1.125, '-1.12'), array(-1.127, '-1.12'), + array(1020 / 100, '10.20'), ); } @@ -570,6 +576,7 @@ public function formatRoundingModeRoundUpProvider() array(-1.123, '-1.13'), array(-1.125, '-1.13'), array(-1.127, '-1.13'), + array(1020 / 100, '10.20'), ); } diff --git a/src/Symfony/Component/Ldap/Ldap.php b/src/Symfony/Component/Ldap/Ldap.php index 26fd4f946ccc9..de1ab1a249eda 100644 --- a/src/Symfony/Component/Ldap/Ldap.php +++ b/src/Symfony/Component/Ldap/Ldap.php @@ -70,7 +70,7 @@ public function escape($subject, $ignore = '', $flags = 0) * * @return static */ - public static function create($adapter, array $config = array()): Ldap + public static function create($adapter, array $config = array()): self { if (!isset(self::$adapterMap[$adapter])) { throw new DriverNotFoundException(sprintf( diff --git a/src/Symfony/Component/Lock/Store/FlockStore.php b/src/Symfony/Component/Lock/Store/FlockStore.php index 64438fd461b2e..bf72eaefe2e3c 100644 --- a/src/Symfony/Component/Lock/Store/FlockStore.php +++ b/src/Symfony/Component/Lock/Store/FlockStore.php @@ -78,8 +78,7 @@ private function lock(Key $key, $blocking) ); // Silence error reporting - set_error_handler(function () { - }); + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); if (!$handle = fopen($fileName, 'r')) { if ($handle = fopen($fileName, 'x')) { chmod($fileName, 0444); @@ -91,8 +90,7 @@ private function lock(Key $key, $blocking) restore_error_handler(); if (!$handle) { - $error = error_get_last(); - throw new LockStorageException($error['message'], 0, null); + throw new LockStorageException($error, 0, null); } // On Windows, even if PHP doc says the contrary, LOCK_NB works, see diff --git a/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php b/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php index 2fe15cd921bf9..1e3436eb5a094 100644 --- a/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php +++ b/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php @@ -31,6 +31,7 @@ abstract protected function getStore(); * This test is time sensible: the $clockDelay could be adjust. * * @requires extension pcntl + * @requires extension posix * @requires function pcntl_sigwaitinfo */ public function testBlockingLocks() diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index 8edd6b5c89791..68b4154b10da2 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -975,7 +975,7 @@ public function count() * parameters should usually not be included in messages aimed at * non-technical people. * - * @param mixed $value The value to return the type of + * @param mixed $value The value to return the type of */ private function formatTypeOf($value, ?string $type): string { diff --git a/src/Symfony/Component/Process/ExecutableFinder.php b/src/Symfony/Component/Process/ExecutableFinder.php index d042a5b13a211..1ec6526d45efd 100644 --- a/src/Symfony/Component/Process/ExecutableFinder.php +++ b/src/Symfony/Component/Process/ExecutableFinder.php @@ -77,7 +77,7 @@ public function find($name, $default = null, array $extraDirs = array()) } foreach ($suffixes as $suffix) { foreach ($dirs as $dir) { - if (@is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || is_executable($file))) { + if (@is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || @is_executable($file))) { return $file; } } diff --git a/src/Symfony/Component/Process/PhpExecutableFinder.php b/src/Symfony/Component/Process/PhpExecutableFinder.php index d2b6d5e321dcf..f632a89b4acb1 100644 --- a/src/Symfony/Component/Process/PhpExecutableFinder.php +++ b/src/Symfony/Component/Process/PhpExecutableFinder.php @@ -44,7 +44,7 @@ public function find($includeArgs = true) } if ($php = getenv('PHP_PATH')) { - if (!is_executable($php)) { + if (!@is_executable($php)) { return false; } @@ -52,12 +52,12 @@ public function find($includeArgs = true) } if ($php = getenv('PHP_PEAR_PHP_BIN')) { - if (is_executable($php)) { + if (@is_executable($php)) { return $php; } } - if (is_executable($php = PHP_BINDIR.('\\' === DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) { + if (@is_executable($php = PHP_BINDIR.('\\' === DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) { return $php; } diff --git a/src/Symfony/Component/Process/Pipes/AbstractPipes.php b/src/Symfony/Component/Process/Pipes/AbstractPipes.php index 2bd1fe75b7ff9..9a7d88be896f4 100644 --- a/src/Symfony/Component/Process/Pipes/AbstractPipes.php +++ b/src/Symfony/Component/Process/Pipes/AbstractPipes.php @@ -25,6 +25,7 @@ abstract class AbstractPipes implements PipesInterface private $inputBuffer = ''; private $input; private $blocked = true; + private $lastError; /** * @param resource|string|int|float|bool|\Iterator|null $input @@ -58,10 +59,11 @@ public function close() */ protected function hasSystemCallBeenInterrupted() { - $lastError = error_get_last(); + $lastError = $this->lastError; + $this->lastError = null; // stream_select returns false when the `select` system call is interrupted by an incoming signal - return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); } /** @@ -165,4 +167,12 @@ protected function write() return array($this->pipes[0]); } } + + /** + * @internal + */ + public function handleError($type, $msg) + { + $this->lastError = $msg; + } } diff --git a/src/Symfony/Component/Process/Pipes/UnixPipes.php b/src/Symfony/Component/Process/Pipes/UnixPipes.php index badaf34d42012..23f873471d874 100644 --- a/src/Symfony/Component/Process/Pipes/UnixPipes.php +++ b/src/Symfony/Component/Process/Pipes/UnixPipes.php @@ -99,7 +99,9 @@ public function readAndWrite($blocking, $close = false) unset($r[0]); // let's have a look if something changed in streams - if (($r || $w) && false === @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + set_error_handler(array($this, 'handleError')); + if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + restore_error_handler(); // if a system call has been interrupted, forget about it, let's try again // otherwise, an error occurred, let's reset pipes if (!$this->hasSystemCallBeenInterrupted()) { @@ -108,6 +110,7 @@ public function readAndWrite($blocking, $close = false) return $read; } + restore_error_handler(); foreach ($r as $pipe) { // prior PHP 5.4 the array passed to stream_select is modified and diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 1768dda885421..40a2ae047e812 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -588,7 +588,7 @@ private function writeCollection($zval, $property, $collection, $addMethod, $rem /** * Guesses how to write the property value. * - * @param mixed $value + * @param mixed $value */ private function getWriteAccessInfo(string $class, string $property, $value): array { diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index ca26c69c9c2e0..b0713aa93fe38 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -153,7 +153,7 @@ private function extractFromMutator(string $class, string $property): ?array if (!$reflectionType = $reflectionParameter->getType()) { return null; } - $type = $this->extractFromReflectionType($reflectionType); + $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod); if (\in_array($prefix, $this->arrayMutatorPrefixes)) { $type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type); @@ -175,7 +175,7 @@ private function extractFromAccessor(string $class, string $property): ?array } if ($reflectionType = $reflectionMethod->getReturnType()) { - return array($this->extractFromReflectionType($reflectionType)); + return array($this->extractFromReflectionType($reflectionType, $reflectionMethod)); } if (\in_array($prefix, array('is', 'can'))) { @@ -185,7 +185,7 @@ private function extractFromAccessor(string $class, string $property): ?array return null; } - private function extractFromReflectionType(\ReflectionType $reflectionType): Type + private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionMethod $reflectionMethod): Type { $phpTypeOrClass = $reflectionType->getName(); $nullable = $reflectionType->allowsNull(); @@ -197,12 +197,24 @@ private function extractFromReflectionType(\ReflectionType $reflectionType): Typ } elseif ($reflectionType->isBuiltin()) { $type = new Type($phpTypeOrClass, $nullable); } else { - $type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $phpTypeOrClass); + $type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflectionMethod)); } return $type; } + private function resolveTypeName(string $name, \ReflectionMethod $reflectionMethod): string + { + if ('self' === $lcName = strtolower($name)) { + return $reflectionMethod->getDeclaringClass()->name; + } + if ('parent' === $lcName && $parent = $reflectionMethod->getDeclaringClass()->getParentClass()) { + return $parent->name; + } + + return $name; + } + private function isPublicProperty(string $class, string $property): bool { try { diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php similarity index 99% rename from src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php rename to src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index dab463f7e5105..647d4c1072e64 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\PropertyInfo\Tests\PhpDocExtractors; +namespace Symfony\Component\PropertyInfo\Tests\PhpDocExtractor; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php similarity index 93% rename from src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php rename to src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index d73c6ec247bae..79aa75467660b 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -52,6 +52,8 @@ public function testGetProperties() 'DOB', 'Id', '123', + 'self', + 'realParent', 'c', 'd', 'e', @@ -135,6 +137,8 @@ public function typesProvider() array('donotexist', null), array('staticGetter', null), array('staticSetter', null), + array('self', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy'))), + array('realParent', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy'))), ); } @@ -152,6 +156,8 @@ public function php7TypesProvider() array('foo', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true))), array('bar', array(new Type(Type::BUILTIN_TYPE_INT))), array('baz', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))), + array('buz', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Php7Dummy'))), + array('biz', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'stdClass'))), array('donotexist', null), ); } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/SerializerExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php similarity index 95% rename from src/Symfony/Component/PropertyInfo/Tests/Extractors/SerializerExtractorTest.php rename to src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php index b1be5ee11821e..91cdf80f8d608 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/SerializerExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\PropertyInfo\Tests\Extractors; +namespace Symfony\Component\PropertyInfo\Tests\Extractor; use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php index 4e558eca014e5..5993d6e1d47e8 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php @@ -127,4 +127,18 @@ public function getId() public function get123() { } + + /** + * @param self $self + */ + public function setSelf(self $self) + { + } + + /** + * @param parent $realParent + */ + public function setRealParent(parent $realParent) + { + } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php index 330496827cfc4..8d5c4fe107162 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php @@ -29,7 +29,7 @@ class ParentDummy public $foo2; /** - * @var callback + * @var callable */ public $foo3; diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php7Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php7Dummy.php index cd5ba380d9d53..5dcb4c565e768 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php7Dummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php7Dummy.php @@ -14,7 +14,7 @@ /** * @author Kévin Dunglas */ -class Php7Dummy +class Php7Dummy extends \stdClass { public function getFoo(): array { @@ -27,4 +27,12 @@ public function setBar(int $bar) public function addBaz(string $baz) { } + + public function getBuz(): self + { + } + + public function getBiz(): parent + { + } } diff --git a/src/Symfony/Component/PropertyInfo/Type.php b/src/Symfony/Component/PropertyInfo/Type.php index 71aa162f70b79..461495ed510ac 100644 --- a/src/Symfony/Component/PropertyInfo/Type.php +++ b/src/Symfony/Component/PropertyInfo/Type.php @@ -59,7 +59,7 @@ class Type /** * @throws \InvalidArgumentException */ - public function __construct(string $builtinType, bool $nullable = false, string $class = null, bool $collection = false, Type $collectionKeyType = null, Type $collectionValueType = null) + public function __construct(string $builtinType, bool $nullable = false, string $class = null, bool $collection = false, self $collectionKeyType = null, self $collectionValueType = null) { if (!in_array($builtinType, self::$builtinTypes)) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid PHP type.', $builtinType)); diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php index 6916297b8c174..dd057d2ee8301 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php @@ -106,7 +106,7 @@ protected function getParent() /** * Sets the parent collection. */ - protected function setParent(DumperCollection $parent) + protected function setParent(self $parent) { $this->parent = $parent; } diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index d63c6138f7983..c98d642a06650 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -118,7 +118,7 @@ public function createBuilder() * @param string $prefix * @param RouteCollectionBuilder $builder */ - public function mount($prefix, RouteCollectionBuilder $builder) + public function mount($prefix, self $builder) { $builder->prefix = trim(trim($prefix), '/'); $this->routes[] = $builder; @@ -251,11 +251,9 @@ public function setMethods($methods) /** * Adds a resource for this collection. * - * @param ResourceInterface $resource - * * @return $this */ - private function addResource(ResourceInterface $resource): RouteCollectionBuilder + private function addResource(ResourceInterface $resource): self { $this->resources[] = $resource; diff --git a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php index c629ca15255ff..306e19ad91bb9 100644 --- a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php +++ b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Security\Csrf\Tests\TokenStorage; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; /** @@ -22,7 +24,7 @@ class SessionTokenStorageTest extends TestCase const SESSION_NAMESPACE = 'foobar'; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var Session */ private $session; @@ -33,118 +35,53 @@ class SessionTokenStorageTest extends TestCase protected function setUp() { - $this->session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface') - ->disableOriginalConstructor() - ->getMock(); + $this->session = new Session(new MockArraySessionStorage()); $this->storage = new SessionTokenStorage($this->session, self::SESSION_NAMESPACE); } - public function testStoreTokenInClosedSession() + public function testStoreTokenInNotStartedSessionStartsTheSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(false)); - - $this->session->expects($this->once()) - ->method('start'); - - $this->session->expects($this->once()) - ->method('set') - ->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN'); - $this->storage->setToken('token_id', 'TOKEN'); + + $this->assertTrue($this->session->isStarted()); } public function testStoreTokenInActiveSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(true)); - - $this->session->expects($this->never()) - ->method('start'); - - $this->session->expects($this->once()) - ->method('set') - ->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN'); - + $this->session->start(); $this->storage->setToken('token_id', 'TOKEN'); + + $this->assertSame('TOKEN', $this->session->get(self::SESSION_NAMESPACE.'/token_id')); } public function testCheckTokenInClosedSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(false)); - - $this->session->expects($this->once()) - ->method('start'); + $this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT'); - $this->session->expects($this->once()) - ->method('has') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue('RESULT')); - - $this->assertSame('RESULT', $this->storage->hasToken('token_id')); + $this->assertTrue($this->storage->hasToken('token_id')); + $this->assertTrue($this->session->isStarted()); } public function testCheckTokenInActiveSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(true)); - - $this->session->expects($this->never()) - ->method('start'); + $this->session->start(); + $this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT'); - $this->session->expects($this->once()) - ->method('has') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue('RESULT')); - - $this->assertSame('RESULT', $this->storage->hasToken('token_id')); + $this->assertTrue($this->storage->hasToken('token_id')); } public function testGetExistingTokenFromClosedSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(false)); - - $this->session->expects($this->once()) - ->method('start'); - - $this->session->expects($this->once()) - ->method('has') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue(true)); - - $this->session->expects($this->once()) - ->method('get') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue('RESULT')); + $this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT'); $this->assertSame('RESULT', $this->storage->getToken('token_id')); + $this->assertTrue($this->session->isStarted()); } public function testGetExistingTokenFromActiveSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(true)); - - $this->session->expects($this->never()) - ->method('start'); - - $this->session->expects($this->once()) - ->method('has') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue(true)); - - $this->session->expects($this->once()) - ->method('get') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue('RESULT')); + $this->session->start(); + $this->session->set(self::SESSION_NAMESPACE.'/token_id', 'RESULT'); $this->assertSame('RESULT', $this->storage->getToken('token_id')); } @@ -154,18 +91,6 @@ public function testGetExistingTokenFromActiveSession() */ public function testGetNonExistingTokenFromClosedSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(false)); - - $this->session->expects($this->once()) - ->method('start'); - - $this->session->expects($this->once()) - ->method('has') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue(false)); - $this->storage->getToken('token_id'); } @@ -174,85 +99,33 @@ public function testGetNonExistingTokenFromClosedSession() */ public function testGetNonExistingTokenFromActiveSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(true)); - - $this->session->expects($this->never()) - ->method('start'); - - $this->session->expects($this->once()) - ->method('has') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue(false)); - + $this->session->start(); $this->storage->getToken('token_id'); } public function testRemoveNonExistingTokenFromClosedSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(false)); - - $this->session->expects($this->once()) - ->method('start'); - - $this->session->expects($this->once()) - ->method('remove') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue(null)); - $this->assertNull($this->storage->removeToken('token_id')); } public function testRemoveNonExistingTokenFromActiveSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(true)); - - $this->session->expects($this->never()) - ->method('start'); - - $this->session->expects($this->once()) - ->method('remove') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue(null)); + $this->session->start(); $this->assertNull($this->storage->removeToken('token_id')); } public function testRemoveExistingTokenFromClosedSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(false)); - - $this->session->expects($this->once()) - ->method('start'); - - $this->session->expects($this->once()) - ->method('remove') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue('TOKEN')); + $this->session->set(self::SESSION_NAMESPACE.'/token_id', 'TOKEN'); $this->assertSame('TOKEN', $this->storage->removeToken('token_id')); } public function testRemoveExistingTokenFromActiveSession() { - $this->session->expects($this->any()) - ->method('isStarted') - ->will($this->returnValue(true)); - - $this->session->expects($this->never()) - ->method('start'); - - $this->session->expects($this->once()) - ->method('remove') - ->with(self::SESSION_NAMESPACE.'/token_id') - ->will($this->returnValue('TOKEN')); + $this->session->start(); + $this->session->set(self::SESSION_NAMESPACE.'/token_id', 'TOKEN'); $this->assertSame('TOKEN', $this->storage->removeToken('token_id')); } diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index 80ae75caa2b85..b9859452b1585 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -14,7 +14,6 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; use Symfony\Component\Security\Guard\AuthenticatorInterface; use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; diff --git a/src/Symfony/Component/Security/Http/Firewall.php b/src/Symfony/Component/Security/Http/Firewall.php index 28b56d5791bd4..9bee596759bd8 100644 --- a/src/Symfony/Component/Security/Http/Firewall.php +++ b/src/Symfony/Component/Security/Http/Firewall.php @@ -47,13 +47,22 @@ public function onKernelRequest(GetResponseEvent $event) } // register listeners for this firewall - list($listeners, $exceptionListener) = $this->map->getListeners($event->getRequest()); + $listeners = $this->map->getListeners($event->getRequest()); + + $authenticationListeners = $listeners[0]; + $exceptionListener = $listeners[1]; + $logoutListener = isset($listeners[2]) ? $listeners[2] : null; + if (null !== $exceptionListener) { $this->exceptionListeners[$event->getRequest()] = $exceptionListener; $exceptionListener->register($this->dispatcher); } - return $this->handleRequest($event, $listeners); + $this->handleRequest($event, $authenticationListeners); + + if (null !== $logoutListener) { + $logoutListener->handle($event); + } } public function onKernelFinishRequest(FinishRequestEvent $event) diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index fb8c7a1761cbc..6486f0eaf6085 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -45,8 +45,7 @@ class ContextListener implements ListenerInterface private $trustResolver; /** - * @param TokenStorageInterface $tokenStorage - * @param iterable|UserProviderInterface[] $userProviders + * @param iterable|UserProviderInterface[] $userProviders */ public function __construct(TokenStorageInterface $tokenStorage, iterable $userProviders, string $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, AuthenticationTrustResolverInterface $trustResolver = null) { diff --git a/src/Symfony/Component/Security/Http/FirewallMap.php b/src/Symfony/Component/Security/Http/FirewallMap.php index e767d123cb03e..fc97410d4e698 100644 --- a/src/Symfony/Component/Security/Http/FirewallMap.php +++ b/src/Symfony/Component/Security/Http/FirewallMap.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpFoundation\RequestMatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\LogoutListener; /** * FirewallMap allows configuration of different firewalls for specific parts @@ -25,9 +26,9 @@ class FirewallMap implements FirewallMapInterface { private $map = array(); - public function add(RequestMatcherInterface $requestMatcher = null, array $listeners = array(), ExceptionListener $exceptionListener = null) + public function add(RequestMatcherInterface $requestMatcher = null, array $listeners = array(), ExceptionListener $exceptionListener = null, LogoutListener $logoutListener = null) { - $this->map[] = array($requestMatcher, $listeners, $exceptionListener); + $this->map[] = array($requestMatcher, $listeners, $exceptionListener, $logoutListener); } /** @@ -37,10 +38,10 @@ public function getListeners(Request $request) { foreach ($this->map as $elements) { if (null === $elements[0] || $elements[0]->matches($request)) { - return array($elements[1], $elements[2]); + return array($elements[1], $elements[2], $elements[3]); } } - return array(array(), null); + return array(array(), null, null); } } diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php index 944a7b3133f35..d9a15d5ac0de5 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php @@ -60,5 +60,5 @@ public function getMaxDepth(); /** * Merges an {@see AttributeMetadataInterface} with in the current one. */ - public function merge(AttributeMetadataInterface $attributeMetadata); + public function merge(self $attributeMetadata); } diff --git a/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php index 3811e56548a0c..a0765861643bb 100644 --- a/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php @@ -46,7 +46,7 @@ public function getAttributesMetadata(); /** * Merges a {@link ClassMetadataInterface} in the current one. */ - public function merge(ClassMetadataInterface $classMetadata); + public function merge(self $classMetadata); /** * Returns a {@link \ReflectionClass} instance for this class. diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index ecf60dc8884a2..e2b15d1aa1c41 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -228,7 +228,7 @@ abstract protected function setAttributeValue($object, $attribute, $value, $form /** * Validates the submitted data and denormalizes it. * - * @param mixed $data + * @param mixed $data * * @return mixed * @@ -297,7 +297,7 @@ private function validateAndDenormalize(string $currentClass, string $attribute, /** * Sets an attribute and apply the name converter if necessary. * - * @param mixed $attributeValue + * @param mixed $attributeValue */ private function updateData(array $data, string $attribute, $attributeValue): array { diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index 578e29f2bd459..98383a666b31a 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -16,7 +16,8 @@ } ], "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "require-dev": { "symfony/yaml": "~3.4|~4.0", diff --git a/src/Symfony/Component/Templating/composer.json b/src/Symfony/Component/Templating/composer.json index 23111a86a781b..8379210989a59 100644 --- a/src/Symfony/Component/Templating/composer.json +++ b/src/Symfony/Component/Templating/composer.json @@ -16,7 +16,8 @@ } ], "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "require-dev": { "psr/log": "~1.0" diff --git a/src/Symfony/Component/Translation/Command/XliffLintCommand.php b/src/Symfony/Component/Translation/Command/XliffLintCommand.php index 042ab1eab8b32..1d310371a3c05 100644 --- a/src/Symfony/Component/Translation/Command/XliffLintCommand.php +++ b/src/Symfony/Component/Translation/Command/XliffLintCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Translation\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -81,14 +82,14 @@ protected function execute(InputInterface $input, OutputInterface $output) if (!$filename) { if (!$stdin = $this->getStdin()) { - throw new \RuntimeException('Please provide a filename or pipe file content to STDIN.'); + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); } return $this->display($io, array($this->validate($stdin))); } if (!$this->isReadable($filename)) { - throw new \RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); } $filesInfo = array(); @@ -136,7 +137,7 @@ private function display(SymfonyStyle $io, array $files) case 'json': return $this->displayJson($io, $files); default: - throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); + throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); } } diff --git a/src/Symfony/Component/Translation/MessageCatalogueInterface.php b/src/Symfony/Component/Translation/MessageCatalogueInterface.php index 4dad27fbf6aa3..e0dbb2bd962cb 100644 --- a/src/Symfony/Component/Translation/MessageCatalogueInterface.php +++ b/src/Symfony/Component/Translation/MessageCatalogueInterface.php @@ -105,7 +105,7 @@ public function add($messages, $domain = 'messages'); * * The two catalogues must have the same locale. */ - public function addCatalogue(MessageCatalogueInterface $catalogue); + public function addCatalogue(self $catalogue); /** * Merges translations from the given Catalogue into the current one @@ -113,7 +113,7 @@ public function addCatalogue(MessageCatalogueInterface $catalogue); * * This is used to provide default translations when they do not exist for the current locale. */ - public function addFallbackCatalogue(MessageCatalogueInterface $catalogue); + public function addFallbackCatalogue(self $catalogue); /** * Gets the fallback catalogue. diff --git a/src/Symfony/Component/Translation/PluralizationRules.php b/src/Symfony/Component/Translation/PluralizationRules.php index 38dde743acc4f..48a6c608cbfe2 100644 --- a/src/Symfony/Component/Translation/PluralizationRules.php +++ b/src/Symfony/Component/Translation/PluralizationRules.php @@ -107,6 +107,7 @@ public static function get($number, $locale) case 'nl': case 'nn': case 'no': + case 'oc': case 'om': case 'or': case 'pa': diff --git a/src/Symfony/Component/Validator/ConstraintViolationListInterface.php b/src/Symfony/Component/Validator/ConstraintViolationListInterface.php index 0489ab500a1dc..47e986f51e50a 100644 --- a/src/Symfony/Component/Validator/ConstraintViolationListInterface.php +++ b/src/Symfony/Component/Validator/ConstraintViolationListInterface.php @@ -26,7 +26,7 @@ public function add(ConstraintViolationInterface $violation); /** * Merges an existing violation list into this list. */ - public function addAll(ConstraintViolationListInterface $otherList); + public function addAll(self $otherList); /** * Returns the violation at a given offset. diff --git a/src/Symfony/Component/Validator/Constraints/UrlValidator.php b/src/Symfony/Component/Validator/Constraints/UrlValidator.php index 932aa14436fbc..eb244de1fe3a4 100644 --- a/src/Symfony/Component/Validator/Constraints/UrlValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UrlValidator.php @@ -86,7 +86,7 @@ public function validate($value, Constraint $constraint) Url::CHECK_DNS_TYPE_SOA, Url::CHECK_DNS_TYPE_SRV, Url::CHECK_DNS_TYPE_TXT, - ))) { + ), true)) { throw new InvalidOptionsException(sprintf('Invalid value for option "checkDNS" in constraint %s', get_class($constraint)), array('checkDNS')); } diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php index dd0dd1fa46a97..d7773861445fb 100644 --- a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php @@ -330,7 +330,7 @@ public function addGetterMethodConstraints($property, $method, array $constraint /** * Merges the constraints of the given metadata into this object. */ - public function mergeConstraints(ClassMetadata $source) + public function mergeConstraints(self $source) { if ($source->isGroupSequenceProvider()) { $this->setGroupSequenceProvider(true); diff --git a/src/Symfony/Component/Validator/ObjectInitializerInterface.php b/src/Symfony/Component/Validator/ObjectInitializerInterface.php index dcbc2cd11dbdd..1bd55f0cda8e7 100644 --- a/src/Symfony/Component/Validator/ObjectInitializerInterface.php +++ b/src/Symfony/Component/Validator/ObjectInitializerInterface.php @@ -14,7 +14,7 @@ /** * Prepares an object for validation. * - * Concrete implementations of this interface are used by {@link ValidationVisitorInterface} + * Concrete implementations of this interface are used by {@link Validator\ContextualValidatorInterface} * to initialize objects just before validating them. * * @author Fabien Potencier diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index 216b72416e83f..36da9d2f560b0 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", "symfony/translation": "~3.4|~4.0" }, diff --git a/src/Symfony/Component/Yaml/Command/LintCommand.php b/src/Symfony/Component/Yaml/Command/LintCommand.php index 04ead2584b195..8e3a848832e8d 100644 --- a/src/Symfony/Component/Yaml/Command/LintCommand.php +++ b/src/Symfony/Component/Yaml/Command/LintCommand.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Yaml\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -86,14 +88,14 @@ protected function execute(InputInterface $input, OutputInterface $output) if (!$filename) { if (!$stdin = $this->getStdin()) { - throw new \RuntimeException('Please provide a filename or pipe file content to STDIN.'); + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); } return $this->display($io, array($this->validate($stdin, $flags))); } if (!$this->isReadable($filename)) { - throw new \RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); } $filesInfo = array(); @@ -133,7 +135,7 @@ private function display(SymfonyStyle $io, array $files) case 'json': return $this->displayJson($io, $files); default: - throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); + throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); } } diff --git a/src/Symfony/Component/Yaml/composer.json b/src/Symfony/Component/Yaml/composer.json index 7a34d828571aa..1b8bd0446d401 100644 --- a/src/Symfony/Component/Yaml/composer.json +++ b/src/Symfony/Component/Yaml/composer.json @@ -16,7 +16,8 @@ } ], "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "require-dev": { "symfony/console": "~3.4|~4.0"