diff --git a/CHANGELOG-4.0.md b/CHANGELOG-4.0.md index f1c98b1874d2a..2a4c1db371283 100644 --- a/CHANGELOG-4.0.md +++ b/CHANGELOG-4.0.md @@ -7,6 +7,35 @@ 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.1 (2017-12-05) + + * bug #25304 [Bridge/PhpUnit] Prefer $_SERVER['argv'] over $argv (ricknox) + * bug #25272 [SecurityBundle] fix setLogoutOnUserChange calls for context listeners (dmaicher) + * bug #25282 [DI] Register singly-implemented interfaces when doing PSR-4 discovery (nicolas-grekas) + * bug #25274 [Security] Adding a GuardAuthenticatorHandler alias (weaverryan) + * bug #25308 [FrameworkBundle] Fix a bug where a color tag will be shown when passing an antislash (Simperfit) + * bug #25278 Fix for missing whitespace control modifier in form layout (kubawerlos) + * bug #25306 [Form][TwigBridge] Fix collision between view properties and form fields (yceruto) + * bug #25305 [Form][TwigBridge] Fix collision between view properties and form fields (yceruto) + * bug #25236 [Form][TwigBridge] Fix collision between view properties and form fields (yceruto) + * bug #25312 [DI] Fix deep-inlining of non-shared refs (nicolas-grekas) + * bug #25309 [Yaml] parse newlines in quoted multiline strings (xabbuh) + * bug #25313 [DI] Fix missing unset leading to false-positive circular ref (nicolas-grekas) + * bug #25268 [DI] turn $private to protected in dumped container, to make cache:clear BC (nicolas-grekas) + * bug #25285 [DI] Throw an exception if Expression Language is not installed (sroze) + * bug #25241 [Yaml] do not eagerly filter comment lines (xabbuh) + * bug #25284 [DI] Cast ids to string, as done on 3.4 (nicolas-grekas, sroze) + * bug #25297 [Validator] Fixed the @Valid(groups={"group"}) against null exception case (vudaltsov) + * bug #25255 [Console][DI] Fail gracefully (nicolas-grekas) + * bug #25264 [DI] Trigger deprecation when setting a to-be-private synthetic service (nicolas-grekas) + * bug #25258 [link] Prevent warnings when running link with 2.7 (dunglas) + * bug #25244 [DI] Add missing deprecation when fetching private services from ContainerBuilder (nicolas-grekas) + * bug #24750 [Validator] ExpressionValidator should use OBJECT_TO_STRING (Simperfit) + * bug #25247 [DI] Fix false-positive circular exception (nicolas-grekas) + * bug #25226 [HttpKernel] Fix issue when resetting DumpDataCollector (Pierstoval) + * bug #25230 Use a more specific file for detecting the bridge (greg0ire) + * bug #25232 [WebProfilerBundle] [TwigBundle] Fix Profiler breaking XHTML pages (tistre) + * 4.0.0 (2017-11-30) * bug #25220 [HttpFoundation] Add Session::isEmpty(), fix MockFileSessionStorage to behave like the native one (nicolas-grekas) diff --git a/link b/link index f4070998e72b5..a5030d0683379 100755 --- a/link +++ b/link @@ -35,11 +35,14 @@ if (!is_dir("$argv[1]/vendor/symfony")) { } $sfPackages = array('symfony/symfony' => __DIR__); + +$filesystem = new Filesystem(); foreach (glob(__DIR__.'/src/Symfony/{Bundle,Bridge,Component,Component/Security}/*', GLOB_BRACE | GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { - $sfPackages[json_decode(file_get_contents("$dir/composer.json"))->name] = $dir; + if ($filesystem->exists($composer = "$dir/composer.json")) { + $sfPackages[json_decode(file_get_contents($composer))->name] = $dir; + } } -$filesystem = new Filesystem(); foreach (glob("$argv[1]/vendor/symfony/*", GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { $package = 'symfony/'.basename($dir); if (is_link($dir)) { @@ -57,3 +60,7 @@ foreach (glob("$argv[1]/vendor/symfony/*", GLOB_ONLYDIR | GLOB_NOSORT) as $dir) $filesystem->symlink($sfDir, $dir); echo "\"$package\" has been linked to \"$sfPackages[$package]\".".PHP_EOL; } + +foreach (glob("$argv[1]/var/cache/*") as $cacheDir) { + $filesystem->remove($cacheDir); +} diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index 562d018124a37..edf11e7a3ef38 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -27,7 +27,7 @@ if (PHP_VERSION_ID >= 70200) { } $root = __DIR__; -while (!file_exists($root.'/composer.json') || file_exists($root.'/bin/simple-phpunit')) { +while (!file_exists($root.'/composer.json') || file_exists($root.'/DeprecationErrorHandler.php')) { if ($root === dirname($root)) { break; } @@ -59,7 +59,12 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? '(del /S /F /Q %s & rmdir %1$s) >nul': 'rm -rf %s', "phpunit-$PHPUNIT_VERSION")); } if (extension_loaded('openssl') && ini_get('allow_url_fopen') && !isset($_SERVER['http_proxy']) && !isset($_SERVER['https_proxy'])) { - stream_copy_to_stream(fopen("https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip", 'rb'), fopen("$PHPUNIT_VERSION.zip", 'wb')); + $remoteZip = "https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip"; + $remoteZipStream = @fopen($remoteZip, 'rb'); + if (!$remoteZipStream) { + throw new \RuntimeException("Could not find $remoteZip"); + } + stream_copy_to_stream($remoteZipStream, fopen("$PHPUNIT_VERSION.zip", 'wb')); } else { @unlink("$PHPUNIT_VERSION.zip"); passthru("wget https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip"); @@ -119,6 +124,9 @@ EOPHP } +global $argv, $argc; +$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array(); +$argc = isset($_SERVER['argc']) ? $_SERVER['argc'] : 0; $components = array(); $cmd = array_map('escapeshellarg', $argv); $exit = 0; diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index 4ca968bca9a8c..e2ddbaf47fdaf 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -13,6 +13,7 @@ use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; use Symfony\Component\Form\ChoiceList\View\ChoiceView; +use Symfony\Component\Form\FormView; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use Twig\TwigFunction; @@ -72,6 +73,7 @@ public function getTests() { return array( new TwigTest('selectedchoice', 'Symfony\Bridge\Twig\Extension\twig_is_selected_choice'), + new TwigTest('rootform', 'Symfony\Bridge\Twig\Extension\twig_is_root_form'), ); } @@ -103,3 +105,11 @@ function twig_is_selected_choice(ChoiceView $choice, $selectedValue) return $choice->value === $selectedValue; } + +/** + * @internal + */ +function twig_is_root_form(FormView $formView) +{ + return null === $formView->parent; +} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig index 64fc2a210ee3e..0e198aa517cae 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig @@ -140,12 +140,12 @@ {% block form_errors -%} {% if errors|length > 0 -%} - {% if form.parent %}{% else %}
{% endif %} + {% if form is not rootform %}{% else %}
{% endif %}
    {%- for error in errors -%}
  • {{ error.message }}
  • {%- endfor -%}
- {% if form.parent %}{% else %}
{% endif %} + {% if form is not rootform %}
{% else %}
{% endif %} {%- endif %} {%- endblock form_errors %} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig index 96ca4be4d68a7..4e4e9facc3a22 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig @@ -173,7 +173,7 @@ {% block form_errors -%} {%- if errors|length > 0 -%} -
+
    {%- for error in errors -%}
  • {{ error.message }}
  • diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 52525c061ccba..2a7d58c0f878e 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -15,7 +15,7 @@ {%- block form_widget_compound -%}
    - {%- if form.parent is empty -%} + {%- if form is rootform -%} {{ form_errors(form) }} {%- endif -%} {{- block('form_rows') -}} @@ -347,9 +347,9 @@ {% if not child.rendered %} {{- form_row(child) -}} {% endif %} - {%- endfor %} + {%- endfor -%} - {% if not form.methodRendered and form.parent is null %} + {% if not form.methodRendered and form is rootform %} {%- do form.setMethodRendered() -%} {% set method = method|upper %} {%- if method in ["GET", "POST"] -%} @@ -361,7 +361,7 @@ {%- if form_method != method -%} {%- endif -%} - {% endif %} + {% endif -%} {% endblock form_rest %} {# Support #} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig index c7b3a4365b51b..39274c6c8d058 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig @@ -31,7 +31,7 @@ {%- block form_widget_compound -%} - {%- if form.parent is empty and errors|length > 0 -%} + {%- if form is rootform and errors|length > 0 -%}
    {{- form_errors(form) -}} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig index d34161ad0c669..3035689cc9dff 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig @@ -318,11 +318,11 @@ {% block form_errors -%} {% if errors|length > 0 -%} - {% if form.parent %}{% else %}
    {% endif %} + {% if form is not rootform %}{% else %}
    {% endif %} {%- for error in errors -%} {{ error.message }} {% if not loop.last %}, {% endif %} {%- endfor -%} - {% if form.parent %}{% else %}
    {% endif %} + {% if form is not rootform %}
    {% else %}
    {% endif %} {%- endif %} {%- endblock form_errors %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index 75d6f1e0504b8..22a1413f38cbc 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -149,6 +149,22 @@ public function testStartTagHasActionAttributeWhenActionIsZero() $this->assertSame('
    ', $html); } + public function isRootFormProvider() + { + return array( + array(true, new FormView()), + array(false, new FormView(new FormView())), + ); + } + + /** + * @dataProvider isRootFormProvider + */ + public function testIsRootForm($expected, FormView $formView) + { + $this->assertSame($expected, \Symfony\Bridge\Twig\Extension\twig_is_root_form($formView)); + } + protected function renderForm(FormView $view, array $vars = array()) { return (string) $this->renderer->renderBlock($view, 'form', $vars); diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 94bcd323960bc..98977ab8a6ec7 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -24,7 +24,7 @@ "symfony/asset": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", - "symfony/form": "~3.4-beta4|~4.0-beta4", + "symfony/form": "~3.4|~4.0", "symfony/http-foundation": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", "symfony/polyfill-intl-icu": "~1.0", diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php index 64fcc5c8157a1..3c352b4c051a2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php @@ -90,7 +90,7 @@ protected function execute(InputInterface $input, OutputInterface $output) array('Xdebug', extension_loaded('xdebug') ? 'true' : 'false'), ); - if ($dotenv = self::getDotEnvVars()) { + if ($dotenv = self::getDotenvVars()) { $rows = array_merge($rows, array( new TableSeparator(), array('Environment (.env)'), @@ -129,7 +129,7 @@ private static function isExpired(string $date): bool return false !== $date && new \DateTime() > $date->modify('last day of this month 23:59:59'); } - private static function getDotEnvVars(): array + private static function getDotenvVars(): array { $vars = array(); foreach (explode(',', getenv('SYMFONY_DOTENV_VARS')) as $name) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index e4bb3f66a7516..63513bfa430b5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; +use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Alias; @@ -226,7 +227,8 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $rawOutput = isset($options['raw_text']) && $options['raw_text']; foreach ($this->sortServiceIds($serviceIds) as $serviceId) { $definition = $this->resolveServiceDefinition($builder, $serviceId); - $styledServiceId = $rawOutput ? $serviceId : sprintf('%s', $serviceId); + + $styledServiceId = $rawOutput ? $serviceId : sprintf('%s', OutputFormatter::escape($serviceId)); if ($definition instanceof Definition) { if ($showTag) { foreach ($definition->getTag($showTag) as $key => $tag) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php index 7555f8f7aa025..4a5beeb5b8f71 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php @@ -252,7 +252,7 @@ private function createRedirectController($httpPort = null, $httpsPort = null) return new RedirectController(null, $httpPort, $httpsPort); } - public function assertRedirectUrl(Response $returnResponse, $expectedUrl) + private function assertRedirectUrl(Response $returnResponse, $expectedUrl) { $this->assertTrue($returnResponse->isRedirect($expectedUrl), "Expected: $expectedUrl\nGot: ".$returnResponse->headers->get('Location')); } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 39c90360cb964..054d05c40b2bd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -21,7 +21,7 @@ "symfony/cache": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/config": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4-beta4|~4.0-beta4", + "symfony/event-dispatcher": "~3.4|~4.0", "symfony/http-foundation": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", "symfony/polyfill-mbstring": "~1.0", diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index 0ca7e7f4b7f51..23fd3e03a35b6 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -199,7 +199,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->scalarNode('context')->cannotBeEmpty()->end() ->booleanNode('logout_on_user_change') ->defaultTrue() - ->info('When true, it will trigger a logout for the user if something has changed.') + ->info('When true, it will trigger a logout for the user if something has changed. Note: No-Op option since 4.0. Will always be true.') ->end() ->arrayNode('logout') ->treatTrueLike(array()) @@ -290,17 +290,6 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto return $firewall; }) ->end() - ->validate() - ->ifTrue(function ($v) { - return (isset($v['stateless']) && true === $v['stateless']) || (isset($v['security']) && false === $v['security']); - }) - ->then(function ($v) { - // this option doesn't change behavior when true when stateless, so prevent deprecations - $v['logout_on_user_change'] = true; - - return $v; - }) - ->end() ; } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 4b5270659e7b5..02d1eaeff574b 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -181,8 +181,6 @@ private function createFirewalls($config, ContainerBuilder $container) $customUserChecker = true; } - $contextListenerDefinition->addMethodCall('setLogoutOnUserChange', array($firewall['logout_on_user_change'])); - $configId = 'security.firewall.map.config.'.$name; list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml index ce6021823ba74..8e6133528c4bd 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml @@ -13,6 +13,8 @@ + + /*/*registerExtension(new TwigExtension()); $container->loadFromExtension('twig', array()); + $container->setAlias('test.twig.extension.debug.stopwatch', 'twig.extension.debug.stopwatch')->setPublic(true); $this->compileContainer($container); - $tokenParsers = $container->get('twig.extension.debug.stopwatch')->getTokenParsers(); + $tokenParsers = $container->get('test.twig.extension.debug.stopwatch')->getTokenParsers(); $stopwatchIsAvailable = new \ReflectionProperty($tokenParsers[0], 'stopwatchIsAvailable'); $stopwatchIsAvailable->setAccessible(true); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index 1f6644d5ddc30..ee9e72f7a1355 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -368,7 +368,7 @@ public function openAction(Request $request) $filename = $this->baseDir.DIRECTORY_SEPARATOR.$file; - if (preg_match("'(^|[/\\\\])\.\.?([/\\\\]|$)'", $file) || !is_readable($filename)) { + if (preg_match("'(^|[/\\\\])\.'", $file) || !is_readable($filename)) { throw new NotFoundHttpException(sprintf('The file "%s" cannot be opened.', $file)); } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index 5edfee1e049f4..e83e95ae134f6 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -1,6 +1,6 @@ {# This file is partially duplicated in TwigBundle/Resources/views/base_js.html.twig. If you make any change in this file, verify the same change is needed in the other file. #} -/*/* {{ include('@WebProfiler/Profiler/toolbar.css.twig') }} -/*/*getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock(); + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + $profiler = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock(); + + $controller = new ProfilerController($urlGenerator, $profiler, $twig, array(), null, __DIR__.'/../..'); + + try { + $response = $controller->openAction(Request::create('/_wdt/open', Request::METHOD_GET, array('file' => $path))); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertTrue($isAllowed); + } catch (NotFoundHttpException $e) { + $this->assertFalse($isAllowed); + } + } + + public function getOpenFileCases() + { + return array( + array('README.md', true), + array('composer.json', true), + array('Controller/ProfilerController.php', true), + array('.gitignore', false), + array('../TwigBundle/README.md', false), + array('Controller/../README.md', false), + array('Controller/./ProfilerController.php', false), + ); + } + /** * @dataProvider provideCspVariants */ diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 2845613b330b6..64d8fd6d19e4e 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -39,6 +39,8 @@ use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\Exception\FatalThrowableError; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -116,6 +118,25 @@ public function run(InputInterface $input = null, OutputInterface $output = null $output = new ConsoleOutput(); } + $renderException = function ($e) use ($output) { + if (!$e instanceof \Exception) { + $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine()); + } + if ($output instanceof ConsoleOutputInterface) { + $this->renderException($e, $output->getErrorOutput()); + } else { + $this->renderException($e, $output); + } + }; + if ($phpHandler = set_exception_handler($renderException)) { + restore_exception_handler(); + if (!is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) { + $debugHandler = true; + } elseif ($debugHandler = $phpHandler[0]->setExceptionHandler($renderException)) { + $phpHandler[0]->setExceptionHandler($debugHandler); + } + } + $this->configureIO($input, $output); try { @@ -125,11 +146,7 @@ public function run(InputInterface $input = null, OutputInterface $output = null throw $e; } - if ($output instanceof ConsoleOutputInterface) { - $this->renderException($e, $output->getErrorOutput()); - } else { - $this->renderException($e, $output); - } + $renderException($e); $exitCode = $e->getCode(); if (is_numeric($exitCode)) { @@ -140,6 +157,12 @@ public function run(InputInterface $input = null, OutputInterface $output = null } else { $exitCode = 1; } + } finally { + if (!$phpHandler) { + restore_exception_handler(); + } elseif (!$debugHandler) { + $phpHandler[0]->setExceptionHandler(null); + } } if ($this->autoExit) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index 967d6bb41cfee..e40d668d86fd3 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\ExpressionLanguage; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -148,6 +149,10 @@ private function getDefinitionId(string $id): ?string private function getExpressionLanguage() { if (null === $this->expressionLanguage) { + if (!class_exists(ExpressionLanguage::class)) { + throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $providers = $this->container->getExpressionLanguageProviders(); $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { if ('""' === substr_replace($arg, '', 1, -1)) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index 8fdbd8e2b5681..517085eb75fce 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -13,6 +13,7 @@ use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Reference; /** @@ -23,6 +24,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements RepeatablePassInterface { private $repeatedPass; + private $cloningIds = array(); /** * {@inheritdoc} @@ -41,17 +43,43 @@ protected function processValue($value, $isRoot = false) // Reference found in ArgumentInterface::getValues() are not inlineable return $value; } - if ($value instanceof Reference && $this->container->hasDefinition($id = (string) $value)) { - $definition = $this->container->getDefinition($id); - if ($this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) { - $this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId)); - - return $definition->isShared() ? $definition : clone $definition; + if ($value instanceof Definition && $this->cloningIds) { + if ($value->isShared()) { + return $value; } + $value = clone $value; + } + + if (!$value instanceof Reference || !$this->container->hasDefinition($id = (string) $value)) { + return parent::processValue($value, $isRoot); + } + + $definition = $this->container->getDefinition($id); + + if (!$this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) { + return $value; } - return parent::processValue($value, $isRoot); + $this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId)); + + if ($definition->isShared()) { + return $definition; + } + + if (isset($this->cloningIds[$id])) { + $ids = array_keys($this->cloningIds); + $ids[] = $id; + + throw new ServiceCircularReferenceException($id, array_slice($ids, array_search($id, $ids))); + } + + $this->cloningIds[$id] = true; + try { + return $this->processValue($definition); + } finally { + unset($this->cloningIds[$id]); + } } /** diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 7ee66c05f015b..42e03f4785b33 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -467,6 +467,8 @@ public function getCompiler() */ public function set($id, $service) { + $id = (string) $id; + if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) { // setting a synthetic service on a compiled container is alright throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a compiled container is not allowed.', $id)); @@ -484,7 +486,7 @@ public function set($id, $service) */ public function removeDefinition($id) { - if (isset($this->definitions[$id])) { + if (isset($this->definitions[$id = (string) $id])) { unset($this->definitions[$id]); $this->removedIds[$id] = true; } @@ -499,6 +501,8 @@ public function removeDefinition($id) */ public function has($id) { + $id = (string) $id; + return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id); } @@ -519,6 +523,10 @@ public function has($id) */ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { + if ($this->isCompiled() && isset($this->removedIds[$id = (string) $id]) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) { + return parent::get($id); + } + return $this->doGet($id, $invalidBehavior); } @@ -715,6 +723,12 @@ public function compile(bool $resolveEnvPlaceholders = false) } parent::compile(); + + foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) { + if (!$definition->isPublic() || $definition->isPrivate()) { + $this->removedIds[$id] = true; + } + } } /** @@ -769,6 +783,8 @@ public function setAliases(array $aliases) */ public function setAlias($alias, $id) { + $alias = (string) $alias; + if (is_string($id)) { $id = new Alias($id); } elseif (!$id instanceof Alias) { @@ -791,7 +807,7 @@ public function setAlias($alias, $id) */ public function removeAlias($alias) { - if (isset($this->aliasDefinitions[$alias])) { + if (isset($this->aliasDefinitions[$alias = (string) $alias])) { unset($this->aliasDefinitions[$alias]); $this->removedIds[$alias] = true; } @@ -806,7 +822,7 @@ public function removeAlias($alias) */ public function hasAlias($id) { - return isset($this->aliasDefinitions[$id]); + return isset($this->aliasDefinitions[$id = (string) $id]); } /** @@ -830,6 +846,8 @@ public function getAliases() */ public function getAlias($id) { + $id = (string) $id; + if (!isset($this->aliasDefinitions[$id])) { throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id)); } @@ -918,6 +936,8 @@ public function setDefinition($id, Definition $definition) throw new BadMethodCallException('Adding definition to a compiled container is not allowed'); } + $id = (string) $id; + unset($this->aliasDefinitions[$id], $this->removedIds[$id]); return $this->definitions[$id] = $definition; @@ -932,7 +952,7 @@ public function setDefinition($id, Definition $definition) */ public function hasDefinition($id) { - return isset($this->definitions[$id]); + return isset($this->definitions[(string) $id]); } /** @@ -946,6 +966,8 @@ public function hasDefinition($id) */ public function getDefinition($id) { + $id = (string) $id; + if (!isset($this->definitions[$id])) { throw new ServiceNotFoundException($id); } @@ -966,6 +988,8 @@ public function getDefinition($id) */ public function findDefinition($id) { + $id = (string) $id; + while (isset($this->aliasDefinitions[$id])) { $id = (string) $this->aliasDefinitions[$id]; } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index f6204169bd96f..b4416b849f3bc 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -224,12 +224,16 @@ public function dump(array $options = array()) {$namespaceLine} // This file has been auto-generated by the Symfony Dependency Injection Component for internal use. -if (!class_exists(\\Container{$hash}\\{$options['class']}::class, false)) { - require __DIR__.'/Container{$hash}/{$options['class']}.php'; +if (\\class_exists(\\Container{$hash}\\{$options['class']}::class, false)) { + // no-op +} elseif (!include __DIR__.'/Container{$hash}/{$options['class']}.php') { + touch(__DIR__.'/Container{$hash}.legacy'); + + return; } -if (!class_exists({$options['class']}::class, false)) { - class_alias(\\Container{$hash}\\{$options['class']}::class, {$options['class']}::class, false); +if (!\\class_exists({$options['class']}::class, false)) { + \\class_alias(\\Container{$hash}\\{$options['class']}::class, {$options['class']}::class, false); } return new \\Container{$hash}\\{$options['class']}(); @@ -424,13 +428,13 @@ private function addServiceInclude(string $cId, Definition $definition, \SplObje } foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) { - $code .= sprintf(" require_once %s;\n", $file); + $code .= sprintf(" include_once %s;\n", $file); } } foreach ($inlinedDefinitions as $def) { if ($file = $def->getFile()) { - $code .= sprintf(" require_once %s;\n", $this->dumpValue($file)); + $code .= sprintf(" include_once %s;\n", $this->dumpValue($file)); } } @@ -472,7 +476,7 @@ private function addServiceInlinedDefinitions(string $id, Definition $definition // $b = new ServiceB(); // $a = new ServiceA(ServiceB $b); // $b->setServiceA(ServiceA $a); - if ($this->hasReference($id, array($def->getArguments(), $def->getFactory()))) { + if (isset($inlinedDefinition[$definition]) && $this->hasReference($id, array($def->getArguments(), $def->getFactory()))) { throw new ServiceCircularReferenceException($id, array($id)); } @@ -874,7 +878,11 @@ class $class extends $baseClass { private \$parameters; private \$targetDirs = array(); - private \$privates = array(); + + /*{$this->docStar} + * @internal but protected for BC on cache:clear + */ + protected \$privates = array(); public function __construct() { @@ -1058,7 +1066,7 @@ private function addAliases(): string return $code." );\n"; } - private function addInlineRequires() :string + private function addInlineRequires(): string { if (!$this->hotPathTag || !$this->inlineRequires) { return ''; @@ -1082,7 +1090,7 @@ private function addInlineRequires() :string foreach ($lineage as $file) { if (!isset($this->inlinedRequires[$file])) { $this->inlinedRequires[$file] = true; - $code .= sprintf(" require_once %s;\n", $file); + $code .= sprintf(" include_once %s;\n", $file); } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index 55809e517852b..42cccaa186659 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -56,10 +56,25 @@ public function registerClasses(Definition $prototype, $namespace, $resource, $e $classes = $this->findClasses($namespace, $resource, $exclude); // prepare for deep cloning - $prototype = serialize($prototype); + $serializedPrototype = serialize($prototype); + $interfaces = array(); + $singlyImplemented = array(); foreach ($classes as $class) { - $this->setDefinition($class, unserialize($prototype)); + if (interface_exists($class, false)) { + $interfaces[] = $class; + } else { + $this->setDefinition($class, unserialize($serializedPrototype)); + foreach (class_implements($class, false) as $interface) { + $singlyImplemented[$interface] = isset($singlyImplemented[$interface]) ? false : $class; + } + } + } + foreach ($interfaces as $interface) { + if (!empty($singlyImplemented[$interface])) { + $this->container->setAlias($interface, $singlyImplemented[$interface]) + ->setPublic(false); + } } } @@ -129,7 +144,7 @@ private function findClasses($namespace, $pattern, $excludePattern) throw new InvalidArgumentException(sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.', $class, $path, $pattern)); } - if ($r->isInstantiable()) { + if ($r->isInstantiable() || $r->isInterface()) { $classes[] = $class; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php index 9630cca14f015..bb60af4d19e64 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php @@ -111,6 +111,60 @@ public function testProcessInlinesMixedServicesLoop() $this->assertEquals($container->getDefinition('foo')->getArgument(0), $container->getDefinition('bar')); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException + * @expectedExceptionMessage Circular reference detected for service "bar", path: "bar -> foo -> bar". + */ + public function testProcessThrowsOnNonSharedLoops() + { + $container = new ContainerBuilder(); + $container + ->register('foo') + ->addArgument(new Reference('bar')) + ->setShared(false) + ; + $container + ->register('bar') + ->setShared(false) + ->addMethodCall('setFoo', array(new Reference('foo'))) + ; + + $this->process($container); + } + + public function testProcessNestedNonSharedServices() + { + $container = new ContainerBuilder(); + $container + ->register('foo') + ->addArgument(new Reference('bar1')) + ->addArgument(new Reference('bar2')) + ; + $container + ->register('bar1') + ->setShared(false) + ->addArgument(new Reference('baz')) + ; + $container + ->register('bar2') + ->setShared(false) + ->addArgument(new Reference('baz')) + ; + $container + ->register('baz') + ->setShared(false) + ; + + $this->process($container); + + $baz1 = $container->getDefinition('foo')->getArgument(0)->getArgument(0); + $baz2 = $container->getDefinition('foo')->getArgument(1)->getArgument(0); + + $this->assertEquals($container->getDefinition('baz'), $baz1); + $this->assertEquals($container->getDefinition('baz'), $baz2); + $this->assertNotSame($baz1, $baz2); + } + public function testProcessInlinesIfMultipleReferencesButAllFromTheSameDefinition() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 1a52ebf5f9b22..4942121e188e3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -733,6 +733,7 @@ public function testEnvInId() PsrContainerInterface::class => true, ContainerInterface::class => true, 'baz_%env(BAR)%' => true, + 'bar_%env(BAR)%' => true, ); $this->assertSame($expected, $container->getRemovedIds()); @@ -1237,6 +1238,9 @@ public function testAlmostCircular($visibility) $this->assertSame($foo2, $foo2->bar->foobar->foo); $this->assertSame(array(), (array) $container->get('foobar4')); + + $foo5 = $container->get('foo5'); + $this->assertSame($foo5, $foo5->bar->foo); } public function provideAlmostCircular() @@ -1306,6 +1310,24 @@ public function testArgumentsHaveHigherPriorityThanBindings() $this->assertSame('via-argument', $container->get('foo')->class1->identifier); $this->assertSame('via-bindings', $container->get('foo')->class2->identifier); } + + public function testIdCanBeAnObjectAsLongAsItCanBeCastToString() + { + $id = new Reference('another_service'); + $aliasId = new Reference('alias_id'); + + $container = new ContainerBuilder(); + $container->set($id, new \stdClass()); + $container->setAlias($aliasId, 'another_service'); + + $this->assertTrue($container->has('another_service')); + $this->assertTrue($container->has($id)); + $this->assertTrue($container->hasAlias('alias_id')); + $this->assertTrue($container->hasAlias($aliasId)); + + $container->removeAlias($aliasId); + $container->removeDefinition($id); + } } class FooClass diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 8dadbb8fbfd01..8380cd02bcda7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -781,6 +781,9 @@ public function testAlmostCircular($visibility) $this->assertSame($foo2, $foo2->bar->foobar->foo); $this->assertSame(array(), (array) $container->get('foobar4')); + + $foo5 = $container->get('foo5'); + $this->assertSame($foo5, $foo5->bar->foo); } public function provideAlmostCircular() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/Foo.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/Foo.php index ee533fecd9019..b6690a8d2680a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/Foo.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/Foo.php @@ -2,13 +2,13 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype; -class Foo +class Foo implements FooInterface, Sub\BarInterface { public function __construct($bar = null) { } - function setFoo(self $foo) + public function setFoo(self $foo) { } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/FooInterface.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/FooInterface.php new file mode 100644 index 0000000000000..1855dcfc59e0e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/FooInterface.php @@ -0,0 +1,7 @@ +register('foobar4', 'stdClass')->setPublic(true) ->addArgument(new Reference('foo4')); +// loop on the constructor of a setter-injected dep with property + +$container->register('foo5', 'stdClass')->setPublic(true) + ->setProperty('bar', new Reference('bar5')); + +$container->register('bar5', 'stdClass')->setPublic($public) + ->addArgument(new Reference('foo5')) + ->setProperty('foo', new Reference('foo5')); + return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php index cbc17d74df3b7..a04d80affcec1 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php @@ -20,7 +20,11 @@ class Container extends \Symfony\Component\DependencyInjection\Dump\AbstractCont { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1.php index bb312436dc837..f25c59b81596f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10.php index ef88c481583af..31c4475ec7dab 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php index 4a1fbb901c313..9e193f5c9b0ee 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services13.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services13.php index d17073ae0b2ed..8c90280d272a2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services13.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services13.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php index 80635ad02dbb0..673c9d54bbeca 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services24.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services24.php index baa8a5eeb207e..090a77dd3c2c3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services24.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services24.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php index 700275f1e6b96..2089bfe6386c3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php @@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_EnvParameters extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services33.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services33.php index 4ca6299d7435d..1c70b0ee8d06b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services33.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services33.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php index 0d60c3699cba6..dc7da1c274e5c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services8.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt index 579bf285ad643..29f544eaaf3c7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt @@ -231,7 +231,7 @@ use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; // This file has been auto-generated by the Symfony Dependency Injection Component for internal use. // Returns the public 'method_call1' shared service. -require_once ($this->targetDirs[0].'/Fixtures/includes/foo.php'); +include_once ($this->targetDirs[0].'/Fixtures/includes/foo.php'); $this->services['method_call1'] = $instance = new \Bar\FooClass(); @@ -309,7 +309,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { @@ -483,12 +487,16 @@ class ProjectServiceContainer extends Container // This file has been auto-generated by the Symfony Dependency Injection Component for internal use. -if (!class_exists(\Container%s\ProjectServiceContainer::class, false)) { - require __DIR__.'/Container%s/ProjectServiceContainer.php'; +if (\class_exists(\Container%s\ProjectServiceContainer::class, false)) { + // no-op +} elseif (!include __DIR__.'/Container%s/ProjectServiceContainer.php') { + touch(__DIR__.'/Container%s.legacy'); + + return; } -if (!class_exists(ProjectServiceContainer::class, false)) { - class_alias(\Container%s\ProjectServiceContainer::class, ProjectServiceContainer::class, false); +if (!\class_exists(ProjectServiceContainer::class, false)) { + \class_alias(\Container%s\ProjectServiceContainer::class, ProjectServiceContainer::class, false); } return new \Container%s\ProjectServiceContainer(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index 72e06b3416f69..a45456163f796 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { @@ -340,7 +344,7 @@ protected function getLazyContextIgnoreInvalidRefService() */ protected function getMethodCall1Service() { - require_once '%path%foo.php'; + include_once '%path%foo.php'; $this->services['method_call1'] = $instance = new \Bar\FooClass(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index 76af8a5484861..4de6bfc233193 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { @@ -28,6 +32,7 @@ public function __construct() 'bar3' => 'getBar3Service', 'foo' => 'getFooService', 'foo2' => 'getFoo2Service', + 'foo5' => 'getFoo5Service', 'foobar4' => 'getFoobar4Service', ); @@ -56,6 +61,7 @@ public function getRemovedIds() 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'bar' => true, + 'bar5' => true, 'foo4' => true, 'foobar' => true, 'foobar2' => true, @@ -125,6 +131,24 @@ protected function getFoo2Service() return $this->services['foo2'] = new \FooCircular($a); } + /** + * Gets the public 'foo5' shared service. + * + * @return \stdClass + */ + protected function getFoo5Service() + { + $this->services['foo5'] = $instance = new \stdClass(); + + $a = new \stdClass(($this->services['foo5'] ?? $this->getFoo5Service())); + + $a->foo = $instance; + + $instance->bar = $a; + + return $instance; + } + /** * Gets the public 'foobar4' shared service. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index a552a22b43358..79a0c11cc15c3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { @@ -26,9 +30,11 @@ public function __construct() $this->methodMap = array( 'bar' => 'getBarService', 'bar3' => 'getBar3Service', + 'bar5' => 'getBar5Service', 'foo' => 'getFooService', 'foo2' => 'getFoo2Service', 'foo4' => 'getFoo4Service', + 'foo5' => 'getFoo5Service', 'foobar' => 'getFoobarService', 'foobar2' => 'getFoobar2Service', 'foobar3' => 'getFoobar3Service', @@ -93,6 +99,26 @@ protected function getBar3Service() return $instance; } + /** + * Gets the public 'bar5' shared service. + * + * @return \stdClass + */ + protected function getBar5Service() + { + $a = ($this->services['foo5'] ?? $this->getFoo5Service()); + + if (isset($this->services['bar5'])) { + return $this->services['bar5']; + } + + $this->services['bar5'] = $instance = new \stdClass($a); + + $instance->foo = $a; + + return $instance; + } + /** * Gets the public 'foo' shared service. * @@ -139,6 +165,20 @@ protected function getFoo4Service() return $instance; } + /** + * Gets the public 'foo5' shared service. + * + * @return \stdClass + */ + protected function getFoo5Service() + { + $this->services['foo5'] = $instance = new \stdClass(); + + $instance->bar = ($this->services['bar5'] ?? $this->getBar5Service()); + + return $instance; + } + /** * Gets the public 'foobar' shared service. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php index cd7bc61ceb015..0e4e9ea239ee6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php index 83aee3007b6fe..8af802f70dab3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php @@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_Base64Parameters extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php index b6afb3cf15580..4100dcdd2b914 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php index 05bae361195d4..a0092b0160f9a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { @@ -37,10 +41,10 @@ public function __construct() $this->aliases = array(); - require_once $this->targetDirs[1].'/includes/HotPath/I1.php'; - require_once $this->targetDirs[1].'/includes/HotPath/P1.php'; - require_once $this->targetDirs[1].'/includes/HotPath/T1.php'; - require_once $this->targetDirs[1].'/includes/HotPath/C1.php'; + include_once $this->targetDirs[1].'/includes/HotPath/I1.php'; + include_once $this->targetDirs[1].'/includes/HotPath/P1.php'; + include_once $this->targetDirs[1].'/includes/HotPath/T1.php'; + include_once $this->targetDirs[1].'/includes/HotPath/C1.php'; } public function reset() @@ -95,8 +99,8 @@ protected function getC1Service() */ protected function getC2Service() { - require_once $this->targetDirs[1].'/includes/HotPath/C3.php'; - require_once $this->targetDirs[1].'/includes/HotPath/C2.php'; + include_once $this->targetDirs[1].'/includes/HotPath/C3.php'; + include_once $this->targetDirs[1].'/includes/HotPath/C2.php'; return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2(new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3()); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php index b8aba31b1ea8b..59bbce3a995c4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php index 404944a3369b5..86315e2ebaffc 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php index efaa0fb1d9839..5caf9104dd34d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php index 61b0b7294799f..012a36023b0f8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php @@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_Rot13Parameters extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php index 934cad86b5feb..1c223c3e65b96 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php @@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php index 446d2ae482e77..0f5090c80bebe 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php @@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_Uninitialized_Reference extends Container { private $parameters; private $targetDirs = array(); - private $privates = array(); + + /** + * @internal but protected for BC on cache:clear + */ + protected $privates = array(); public function __construct() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index 6544145c4e0e0..4cba7443e8ce9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -11,10 +11,12 @@ namespace Symfony\Component\DependencyInjection\Tests\Loader; +use Psr\Container\ContainerInterface as PsrContainerInterface; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Loader\FileLoader; @@ -25,7 +27,10 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\AnotherSub\DeeperBaz; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Baz; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\FooInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\BarInterface; class FileLoaderTest extends TestCase { @@ -91,6 +96,14 @@ public function testRegisterClasses() array('service_container', Bar::class), array_keys($container->getDefinitions()) ); + $this->assertEquals( + array( + PsrContainerInterface::class, + ContainerInterface::class, + BarInterface::class, + ), + array_keys($container->getAliases()) + ); } public function testRegisterClassesWithExclude() @@ -111,6 +124,43 @@ public function testRegisterClassesWithExclude() $this->assertTrue($container->has(Baz::class)); $this->assertFalse($container->has(Foo::class)); $this->assertFalse($container->has(DeeperBaz::class)); + + $this->assertEquals( + array( + PsrContainerInterface::class, + ContainerInterface::class, + BarInterface::class, + ), + array_keys($container->getAliases()) + ); + } + + public function testNestedRegisterClasses() + { + $container = new ContainerBuilder(); + $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + + $prototype = new Definition(); + $prototype->setPublic(true)->setPrivate(true); + $loader->registerClasses($prototype, 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', 'Prototype/*'); + + $this->assertTrue($container->has(Bar::class)); + $this->assertTrue($container->has(Baz::class)); + $this->assertTrue($container->has(Foo::class)); + + $this->assertEquals( + array( + PsrContainerInterface::class, + ContainerInterface::class, + FooInterface::class, + ), + array_keys($container->getAliases()) + ); + + $alias = $container->getAlias(FooInterface::class); + $this->assertSame(Foo::class, (string) $alias); + $this->assertFalse($alias->isPublic()); + $this->assertFalse($alias->isPrivate()); } /** diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 7ce9f496cd0fe..cf2d7ecbd306e 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -33,7 +33,7 @@ "symfony/http-kernel": "~3.4|~4.0", "symfony/security-csrf": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0", - "symfony/var-dumper": "~3.4-beta3|~4.0-beta3" + "symfony/var-dumper": "~3.4|~4.0" }, "conflict": { "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", diff --git a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php index 7b0aa4b5a226e..6f25f0bf23134 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php @@ -92,7 +92,7 @@ protected function instantiateController($class) } catch (\ArgumentCountError $e) { } - if ($this->container instanceof Container && in_array($class, $this->container->getRemovedIds(), true)) { + if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$class])) { throw new \LogicException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $class), 0, $e); } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index 5ec4e5f23f9e0..e0297ea395967 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -166,7 +166,9 @@ public function collect(Request $request, Response $response, \Exception $except public function reset() { - $this->stopwatch->reset(); + if ($this->stopwatch) { + $this->stopwatch->reset(); + } $this->data = array(); $this->dataCount = 0; $this->isCollected = false; diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 2a6d8a4fd17e8..39efec369d7f8 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.0'; - const VERSION_ID = 40000; + const VERSION = '4.0.1'; + const VERSION_ID = 40001; const MAJOR_VERSION = 4; const MINOR_VERSION = 0; - const RELEASE_VERSION = 0; + const RELEASE_VERSION = 1; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '07/2018'; @@ -356,7 +356,7 @@ public function getContainer() */ public function setAnnotatedClassCache(array $annotatedClasses) { - file_put_contents(($this->warmupDir ?: $this->getCacheDir()).'/annotations.map', sprintf('warmupDir ?: $this->getCacheDir()).'/annotations.map', sprintf('warmupDir ?: $this->getCacheDir(); $cache = new ConfigCache($cacheDir.'/'.$class.'.php', $this->debug); if ($fresh = $cache->isFresh()) { - $this->container = require $cache->getPath(); + // Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors + $errorLevel = error_reporting(\E_ALL ^ \E_WARNING); + try { + $this->container = include $cache->getPath(); + } finally { + error_reporting($errorLevel); + } $fresh = \is_object($this->container); } if (!$fresh) { @@ -460,7 +466,7 @@ protected function initializeContainer() $collectedLogs = array(); $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { - return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; + return $previousHandler ? $previousHandler($type & ~E_WARNING, $message, $file, $line) : E_WARNING === $type; } if (isset($collectedLogs[$message])) { @@ -487,23 +493,27 @@ protected function initializeContainer() 'count' => 1, ); }); + } else { + $errorLevel = error_reporting(\E_ALL ^ \E_WARNING); } try { $container = null; $container = $this->buildContainer(); $container->compile(); + + $oldContainer = file_exists($cache->getPath()) && is_object($oldContainer = include $cache->getPath()) ? new \ReflectionClass($oldContainer) : false; } finally { if ($this->debug) { restore_error_handler(); file_put_contents($cacheDir.'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs))); file_put_contents($cacheDir.'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : ''); + } else { + error_reporting($errorLevel); } } - $oldContainer = file_exists($cache->getPath()) && is_object($oldContainer = @include $cache->getPath()) ? new \ReflectionClass($oldContainer) : false; - $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); $this->container = require $cache->getPath(); } @@ -519,13 +529,13 @@ protected function initializeContainer() // old container files are not removed immediately, // but on a next dump of the container. $oldContainerDir = dirname($oldContainer->getFileName()); - foreach (glob(dirname($oldContainerDir).'/*.legacyContainer') as $legacyContainer) { - if ($oldContainerDir.'.legacyContainer' !== $legacyContainer && @unlink($legacyContainer)) { + foreach (glob(dirname($oldContainerDir).'/*.legacy') as $legacyContainer) { + if ($oldContainerDir.'.legacy' !== $legacyContainer && @unlink($legacyContainer)) { (new Filesystem())->remove(substr($legacyContainer, 0, -16)); } } - touch($oldContainerDir.'.legacyContainer'); + touch($oldContainerDir.'.legacy'); } if ($this->container->has('cache_warmer')) { diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php index b3fa081a63c88..8624269be80e5 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php @@ -130,7 +130,7 @@ public function testNonConstructController() $container->expects($this->atLeastOnce()) ->method('getRemovedIds') ->with() - ->will($this->returnValue(array(ImpossibleConstructController::class))) + ->will($this->returnValue(array(ImpossibleConstructController::class => true))) ; $resolver = $this->createControllerResolver(null, $container); diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index 955b1cf83ba57..8461a180c8c31 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -521,7 +521,7 @@ public function testKernelReset() $this->assertTrue(get_class($kernel->getContainer()) !== $containerClass); $this->assertFileExists($containerFile); - $this->assertFileExists(dirname($containerFile).'.legacyContainer'); + $this->assertFileExists(dirname($containerFile).'.legacy'); } public function testKernelPass() diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php index 888f68a72b635..874a7e290b776 100644 --- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php +++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php @@ -257,7 +257,7 @@ class NumberFormatter * @throws MethodArgumentValueNotImplementedException When the $style is not supported * @throws MethodArgumentNotImplementedException When the pattern value is different than null */ - public function __construct(?string $locale = 'en', string $style = null, $pattern = null) + public function __construct(?string $locale = 'en', int $style = null, $pattern = null) { if ('en' !== $locale && null !== $locale) { throw new MethodArgumentValueNotImplementedException(__METHOD__, 'locale', $locale, 'Only the locale "en" is supported'); diff --git a/src/Symfony/Component/Security/Csrf/composer.json b/src/Symfony/Component/Security/Csrf/composer.json index 4e03fc7db0fb9..0521b86e83e1a 100644 --- a/src/Symfony/Component/Security/Csrf/composer.json +++ b/src/Symfony/Component/Security/Csrf/composer.json @@ -20,10 +20,10 @@ "symfony/security-core": "~3.4|~4.0" }, "require-dev": { - "symfony/http-foundation": "~3.4-beta5|~4.0-beta5" + "symfony/http-foundation": "~3.4|~4.0" }, "conflict": { - "symfony/http-foundation": "<3.4-beta5|~4.0,<4.0-beta5" + "symfony/http-foundation": "<3.4" }, "suggest": { "symfony/http-foundation": "For using the class SessionTokenStorage." diff --git a/src/Symfony/Component/Security/composer.json b/src/Symfony/Component/Security/composer.json index a3a9717665373..a7299f616d47b 100644 --- a/src/Symfony/Component/Security/composer.json +++ b/src/Symfony/Component/Security/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^7.1.3", "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/http-foundation": "~3.4-beta5|~4.0-beta5", + "symfony/http-foundation": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", "symfony/property-access": "~3.4|~4.0" }, diff --git a/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php b/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php index be05f8f8a0af0..ce77d9ba843a4 100644 --- a/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php @@ -45,7 +45,7 @@ public function validate($value, Constraint $constraint) if (!$this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) { $this->context->buildViolation($constraint->message) - ->setParameter('{{ value }}', $this->formatValue($value)) + ->setParameter('{{ value }}', $this->formatValue($value, self::OBJECT_TO_STRING)) ->setCode(Expression::EXPRESSION_FAILED_ERROR) ->addViolation(); } diff --git a/src/Symfony/Component/Validator/Constraints/ValidValidator.php b/src/Symfony/Component/Validator/Constraints/ValidValidator.php index b2f1f1c5a06b9..be5fbc12660ba 100644 --- a/src/Symfony/Component/Validator/Constraints/ValidValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ValidValidator.php @@ -26,6 +26,10 @@ public function validate($value, Constraint $constraint) throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Valid'); } + if (null === $value) { + return; + } + $this->context ->getValidator() ->inContext($this->context) diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php index e4316243e5580..07f17d648aa39 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Validator\Constraints\ExpressionValidator; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; use Symfony\Component\Validator\Tests\Fixtures\Entity; +use Symfony\Component\Validator\Tests\Fixtures\ToString; class ExpressionValidatorTest extends ConstraintValidatorTestCase { @@ -87,6 +88,40 @@ public function testFailingExpressionAtObjectLevel() ->assertRaised(); } + public function testSucceedingExpressionAtObjectLevelWithToString() + { + $constraint = new Expression('this.data == 1'); + + $object = new ToString(); + $object->data = '1'; + + $this->setObject($object); + + $this->validator->validate($object, $constraint); + + $this->assertNoViolation(); + } + + public function testFailingExpressionAtObjectLevelWithToString() + { + $constraint = new Expression(array( + 'expression' => 'this.data == 1', + 'message' => 'myMessage', + )); + + $object = new ToString(); + $object->data = '2'; + + $this->setObject($object); + + $this->validator->validate($object, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', 'toString') + ->setCode(Expression::EXPRESSION_FAILED_ERROR) + ->assertRaised(); + } + public function testSucceedingExpressionAtPropertyLevel() { $constraint = new Expression('value == this.data'); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ValidValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ValidValidatorTest.php index f95650d359b3b..c4ccf1551f2a0 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ValidValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ValidValidatorTest.php @@ -20,6 +20,18 @@ public function testPropertyPathsArePassedToNestedContexts() $this->assertSame('fooBar.fooBarBaz.foo', $violations->get(0)->getPropertyPath()); } + public function testNullValues() + { + $validatorBuilder = new ValidatorBuilder(); + $validator = $validatorBuilder->enableAnnotationMapping()->getValidator(); + + $foo = new Foo(); + $foo->fooBar = null; + $violations = $validator->validate($foo, null, array('nested')); + + $this->assertCount(0, $violations); + } + protected function createValidator() { return new ValidValidator(); diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/ToString.php b/src/Symfony/Component/Validator/Tests/Fixtures/ToString.php new file mode 100644 index 0000000000000..714fdb9e98f5f --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/ToString.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +class ToString +{ + public $data; + + public function __toString() + { + return 'toString'; + } +} diff --git a/src/Symfony/Component/Workflow/Definition.php b/src/Symfony/Component/Workflow/Definition.php index a8bc0806e2906..1469881de59ca 100644 --- a/src/Symfony/Component/Workflow/Definition.php +++ b/src/Symfony/Component/Workflow/Definition.php @@ -82,7 +82,7 @@ private function setInitialPlace(string $place = null) private function addPlace(string $place) { - if (!preg_match('{^[\w\d_-]+$}', $place)) { + if (!preg_match('{^[\w_-]+$}', $place)) { throw new InvalidArgumentException(sprintf('The place "%s" contains invalid characters.', $place)); } diff --git a/src/Symfony/Component/Workflow/DefinitionBuilder.php b/src/Symfony/Component/Workflow/DefinitionBuilder.php index f663c16d1a255..16d66286efb4c 100644 --- a/src/Symfony/Component/Workflow/DefinitionBuilder.php +++ b/src/Symfony/Component/Workflow/DefinitionBuilder.php @@ -77,7 +77,7 @@ public function setInitialPlace($place) */ public function addPlace($place) { - if (!preg_match('{^[\w\d_-]+$}', $place)) { + if (!preg_match('{^[\w_-]+$}', $place)) { throw new InvalidArgumentException(sprintf('The place "%s" contains invalid characters.', $place)); } diff --git a/src/Symfony/Component/Workflow/Transition.php b/src/Symfony/Component/Workflow/Transition.php index b9f52d8e10910..9b482ac843cb2 100644 --- a/src/Symfony/Component/Workflow/Transition.php +++ b/src/Symfony/Component/Workflow/Transition.php @@ -30,7 +30,7 @@ class Transition */ public function __construct(string $name, $froms, $tos) { - if (!preg_match('{^[\w\d_-]+$}', $name)) { + if (!preg_match('{^[\w_-]+$}', $name)) { throw new InvalidArgumentException(sprintf('The transition "%s" contains invalid characters.', $name)); } diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index cc1487002eb73..e77fa8c5fa0cb 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -585,21 +585,10 @@ private function getNextEmbedBlock(int $indentation = null, bool $inSequence = f continue; } - // we ignore "comment" lines only when we are not inside a scalar block - if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) { - // remember ignored comment lines (they are used later in nested - // parser calls to determine real line numbers) - // - // CAUTION: beware to not populate the global property here as it - // will otherwise influence the getRealCurrentLineNb() call here - // for consecutive comment lines and subsequent embedded blocks - $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb(); - - continue; - } - if ($indent >= $newIndent) { $data[] = substr($this->currentLine, $newIndent); + } elseif ($this->isCurrentLineComment()) { + $data[] = $this->currentLine; } elseif (0 == $indent) { $this->moveToPreviousLine(); @@ -695,6 +684,8 @@ private function parseValue(string $value, int $flags, string $context) return Inline::parse($value, $flags, $this->refs); } + $lines = array(); + while ($this->moveToNextLine()) { // unquoted strings end before the first unindented line if (null === $quotation && 0 === $this->getCurrentLineIndentation()) { @@ -703,7 +694,7 @@ private function parseValue(string $value, int $flags, string $context) break; } - $value .= ' '.trim($this->currentLine); + $lines[] = trim($this->currentLine); // quoted string values end with a line that is terminated with the quotation character if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) { @@ -711,6 +702,21 @@ private function parseValue(string $value, int $flags, string $context) } } + for ($i = 0, $linesCount = count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = false; + } else { + $value .= ' '.$lines[$i]; + $previousLineBlank = false; + } + } + + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); + $parsedValue = Inline::parse($value, $flags, $this->refs); if ('mapping' === $context && is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index b3b1465aaf379..f46d2ce9f749c 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -1433,6 +1433,38 @@ public function testMultiLineQuotedStringWithTrailingBackslash() $this->assertSame(array('foobar' => 'foobar'), $this->parser->parse($yaml)); } + public function testCommentCharactersInMultiLineQuotedStrings() + { + $yaml = << array( + 'foobar' => 'foo #bar', + 'bar' => 'baz', + ), + ); + + $this->assertSame($expected, $this->parser->parse($yaml)); + } + + public function testBlankLinesInQuotedMultiLineString() + { + $yaml = << "foo\nbar", + ); + + $this->assertSame($expected, $this->parser->parse($yaml)); + } + public function testParseMultiLineUnquotedString() { $yaml = <<