diff --git a/CHANGELOG-3.4.md b/CHANGELOG-3.4.md index 70a0155445758..7be69fd18959c 100644 --- a/CHANGELOG-3.4.md +++ b/CHANGELOG-3.4.md @@ -7,6 +7,33 @@ in 3.4 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/v3.4.0...v3.4.1 +* 3.4.1 (2017-12-04) + + * 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 #25285 [DI] Throw an exception if Expression Language is not installed (sroze) + * bug #25241 [Yaml] do not eagerly filter comment lines (xabbuh) + * 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) + * 3.4.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 d21601af76e7f..3821dcd85823d 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Twig\Form\TwigRendererInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Form\ChoiceList\View\ChoiceView; +use Symfony\Component\Form\FormView; use Twig\Environment; use Twig\Extension\AbstractExtension; use Twig\Extension\InitRuntimeInterface; @@ -105,6 +106,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'), ); } @@ -192,3 +194,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 0b16573768da5..33ac7d5849c7c 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -24,7 +24,7 @@ "symfony/asset": "~2.8|~3.0|~4.0", "symfony/dependency-injection": "~2.8|~3.0|~4.0", "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/form": "~3.4-beta4|~4.0-beta4", + "symfony/form": "~3.4|~4.0", "symfony/http-foundation": "^3.3.11|~4.0", "symfony/http-kernel": "~3.2|~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 3c5ba3e93d3cf..35e9d73cd74a6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php @@ -89,7 +89,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)'), @@ -128,7 +128,7 @@ private static function isExpired($date) return false !== $date && new \DateTime() > $date->modify('last day of this month 23:59:59'); } - private static function getDotEnvVars() + private static function getDotenvVars() { $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 fe96e0401f799..3e14b5c2c18e7 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 a95083a382639..18c27385e58fd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php @@ -314,7 +314,7 @@ private function createLegacyRedirectController($httpPort = null, $httpsPort = n return $controller; } - 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 abbc1dbef368a..e2382363bebc5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -22,7 +22,7 @@ "symfony/class-loader": "~3.2", "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.3.11|~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 fb66cf78e706e..ad076f05e71e1 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -340,17 +340,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 835794f15fdd5..1bc06b1f47900 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -43,6 +43,7 @@ class SecurityExtension extends Extension private $factories = array(); private $userProviderFactories = array(); private $expressionLanguage; + private $logoutOnUserChangeByContextKey = array(); public function __construct() { @@ -276,12 +277,6 @@ private function createFirewalls($config, ContainerBuilder $container) $customUserChecker = true; } - if (!isset($firewall['logout_on_user_change']) || !$firewall['logout_on_user_change']) { - @trigger_error(sprintf('Not setting "logout_on_user_change" to true on firewall "%s" is deprecated as of 3.4, it will always be true in 4.0.', $name), E_USER_DEPRECATED); - } - - $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); @@ -370,7 +365,16 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $contextKey = $firewall['context']; } - $listeners[] = new Reference($this->createContextListener($container, $contextKey)); + if (!$logoutOnUserChange = $firewall['logout_on_user_change']) { + @trigger_error(sprintf('Not setting "logout_on_user_change" to true on firewall "%s" is deprecated as of 3.4, it will always be true in 4.0.', $id), E_USER_DEPRECATED); + } + + if (isset($this->logoutOnUserChangeByContextKey[$contextKey]) && $this->logoutOnUserChangeByContextKey[$contextKey][1] !== $logoutOnUserChange) { + throw new InvalidConfigurationException(sprintf('Firewalls "%s" and "%s" need to have the same value for option "logout_on_user_change" as they are sharing the context "%s"', $this->logoutOnUserChangeByContextKey[$contextKey][0], $id, $contextKey)); + } + + $this->logoutOnUserChangeByContextKey[$contextKey] = array($id, $logoutOnUserChange); + $listeners[] = new Reference($this->createContextListener($container, $contextKey, $logoutOnUserChange)); } $config->replaceArgument(6, $contextKey); @@ -481,7 +485,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a return array($matcher, $listeners, $exceptionListener); } - private function createContextListener($container, $contextKey) + private function createContextListener($container, $contextKey, $logoutUserOnChange) { if (isset($this->contextListeners[$contextKey])) { return $this->contextListeners[$contextKey]; @@ -490,6 +494,7 @@ private function createContextListener($container, $contextKey) $listenerId = 'security.context_listener.'.count($this->contextListeners); $listener = $container->setDefinition($listenerId, new ChildDefinition('security.context_listener')); $listener->replaceArgument(2, $contextKey); + $listener->addMethodCall('setLogoutOnUserChange', array($logoutUserOnChange)); return $this->contextListeners[$contextKey] = $listenerId; } 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 @@ + + getRawContainer(); @@ -135,13 +135,52 @@ public function testDeprecationForUserLogout() 'providers' => array( 'default' => array('id' => 'foo'), ), + 'firewalls' => array( + 'some_firewall' => array( + 'pattern' => '/.*', + 'http_basic' => null, + 'logout_on_user_change' => false, + ), + 'some_other_firewall' => array( + 'pattern' => '/.*', + 'http_basic' => null, + 'logout_on_user_change' => true, + ), + ), + )); + + $container->compile(); + + $this->assertEquals(array(array('setLogoutOnUserChange', array(false))), $container->getDefinition('security.context_listener.0')->getMethodCalls()); + $this->assertEquals(array(array('setLogoutOnUserChange', array(true))), $container->getDefinition('security.context_listener.1')->getMethodCalls()); + } + /** + * @group legacy + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage Firewalls "some_firewall" and "some_other_firewall" need to have the same value for option "logout_on_user_change" as they are sharing the context "my_context" + */ + public function testThrowsIfLogoutOnUserChangeDifferentForSharedContext() + { + $container = $this->getRawContainer(); + + $container->loadFromExtension('security', array( + 'providers' => array( + 'default' => array('id' => 'foo'), + ), 'firewalls' => array( 'some_firewall' => array( 'pattern' => '/.*', 'http_basic' => null, + 'context' => 'my_context', 'logout_on_user_change' => false, ), + 'some_other_firewall' => array( + 'pattern' => '/.*', + 'http_basic' => null, + 'context' => 'my_context', + 'logout_on_user_change' => true, + ), ), )); diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 4dc3f78ad0f14..d1bf164f0a1f8 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^5.5.9|>=7.0.8", "ext-xml": "*", - "symfony/security": "~3.4-rc1|~4.0-rc1", + "symfony/security": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/http-kernel": "~3.3|~4.0", "symfony/polyfill-php70": "~1.0" @@ -29,9 +29,9 @@ "symfony/console": "~3.4|~4.0", "symfony/css-selector": "~2.8|~3.0|~4.0", "symfony/dom-crawler": "~2.8|~3.0|~4.0", - "symfony/event-dispatcher": "^3.3.1|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", "symfony/form": "^3.4|~4.0", - "symfony/framework-bundle": "^3.4-rc1|~4.0-rc1", + "symfony/framework-bundle": "~3.4|~4.0", "symfony/http-foundation": "~3.3|~4.0", "symfony/security-acl": "~2.8|~3.0", "symfony/translation": "~3.4|~4.0", @@ -47,7 +47,7 @@ }, "conflict": { "symfony/var-dumper": "<3.3", - "symfony/event-dispatcher": "<3.3.1", + "symfony/event-dispatcher": "<3.4", "symfony/framework-bundle": "<3.4", "symfony/console": "<3.4" }, diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig index ccabdc968fb2e..0ac832ee0610d 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig @@ -1,6 +1,6 @@ {# This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig. If you make any change in this file, verify the same change is needed in the other file. #} -/*/*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 4aca4cc354e44..0920a1aa5e18a 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -385,7 +385,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', { 'position': position, 'floatable': true }) }} -/*/*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(), 'bottom', 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 b98365c059332..f3bc0d59641a5 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -40,6 +40,7 @@ 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; @@ -118,6 +119,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); + } + } + if (null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) { @trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), E_USER_DEPRECATED); } @@ -125,21 +145,13 @@ public function run(InputInterface $input = null, OutputInterface $output = null $this->configureIO($input, $output); try { - $e = null; $exitCode = $this->doRun($input, $output); } catch (\Exception $e) { - } - - if (null !== $e) { if (!$this->catchExceptions) { 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)) { @@ -150,6 +162,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 217c7c2034073..889d81448a94c 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; @@ -155,6 +156,10 @@ private function getDefinitionId($id) 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 7c394695a5aa3..a015b2e2e94e5 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(); private $inlinedServiceIds = array(); /** @@ -58,18 +60,44 @@ 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)); - $this->inlinedServiceIds[$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)); + $this->inlinedServiceIds[$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/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 7e53edd9ddd8d..14af37ef78ac4 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -174,7 +174,7 @@ public function set($id, $service) } if (isset($this->privates[$id]) || !(isset($this->fileMap[$id]) || isset($this->methodMap[$id]))) { - if (isset($this->syntheticIds[$id]) || (!isset($this->privates[$id]) && !isset($this->getRemovedIds()[$id]))) { + if (!isset($this->privates[$id]) && !isset($this->getRemovedIds()[$id])) { // no-op } elseif (null === $service) { @trigger_error(sprintf('The "%s" service is private, unsetting it is deprecated since Symfony 3.2 and will fail in 4.0.', $id), E_USER_DEPRECATED); @@ -298,6 +298,7 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE } elseif (isset($this->methodMap[$id])) { return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->{$this->methodMap[$id]}(); } elseif (--$i && $id !== $normalizedId = $this->normalizeId($id)) { + unset($this->loading[$id]); $id = $normalizedId; continue; } elseif (!$this->methodMap && !$this instanceof ContainerBuilder && __CLASS__ !== static::class && method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) { diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index b96426d487092..086bfdafe2c18 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -556,15 +556,8 @@ public function has($id) */ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { - if ($this->isCompiled()) { - $id = $this->normalizeId($id); - - if (isset($this->definitions[$id]) && $this->definitions[$id]->isPrivate()) { - @trigger_error(sprintf('Fetching the "%s" private service is deprecated and will fail in Symfony 4.0. Make the service public instead.', $id), E_USER_DEPRECATED); - } - if (isset($this->aliasDefinitions[$id]) && $this->aliasDefinitions[$id]->isPrivate()) { - @trigger_error(sprintf('Fetching the "%s" private alias is deprecated and will fail in Symfony 4.0. Make the alias public instead.', $id), E_USER_DEPRECATED); - } + if ($this->isCompiled() && isset($this->removedIds[$id = $this->normalizeId($id)])) { + @trigger_error(sprintf('Fetching the "%s" private service or alias is deprecated since Symfony 3.4 and will fail in 4.0. Make it public instead.', $id), E_USER_DEPRECATED); } return $this->doGet($id, $invalidBehavior); @@ -776,6 +769,12 @@ public function compile(/*$resolveEnvPlaceholders = false*/) } parent::compile(); + + foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) { + if (!$definition->isPublic() || $definition->isPrivate()) { + $this->removedIds[$id] = true; + } + } } /** diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index fb933ce757f5a..0f5e33afb6da9 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -217,12 +217,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']}(); @@ -428,13 +432,13 @@ private function addServiceInclude($cId, Definition $definition, \SplObjectStora } 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)); } } @@ -478,7 +482,7 @@ private function addServiceInlinedDefinitions($id, Definition $definition, \SplO // $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)); } @@ -1233,7 +1237,7 @@ private function addInlineRequires() 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 ade932add1406..c61acdf065183 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 1984f1b2c7cb4..adaa4044f453c 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 bbc2c01ef075a..cf0a0a5627a27 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()); @@ -1263,6 +1264,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/ContainerTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php index 8035de6230882..84271ab02667b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php @@ -538,6 +538,16 @@ public function testReplacingAPreDefinedServiceIsDeprecated() $this->assertSame($bar, $c->get('bar'), '->set() replaces a pre-defined service'); } + + /** + * @group legacy + * @expectedDeprecation The "synthetic" service is private, replacing it is deprecated since Symfony 3.2 and will fail in 4.0. + */ + public function testSetWithPrivateSyntheticServiceThrowsDeprecation() + { + $c = new ProjectServiceContainer(); + $c->set('synthetic', new \stdClass()); + } } class ProjectServiceContainer extends Container @@ -565,8 +575,12 @@ public function __construct() $this->__foo_bar = new \stdClass(); $this->__foo_baz = new \stdClass(); $this->__internal = new \stdClass(); - $this->privates = array('internal' => true); + $this->privates = array( + 'internal' => true, + 'synthetic' => true, + ); $this->aliases = array('alias' => 'bar'); + $this->syntheticIds['synthetic'] = true; } protected function getInternalService() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index b70dc7b172b28..7455dd6cde681 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -782,6 +782,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/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php index 30b94d456ef6f..467733f467d6c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php @@ -297,7 +297,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/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt index 6724846f8dd8e..59ace1ddf3a38 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 @@ -9,8 +9,10 @@ return array( 'configurator_service_simple' => true, 'decorated.pif-pouf' => true, 'decorator_service.inner' => true, + 'factory_simple' => true, 'inlined' => true, 'new_factory' => true, + 'tagged_iterator_foo' => true, ); [Container%s/getBazService.php] => targetDirs[0].'/Fixtures/includes/foo.php'); +include_once ($this->targetDirs[0].'/Fixtures/includes/foo.php'); $this->services['method_call1'] = $instance = new \Bar\FooClass(); @@ -471,12 +473,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 ec0db041f4381..424bb1dbfc630 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -70,8 +70,10 @@ public function getRemovedIds() 'configurator_service_simple' => true, 'decorated.pif-pouf' => true, 'decorator_service.inner' => true, + 'factory_simple' => true, 'inlined' => true, 'new_factory' => true, + 'tagged_iterator_foo' => true, ); } @@ -307,7 +309,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 1707f8912781f..ea0e155f30735 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 @@ -27,6 +27,7 @@ public function __construct() 'bar3' => 'getBar3Service', 'foo' => 'getFooService', 'foo2' => 'getFoo2Service', + 'foo5' => 'getFoo5Service', 'foobar4' => 'getFoobar4Service', ); @@ -39,6 +40,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 +127,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(${($_ = isset($this->services['foo5']) ? $this->services['foo5'] : $this->getFoo5Service()) && false ?: '_'}); + + $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 5d4d9b0f29cf9..36961d1c853d5 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 @@ -25,9 +25,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 +95,26 @@ protected function getBar3Service() return $instance; } + /** + * Gets the public 'bar5' shared service. + * + * @return \stdClass + */ + protected function getBar5Service() + { + $a = ${($_ = isset($this->services['foo5']) ? $this->services['foo5'] : $this->getFoo5Service()) && false ?: '_'}; + + 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 +161,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 = ${($_ = isset($this->services['bar5']) ? $this->services['bar5'] : $this->getBar5Service()) && false ?: '_'}; + + return $instance; + } + /** * Gets the public 'foobar' shared service. * 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 e122069a40990..7649ccd4572e0 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 @@ -44,6 +44,7 @@ public function getRemovedIds() return array( 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + 'bar_%env(BAR)%' => true, 'baz_%env(BAR)%' => true, ); } 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 c888dc997d604..3b45218bd9b9a 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 @@ -46,10 +46,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 getRemovedIds() @@ -57,6 +57,7 @@ public function getRemovedIds() return array( 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C3' => true, ); } @@ -104,8 +105,8 @@ protected function getC1Service() */ protected function getC2Service() { - require_once $this->targetDirs[1].'/includes/HotPath/C2.php'; - require_once $this->targetDirs[1].'/includes/HotPath/C3.php'; + include_once $this->targetDirs[1].'/includes/HotPath/C2.php'; + include_once $this->targetDirs[1].'/includes/HotPath/C3.php'; return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3()) && false ?: '_'}); } @@ -117,7 +118,7 @@ protected function getC2Service() */ protected function getC3Service() { - require_once $this->targetDirs[1].'/includes/HotPath/C3.php'; + include_once $this->targetDirs[1].'/includes/HotPath/C3.php'; return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3(); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php index f8c33ae59966a..03947c425df79 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php @@ -60,9 +60,17 @@ public function getRemovedIds() return array( 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + 'decorated_private' => true, + 'decorated_private_alias' => true, 'foo' => true, + 'private' => true, + 'private_alias' => true, 'private_alias_decorator.inner' => true, + 'private_child' => true, 'private_decorator.inner' => true, + 'private_not_inlined' => true, + 'private_not_removed' => true, + 'private_parent' => true, ); } 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 cd8456be3afcf..2ea323954fa5b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php @@ -45,6 +45,7 @@ public function getRemovedIds() return array( 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + 'baz_service' => true, 'translator.loader_1_locator' => true, 'translator.loader_2_locator' => true, 'translator.loader_3_locator' => true, 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 59857ecac3ef8..7d1c429826a98 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 @@ -39,6 +39,7 @@ public function getRemovedIds() return array( 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + 'baz_service' => true, ); } 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 70af6ce7e3661..abf7271f48a52 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 @@ -39,6 +39,7 @@ public function getRemovedIds() 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'private_bar' => true, + 'private_foo' => true, ); } 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 76d87489a98b6..967d8012680bc 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php @@ -43,6 +43,7 @@ public function getRemovedIds() return array( 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => true, 'service_locator.jmktfsv' => true, ); } 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 df5a40c839749..832530bd7d29d 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 @@ -41,6 +41,7 @@ public function getRemovedIds() 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'foo2' => true, + 'foo3' => true, ); } 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 7afb274b4d27e..f9836b8ac066e 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -32,7 +32,7 @@ "symfony/http-kernel": "^3.3.5|~4.0", "symfony/security-csrf": "~2.8|~3.0|~4.0", "symfony/translation": "~2.8|~3.0|~4.0", - "symfony/var-dumper": "~3.3.11|~3.4-beta3|~4.0-beta3", + "symfony/var-dumper": "~3.3.11|~3.4|~4.0", "symfony/console": "~3.4|~4.0" }, "conflict": { diff --git a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php index a15091a446125..75355084d2608 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php @@ -94,7 +94,7 @@ protected function instantiateController($class) } catch (\TypeError $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 f9730b1485045..d0ca671e056b4 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 bbce191552b7d..743140dab0ed3 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -67,11 +67,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '3.4.0'; - const VERSION_ID = 30400; + const VERSION = '3.4.1'; + const VERSION_ID = 30401; const MAJOR_VERSION = 3; const MINOR_VERSION = 4; - const RELEASE_VERSION = 0; + const RELEASE_VERSION = 1; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '11/2020'; @@ -423,7 +423,7 @@ public function setClassCache(array $classes) */ 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) { @@ -590,7 +596,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])) { @@ -617,23 +623,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(); } @@ -649,13 +659,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 4f77818f12f99..83217aef558fc 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 6dee293ea9de2..243529ed20a2e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -833,7 +833,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/Security/Csrf/composer.json b/src/Symfony/Component/Security/Csrf/composer.json index 72bef3805e2f6..5b27fee3a4591 100644 --- a/src/Symfony/Component/Security/Csrf/composer.json +++ b/src/Symfony/Component/Security/Csrf/composer.json @@ -22,10 +22,10 @@ "symfony/security-core": "~2.8|~3.0|~4.0" }, "require-dev": { - "symfony/http-foundation": "^2.8.31|~3.3.13|~3.4-beta5|~4.0-beta5" + "symfony/http-foundation": "^2.8.31|~3.3.13|~3.4|~4.0" }, "conflict": { - "symfony/http-foundation": "<2.8.31|~3.3,<3.3.13|~3.4,<3.4-beta5|~4.0,<4.0-beta5" + "symfony/http-foundation": "<2.8.31|~3.3,<3.3.13" }, "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 7fee5322a24b9..679208ca096b8 100644 --- a/src/Symfony/Component/Security/composer.json +++ b/src/Symfony/Component/Security/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^5.5.9|>=7.0.8", "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "^2.8.31|~3.3.13|~3.4-beta5|~4.0-beta5", + "symfony/http-foundation": "^2.8.31|~3.3.13|~3.4|~4.0", "symfony/http-kernel": "~3.3|~4.0", "symfony/polyfill-php56": "~1.0", "symfony/polyfill-php70": "~1.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 5f8571b329a09..e366407638594 100644 --- a/src/Symfony/Component/Workflow/Definition.php +++ b/src/Symfony/Component/Workflow/Definition.php @@ -82,7 +82,7 @@ private function setInitialPlace($place) private 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/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 fb4786c88a267..f673517f32e6c 100644 --- a/src/Symfony/Component/Workflow/Transition.php +++ b/src/Symfony/Component/Workflow/Transition.php @@ -30,7 +30,7 @@ class Transition */ public function __construct($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 1d172341edcac..ddab3990fd4bf 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -637,21 +637,10 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false) 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(); @@ -749,6 +738,8 @@ private function parseValue($value, $flags, $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()) { @@ -757,7 +748,7 @@ private function parseValue($value, $flags, $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) { @@ -765,6 +756,21 @@ private function parseValue($value, $flags, $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 c0a647eca783c..f58bc41a5afd1 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -1561,6 +1561,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 = << pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy