diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 8561391dc9..416603f3df 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -237,7 +237,7 @@ Validator WebProfilerBundle ----------------- - * Deprecated the `ExceptionController::templateExists()` method + * Deprecated the `ExceptionController` class in favor of `ExceptionErrorController` * Deprecated the `TemplateManager::templateExists()` method WebServerBundle diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 89c11993da..5650d54ec1 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -604,6 +604,11 @@ Yaml * The parser is now stricter and will throw a `ParseException` when a mapping is found inside a multi-line string. +WebProfilerBundle +----------------- + + * Removed the `ExceptionController` class, use `ExceptionErrorController` instead. + WebServerBundle --------------- diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 0a9bf739fc..90906ef6e0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -848,7 +848,7 @@ class EntityTypeTest extends BaseTypeTest ]); $this->assertEquals([3 => new ChoiceView($entity3, '3', 'Baz'), 2 => new ChoiceView($entity2, '2', 'Bar')], $field->createView()->vars['preferred_choices']); - $this->assertEquals([1 => new ChoiceView($entity1, '1', 'Foo')], $field->createView()->vars['choices']); + $this->assertEquals([1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar'), 3 => new ChoiceView($entity3, '3', 'Baz')], $field->createView()->vars['choices']); } public function testOverrideChoicesWithPreferredChoices() @@ -868,7 +868,7 @@ class EntityTypeTest extends BaseTypeTest ]); $this->assertEquals([3 => new ChoiceView($entity3, '3', 'Baz')], $field->createView()->vars['preferred_choices']); - $this->assertEquals([2 => new ChoiceView($entity2, '2', 'Bar')], $field->createView()->vars['choices']); + $this->assertEquals([2 => new ChoiceView($entity2, '2', 'Bar'), 3 => new ChoiceView($entity3, '3', 'Baz')], $field->createView()->vars['choices']); } public function testDisallowChoicesThatAreNotIncludedChoicesSingleIdentifier() diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php index 6f856d1f41..f00b8d17ad 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php @@ -524,8 +524,9 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][.="[trans]Choice&B[/trans]"] ] - [count(./option)=3] + [count(./option)=4] ' ); } @@ -547,8 +548,9 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest [ ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][.="[trans]Choice&B[/trans]"] ] - [count(./option)=2] + [count(./option)=3] ' ); } @@ -571,8 +573,9 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@disabled="disabled"][not(@selected)][.=""] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][.="[trans]Choice&B[/trans]"] ] - [count(./option)=3] + [count(./option)=4] ' ); } @@ -589,7 +592,7 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], '/select [@class="my&class form-control"] - [count(./option)=2] + [count(./option)=5] ' ); } diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index ff0c85ff91..9ebae1d77c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -13,6 +13,8 @@ CHANGELOG * Added button to clear the ajax request tab * Deprecated the `ExceptionController::templateExists()` method * Deprecated the `TemplateManager::templateExists()` method + * Deprecated the `ExceptionController` in favor of `ExceptionErrorController` + * Marked all classes of the WebProfilerBundle as internal 4.3.0 ----- diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php index 10ee2ef05f..2fc1e7b023 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php @@ -18,10 +18,15 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Profiler\Profiler; use Twig\Environment; +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ExceptionController::class, ExceptionErrorController::class), E_USER_DEPRECATED); + /** * ExceptionController. * * @author Fabien Potencier + * + * @deprecated since Symfony 4.4, use the ExceptionErrorController instead. + * @internal since Symfony 4.4 */ class ExceptionController { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionErrorController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionErrorController.php new file mode 100644 index 0000000000..c0833067cf --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionErrorController.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Controller; + +use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +/** + * Renders the exception panel. + * + * @author Yonel Ceruto + */ +class ExceptionErrorController +{ + private $htmlErrorRenderer; + private $profiler; + + public function __construct(HtmlErrorRenderer $htmlErrorRenderer, ?Profiler $profiler) + { + $this->htmlErrorRenderer = $htmlErrorRenderer; + $this->profiler = $profiler; + } + + /** + * Renders the exception panel stacktrace for the given token. + */ + public function body(string $token): Response + { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + + $exception = $this->profiler->loadProfile($token) + ->getCollector('exception') + ->getException() + ; + + return new Response($this->htmlErrorRenderer->getBody($exception)); + } + + /** + * Renders the exception panel stylesheet. + */ + public function stylesheet(): Response + { + return new Response($this->htmlErrorRenderer->getStylesheet()); + } +} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index 6110f0f2b9..be067b7af2 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -24,6 +24,8 @@ use Twig\Environment; /** * @author Fabien Potencier + * + * @internal since Symfony 4.4 */ class ProfilerController { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php index 95850fae0c..907ab79eed 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php @@ -26,6 +26,8 @@ use Twig\Environment; * RouterController. * * @author Fabien Potencier + * + * @internal since Symfony 4.4 */ class RouterController { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php b/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php index 4d960c6b17..48f8a758fb 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php @@ -21,6 +21,8 @@ use Twig\Environment; * * @author Fabien Potencier * @author Artur Wielogórski + * + * @internal since Symfony 4.4 */ class TemplateManager { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml index dcacc51032..962e1418bd 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml @@ -27,7 +27,13 @@ %kernel.debug% - + + The "%service_id%" service is deprecated since Symfony 4.4, use the "web_profiler.controller.exception_error" service instead. + + + + + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml index 0bc9a9ec4f..0eb4ea72ff 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/routing/profiler.xml @@ -37,11 +37,11 @@ - web_profiler.controller.exception::showAction + web_profiler.controller.exception_error::body - web_profiler.controller.exception::cssAction + web_profiler.controller.exception_error::stylesheet diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig index 78752853b9..ea028e026f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig @@ -1,5 +1,3 @@ -{{ include('@Twig/exception.css.twig') }} - .container { max-width: auto; margin: 0; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig index 94dfbb6aca..261d5cc2b1 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig @@ -4,6 +4,7 @@ {% if collector.hasexception %} {% endif %} {{ parent() }} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig index 4ca49e7c5f..14df36be28 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -663,7 +663,7 @@ {% else %}
-

No options where passed when constructing this form.

+

No options were passed when constructing this form.

{% endif %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig index 779f1259ed..6f2af8dd45 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/messenger.html.twig @@ -119,8 +119,8 @@ exception {% endif %} - {{ include('@Twig/images/icon-minus-square.svg') }} - {{ include('@Twig/images/icon-plus-square.svg') }} + {{ include('@WebProfiler/images/icon-minus-square.svg') }} + {{ include('@WebProfiler/images/icon-plus-square.svg') }} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig index 580b3b5b0e..0b13f57509 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base.html.twig @@ -4,7 +4,7 @@ - Symfony Profiler + {% block title %}Symfony Profiler{% endblock %} {% block head %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig index dd09415568..6669cd721f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -54,6 +54,7 @@ text-align: left; text-transform: none; z-index: 99999; + direction: ltr; /* neutralize the aliasing defined by external CSS styles */ -webkit-font-smoothing: subpixel-antialiased; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_redirect.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_redirect.html.twig index 35b6e90eb5..18d43b2253 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_redirect.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_redirect.html.twig @@ -1,4 +1,4 @@ -{% extends '@Twig/layout.html.twig' %} +{% extends '@WebProfiler/Profiler/base.html.twig' %} {% block title 'Redirection Intercepted' %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/images/icon-minus-square.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/images/icon-minus-square.svg new file mode 100644 index 0000000000..471c2741c7 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/images/icon-minus-square.svg @@ -0,0 +1 @@ + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/images/icon-plus-square.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/images/icon-plus-square.svg new file mode 100644 index 0000000000..2f5c3b3583 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/images/icon-plus-square.svg @@ -0,0 +1 @@ + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php index cfbee00bd0..c2ac0a75ef 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php @@ -54,7 +54,7 @@ class WebProfilerExtensionTest extends TestCase $this->kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\KernelInterface')->getMock(); $this->container = new ContainerBuilder(); - $this->container->register('error_renderer.renderer.html', HtmlErrorRenderer::class); + $this->container->register('error_renderer.renderer.html', HtmlErrorRenderer::class)->setPublic(true); $this->container->register('event_dispatcher', EventDispatcher::class)->setPublic(true); $this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'))->setPublic(true); $this->container->register('twig', 'Twig\Environment')->setPublic(true); @@ -92,10 +92,11 @@ class WebProfilerExtensionTest extends TestCase $extension = new WebProfilerExtension(); $extension->load([[]], $this->container); + $this->container->removeDefinition('web_profiler.controller.exception'); $this->assertFalse($this->container->has('web_profiler.debug_toolbar')); - $this->assertSaneContainer($this->getCompiledContainer()); + self::assertSaneContainer($this->getCompiledContainer()); } /** @@ -105,10 +106,11 @@ class WebProfilerExtensionTest extends TestCase { $extension = new WebProfilerExtension(); $extension->load([['toolbar' => $toolbarEnabled, 'intercept_redirects' => $interceptRedirects]], $this->container); + $this->container->removeDefinition('web_profiler.controller.exception'); $this->assertSame($listenerInjected, $this->container->has('web_profiler.debug_toolbar')); - $this->assertSaneContainer($this->getCompiledContainer(), '', ['web_profiler.csp.handler']); + self::assertSaneContainer($this->getCompiledContainer(), '', ['web_profiler.csp.handler']); if ($listenerInjected) { $this->assertSame($listenerEnabled, $this->container->get('web_profiler.debug_toolbar')->isEnabled()); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php b/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php index b14eb362c8..d5689701f4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php @@ -22,6 +22,8 @@ use Twig\TwigFunction; * Twig extension for the profiler. * * @author Fabien Potencier + * + * @internal since Symfony 4.4 */ class WebProfilerExtension extends ProfilerExtension { diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index 2efda358c0..f565050158 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -18,6 +18,7 @@ "require": { "php": "^7.2.9", "symfony/config": "^4.4|^5.0", + "symfony/error-renderer": "^4.4|^5.0", "symfony/http-kernel": "^4.4|^5.0", "symfony/routing": "^4.4|^5.0", "symfony/twig-bundle": "^4.4|^5.0", diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 72513d0db9..be864ea51d 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -22,7 +22,9 @@ CHANGELOG 4.4.0 ----- + * preferred choices are repeated in the list of all choices * deprecated using `int` or `float` as data for the `NumberType` when the `input` option is set to `string` + * The type guesser guesses the HTML accept attribute when a mime type is configured in the File or Image constraint. 4.3.0 ----- diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index 68da1b2516..7fe1f46247 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -157,9 +157,9 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface if ($isPreferred && false !== $preferredKey = $isPreferred($choice, $key, $value)) { $preferredViews[$nextIndex] = $view; $preferredViewsOrder[$nextIndex] = $preferredKey; - } else { - $otherViews[$nextIndex] = $view; } + + $otherViews[$nextIndex] = $view; } private static function addChoiceViewsFromStructuredValues($values, $label, $choices, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$preferredViewsOrder, &$otherViews) diff --git a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php index 22cc7726d4..6dd15d7e69 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php +++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php @@ -122,7 +122,12 @@ class ValidatorTypeGuesser implements FormTypeGuesserInterface case 'Symfony\Component\Validator\Constraints\File': case 'Symfony\Component\Validator\Constraints\Image': - return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\FileType', [], Guess::HIGH_CONFIDENCE); + $options = []; + if ($constraint->mimeTypes) { + $options = ['attr' => ['accept' => implode(',', (array) $constraint->mimeTypes)]]; + } + + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\FileType', $options, Guess::HIGH_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Language': return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\LanguageType', [], Guess::HIGH_CONFIDENCE); diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 16344f9149..d322d3ff8b 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -741,8 +741,9 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][.="[trans]Choice&B[/trans]"] ] - [count(./option)=3] + [count(./option)=4] ' ); } @@ -763,8 +764,9 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase [ ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][.="[trans]Choice&B[/trans]"] ] - [count(./option)=2] + [count(./option)=3] ' ); } @@ -786,8 +788,9 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@disabled="disabled"][not(@selected)][.=""] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][.="[trans]Choice&B[/trans]"] ] - [count(./option)=3] + [count(./option)=4] ' ); } @@ -803,7 +806,7 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase $this->assertWidgetMatchesXpath($form->createView(), [], '/select - [count(./option)=2] + [count(./option)=5] ' ); } diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index b065718054..7073890d6b 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -739,6 +739,8 @@ class DefaultChoiceListFactoryTest extends TestCase $this->assertEquals(new ChoiceListView( [ 0 => new ChoiceView($this->obj1, '0', 'A'), + 1 => new ChoiceView($this->obj2, '1', 'B'), + 2 => new ChoiceView($this->obj3, '2', 'C'), 3 => new ChoiceView($this->obj4, '3', 'D'), ], [ 1 => new ChoiceView($this->obj2, '1', 'B'), @@ -752,6 +754,8 @@ class DefaultChoiceListFactoryTest extends TestCase $this->assertEquals(new ChoiceListView( [ 'w' => new ChoiceView($this->obj1, '0', 'A'), + 'x' => new ChoiceView($this->obj2, '1', 'B'), + 'y' => new ChoiceView($this->obj3, '2', 'C'), 'z' => new ChoiceView($this->obj4, '3', 'D'), ], [ 'x' => new ChoiceView($this->obj2, '1', 'B'), @@ -765,6 +769,18 @@ class DefaultChoiceListFactoryTest extends TestCase $this->assertEquals(new ChoiceListView( [ 0 => new ChoiceView($this->obj1, '0', 'A'), + 1 => new ChoiceView( + $this->obj2, + '1', + 'B', + ['attr1' => 'value1'] + ), + 2 => new ChoiceView( + $this->obj3, + '2', + 'C', + ['attr2' => 'value2'] + ), 3 => new ChoiceView($this->obj4, '3', 'D'), ], [ 1 => new ChoiceView( @@ -789,11 +805,17 @@ class DefaultChoiceListFactoryTest extends TestCase [ 'Group 1' => new ChoiceGroupView( 'Group 1', - [0 => new ChoiceView($this->obj1, '0', 'A')] + [ + 0 => new ChoiceView($this->obj1, '0', 'A'), + 1 => new ChoiceView($this->obj2, '1', 'B'), + ] ), 'Group 2' => new ChoiceGroupView( 'Group 2', - [3 => new ChoiceView($this->obj4, '3', 'D')] + [ + 2 => new ChoiceView($this->obj3, '2', 'C'), + 3 => new ChoiceView($this->obj4, '3', 'D'), + ] ), ], [ 'Group 1' => new ChoiceGroupView( diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 83633eed0d..b9ecf57583 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -1731,7 +1731,9 @@ class ChoiceTypeTest extends BaseTypeTest $this->assertEquals([ 0 => new ChoiceView('a', 'a', 'A'), + 1 => new ChoiceView('b', 'b', 'B'), 2 => new ChoiceView('c', 'c', 'C'), + 3 => new ChoiceView('d', 'd', 'D'), ], $view->vars['choices']); $this->assertEquals([ 1 => new ChoiceView('b', 'b', 'B'), @@ -1750,9 +1752,11 @@ class ChoiceTypeTest extends BaseTypeTest $this->assertEquals([ 'Symfony' => new ChoiceGroupView('Symfony', [ 0 => new ChoiceView('a', 'a', 'Bernhard'), + 1 => new ChoiceView('b', 'b', 'Fabien'), 2 => new ChoiceView('c', 'c', 'Kris'), ]), 'Doctrine' => new ChoiceGroupView('Doctrine', [ + 3 => new ChoiceView('d', 'd', 'Jon'), 4 => new ChoiceView('e', 'e', 'Roman'), ]), ], $view->vars['choices']); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php index ab30bc77a6..89b7a85c3f 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser; use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\ValueGuess; use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Constraints\File; use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; @@ -107,6 +108,43 @@ class ValidatorTypeGuesserTest extends TestCase $this->assertNull($result); } + public function testGuessMimeTypesForConstraintWithMimeTypesValue() + { + $mineTypes = ['image/png', 'image/jpeg']; + $constraint = new File(['mimeTypes' => $mineTypes]); + $typeGuess = $this->guesser->guessTypeForConstraint($constraint); + $this->assertInstanceOf('Symfony\Component\Form\Guess\TypeGuess', $typeGuess); + $this->assertArrayHasKey('attr', $typeGuess->getOptions()); + $this->assertArrayHasKey('accept', $typeGuess->getOptions()['attr']); + $this->assertEquals(implode(',', $mineTypes), $typeGuess->getOptions()['attr']['accept']); + } + + public function testGuessMimeTypesForConstraintWithoutMimeTypesValue() + { + $constraint = new File(); + $typeGuess = $this->guesser->guessTypeForConstraint($constraint); + $this->assertInstanceOf('Symfony\Component\Form\Guess\TypeGuess', $typeGuess); + $this->assertArrayNotHasKey('attr', $typeGuess->getOptions()); + } + + public function testGuessMimeTypesForConstraintWithMimeTypesStringValue() + { + $constraint = new File(['mimeTypes' => 'image/*']); + $typeGuess = $this->guesser->guessTypeForConstraint($constraint); + $this->assertInstanceOf('Symfony\Component\Form\Guess\TypeGuess', $typeGuess); + $this->assertArrayHasKey('attr', $typeGuess->getOptions()); + $this->assertArrayHasKey('accept', $typeGuess->getOptions()['attr']); + $this->assertEquals('image/*', $typeGuess->getOptions()['attr']['accept']); + } + + public function testGuessMimeTypesForConstraintWithMimeTypesEmptyStringValue() + { + $constraint = new File(['mimeTypes' => '']); + $typeGuess = $this->guesser->guessTypeForConstraint($constraint); + $this->assertInstanceOf('Symfony\Component\Form\Guess\TypeGuess', $typeGuess); + $this->assertArrayNotHasKey('attr', $typeGuess->getOptions()); + } + public function maxLengthTypeProvider() { return [ diff --git a/src/Symfony/Component/HttpClient/HttplugClient.php b/src/Symfony/Component/HttpClient/HttplugClient.php index 6c612ce13c..71eb5200ce 100644 --- a/src/Symfony/Component/HttpClient/HttplugClient.php +++ b/src/Symfony/Component/HttpClient/HttplugClient.php @@ -100,7 +100,13 @@ final class HttplugClient implements HttpClient, RequestFactory, StreamFactory, } if (\is_string($body ?? '')) { - return $this->client->createStream($body ?? ''); + $body = $this->client->createStream($body ?? ''); + + if ($body->isSeekable()) { + $body->seek(0); + } + + return $body; } if (\is_resource($body)) { diff --git a/src/Symfony/Component/HttpClient/Psr18Client.php b/src/Symfony/Component/HttpClient/Psr18Client.php index ee8c813b46..02c24f5562 100644 --- a/src/Symfony/Component/HttpClient/Psr18Client.php +++ b/src/Symfony/Component/HttpClient/Psr18Client.php @@ -78,9 +78,15 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str public function sendRequest(RequestInterface $request): ResponseInterface { try { + $body = $request->getBody(); + + if ($body->isSeekable()) { + $body->seek(0); + } + $response = $this->client->request($request->getMethod(), (string) $request->getUri(), [ 'headers' => $request->getHeaders(), - 'body' => (string) $request->getBody(), + 'body' => $body->getContents(), 'http_version' => '1.0' === $request->getProtocolVersion() ? '1.0' : null, ]); @@ -93,8 +99,13 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str } $body = isset(class_uses($response)[ResponseTrait::class]) ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client); + $body = $this->streamFactory->createStreamFromResource($body); - return $psrResponse->withBody($this->streamFactory->createStreamFromResource($body)); + if ($body->isSeekable()) { + $body->seek(0); + } + + return $psrResponse->withBody($body); } catch (TransportExceptionInterface $e) { if ($e instanceof \InvalidArgumentException) { throw new Psr18RequestException($e, $request); @@ -125,7 +136,13 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str */ public function createStream(string $content = ''): StreamInterface { - return $this->streamFactory->createStream($content); + $stream = $this->streamFactory->createStream($content); + + if ($stream->isSeekable()) { + $stream->seek(0); + } + + return $stream; } /** diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php index 47fc084d37..90bb0df339 100644 --- a/src/Symfony/Component/HttpClient/Response/MockResponse.php +++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php @@ -78,6 +78,15 @@ class MockResponse implements ResponseInterface return null !== $type ? $this->info[$type] ?? null : $this->info; } + /** + * {@inheritdoc} + */ + public function cancel(): void + { + $this->info['error'] = 'Response has been canceled.'; + $this->body = null; + } + /** * {@inheritdoc} */ @@ -150,8 +159,11 @@ class MockResponse implements ResponseInterface foreach ($responses as $response) { $id = $response->id; - if (!$response->body) { - // Last chunk + if (null === $response->body) { + // Canceled response + $response->body = []; + } elseif ([] === $response->body) { + // Error chunk $multi->handlesActivity[$id][] = null; $multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; } elseif (null === $chunk = array_shift($response->body)) { @@ -242,7 +254,7 @@ class MockResponse implements ResponseInterface // populate info related to headers $info = $mock->getInfo() ?: []; - $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode(false) ?: 200; + $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200; $response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers); $dlSize = isset($response->headers['content-encoding']) ? 0 : (int) ($response->headers['content-length'][0] ?? 0); diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php index ed7281e1be..4be2706003 100644 --- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php @@ -349,7 +349,7 @@ trait ResponseTrait unset($multi->handlesActivity[$j]); - if ($chunk instanceof FirstChunk && null === $response->initializer) { + if ($chunk instanceof FirstChunk && null === $response->initializer && null === $response->info['error']) { // Ensure the HTTP status code is always checked $response->getHeaders(true); } elseif ($chunk instanceof ErrorChunk && !$chunk->didThrow()) { diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php index 02453719a1..91676d3e58 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php @@ -33,7 +33,7 @@ class DayTransformer extends Transformer */ public function getReverseMatchingRegExp(int $length): string { - return 1 === $length ? '\d{1,2}' : '\d{'.$length.'}'; + return 1 === $length ? '\d{1,2}' : '\d{1,'.$length.'}'; } /** diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php index f66ffac481..fcc4c4fee9 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php @@ -104,7 +104,7 @@ class MonthTransformer extends Transformer $regExp = '[JFMASOND]'; break; default: - $regExp = '\d{'.$length.'}'; + $regExp = '\d{1,'.$length.'}'; break; } diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php index 51c2eca827..8718094c06 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php @@ -37,7 +37,7 @@ class YearTransformer extends Transformer */ public function getReverseMatchingRegExp(int $length): string { - return 2 === $length ? '\d{2}' : '\d{4}'; + return 2 === $length ? '\d{2}' : '\d{1,4}'; } /** diff --git a/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php b/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php index b2caafa27a..110fd63f7a 100644 --- a/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php @@ -600,6 +600,7 @@ abstract class AbstractIntlDateFormatterTest extends TestCase { return [ ['y-M-d', '1970-1-1', 0], + ['y-MM-d', '1970-1-1', 0], ['y-MMM-d', '1970-Jan-1', 0], ['y-MMMM-d', '1970-January-1', 0], ]; @@ -618,6 +619,7 @@ abstract class AbstractIntlDateFormatterTest extends TestCase { return [ ['y-M-d', '1970-1-1', 0], + ['y-M-dd', '1970-1-1', 0], ['y-M-dd', '1970-1-01', 0], ['y-M-ddd', '1970-1-001', 0], ]; diff --git a/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php b/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php index 9905e326e0..cda1935eb5 100644 --- a/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php @@ -183,6 +183,17 @@ class IntlDateFormatterTest extends AbstractIntlDateFormatterTest return $this->notImplemented(parent::parseQuarterProvider()); } + public function testParseThreeDigitsYears() + { + if (PHP_INT_SIZE < 8) { + $this->markTestSkipped('Parsing three digits years requires a 64bit PHP.'); + } + + $formatter = $this->getDefaultDateFormatter('yyyy-M-d'); + $this->assertSame(-32157648000, $formatter->parse('950-12-19')); + $this->assertIsIntlSuccess($formatter, 'U_ZERO_ERROR', IntlGlobals::U_ZERO_ERROR); + } + protected function getDateFormatter($locale, $datetype, $timetype, $timezone = null, $calendar = IntlDateFormatter::GREGORIAN, $pattern = null) { return new IntlDateFormatter($locale, $datetype, $timetype, $timezone, $calendar, $pattern); diff --git a/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php b/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php index 8c7f3cbadb..ea32e06cde 100644 --- a/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php +++ b/src/Symfony/Component/Messenger/DataCollector/MessengerDataCollector.php @@ -79,6 +79,19 @@ class MessengerDataCollector extends DataCollector implements LateDataCollectorI } } + /** + * {@inheritdoc} + */ + protected function getCasters() + { + $casters = parent::getCasters(); + + // Unset the default caster truncating collectors data. + unset($casters['*']); + + return $casters; + } + private function collectMessage(string $busName, array $tracedMessage) { $message = $tracedMessage['message']; diff --git a/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php b/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php index 13bf06b012..1b03e0036b 100644 --- a/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php +++ b/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php @@ -62,7 +62,7 @@ class TraceableMessageBusTest extends TestCase unset($actualTracedMessage['callTime']); // don't check, too variable $this->assertEquals([ 'message' => $message, - 'stamps' => [[$stamp]], + 'stamps' => [$stamp], 'caller' => [ 'name' => 'TraceableMessageBusTest.php', 'file' => __FILE__, diff --git a/src/Symfony/Component/Messenger/TraceableMessageBus.php b/src/Symfony/Component/Messenger/TraceableMessageBus.php index a83ddee08a..09347bcb2f 100644 --- a/src/Symfony/Component/Messenger/TraceableMessageBus.php +++ b/src/Symfony/Component/Messenger/TraceableMessageBus.php @@ -31,7 +31,7 @@ class TraceableMessageBus implements MessageBusInterface { $envelope = Envelope::wrap($message, $stamps); $context = [ - 'stamps' => array_values($envelope->all()), + 'stamps' => array_merge([], ...array_values($envelope->all())), 'message' => $envelope->getMessage(), 'caller' => $this->getCaller(), 'callTime' => microtime(true), diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php index 02aacaea6d..ad3c9a5b48 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php @@ -43,6 +43,28 @@ class PropertyAccessorCollectionTest_Car } } +class PropertyAccessorCollectionTest_CarOnlyAdder +{ + public function addAxis($axis) + { + } + + public function getAxes() + { + } +} + +class PropertyAccessorCollectionTest_CarOnlyRemover +{ + public function removeAxis($axis) + { + } + + public function getAxes() + { + } +} + class PropertyAccessorCollectionTest_CarNoAdderAndRemover { public function getAxes() @@ -143,25 +165,25 @@ abstract class PropertyAccessorCollectionTest extends PropertyAccessorArrayAcces public function testIsWritableReturnsTrueIfAdderAndRemoverExists() { - $car = $this->getMockBuilder(__CLASS__.'_Car')->getMock(); + $car = new PropertyAccessorCollectionTest_Car(); $this->assertTrue($this->propertyAccessor->isWritable($car, 'axes')); } public function testIsWritableReturnsFalseIfOnlyAdderExists() { - $car = $this->getMockBuilder(__CLASS__.'_CarOnlyAdder')->getMock(); + $car = new PropertyAccessorCollectionTest_CarOnlyAdder(); $this->assertFalse($this->propertyAccessor->isWritable($car, 'axes')); } public function testIsWritableReturnsFalseIfOnlyRemoverExists() { - $car = $this->getMockBuilder(__CLASS__.'_CarOnlyRemover')->getMock(); + $car = new PropertyAccessorCollectionTest_CarOnlyRemover(); $this->assertFalse($this->propertyAccessor->isWritable($car, 'axes')); } public function testIsWritableReturnsFalseIfNoAdderNorRemoverExists() { - $car = $this->getMockBuilder(__CLASS__.'_CarNoAdderAndRemover')->getMock(); + $car = new PropertyAccessorCollectionTest_CarNoAdderAndRemover(); $this->assertFalse($this->propertyAccessor->isWritable($car, 'axes')); } @@ -171,7 +193,7 @@ abstract class PropertyAccessorCollectionTest extends PropertyAccessorArrayAcces */ public function testSetValueFailsIfAdderAndRemoverExistButValueIsNotTraversable() { - $car = $this->getMockBuilder(__CLASS__.'_Car')->getMock(); + $car = new PropertyAccessorCollectionTest_Car(); $this->propertyAccessor->setValue($car, 'axes', 'Not an array or Traversable'); } diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md index bdeb9882cf..9eba061951 100644 --- a/src/Symfony/Component/Routing/CHANGELOG.md +++ b/src/Symfony/Component/Routing/CHANGELOG.md @@ -27,7 +27,7 @@ CHANGELOG Instead of overwriting them, use `__serialize` and `__unserialize` as extension points which are forward compatible with the new serialization methods in PHP 7.4. * exposed `utf8` Route option, defaults "locale" and "format" in configuration loaders and configurators - * added support for invokable route loader services + * added support for invokable service route loaders 4.2.0 ----- diff --git a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php index a7612a595e..4ef3468973 100644 --- a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php @@ -30,7 +30,7 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null) { $cost = $cost ?? 13; - $opsLimit = $opsLimit ?? max(6, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE : 6); + $opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); $memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); if (3 > $opsLimit) { diff --git a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php index c55a486b7c..e63e2d96d3 100644 --- a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php @@ -34,7 +34,7 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.'); } - $this->opsLimit = $opsLimit ?? max(6, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE : 6); + $this->opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); $this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 2014); if (3 > $this->opsLimit) { diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index a3586152b4..af60cb99dc 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -380,7 +380,3 @@ class ArrayDenormalizerDummy implements DenormalizerInterface, SerializerAwareIn $this->serializer = $serializer; } } - -abstract class ObjectSerializerDenormalizer implements SerializerInterface, DenormalizerInterface -{ -} diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php index fdd34b618d..d8645aa726 100644 --- a/src/Symfony/Component/Yaml/Dumper.php +++ b/src/Symfony/Component/Yaml/Dumper.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Yaml; +use Symfony\Component\Yaml\Tag\TaggedValue; + /** * Dumper dumps PHP variables to YAML strings. * @@ -56,7 +58,7 @@ class Dumper $dumpObjectAsInlineMap = empty((array) $input); } - if ($inline <= 0 || (!\is_array($input) && $dumpObjectAsInlineMap) || empty($input)) { + if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) { $output .= $prefix.Inline::dump($input, $flags); } else { $dumpAsMap = Inline::isHash($input); @@ -75,6 +77,19 @@ class Dumper continue; } + if ($value instanceof TaggedValue) { + $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); + + if ($inline - 1 <= 0) { + $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } else { + $output .= "\n"; + $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); + } + + continue; + } + $dumpObjectAsInlineMap = true; if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index d8d544aa28..456d7e9b04 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Yaml\Tests; use PHPUnit\Framework\TestCase; use Symfony\Component\Yaml\Dumper; use Symfony\Component\Yaml\Parser; +use Symfony\Component\Yaml\Tag\TaggedValue; use Symfony\Component\Yaml\Yaml; class DumperTest extends TestCase @@ -368,6 +369,94 @@ outer2: inner2: c inner3: { deep1: d, deep2: e } +YAML; + $this->assertSame($expected, $yaml); + } + + public function testDumpingTaggedValueSequenceRespectsInlineLevel() + { + $data = [ + new TaggedValue('user', [ + 'username' => 'jane', + ]), + new TaggedValue('user', [ + 'username' => 'john', + ]), + ]; + + $yaml = $this->dumper->dump($data, 2); + + $expected = <<assertSame($expected, $yaml); + } + + public function testDumpingTaggedValueSequenceWithInlinedTagValues() + { + $data = [ + new TaggedValue('user', [ + 'username' => 'jane', + ]), + new TaggedValue('user', [ + 'username' => 'john', + ]), + ]; + + $yaml = $this->dumper->dump($data, 1); + + $expected = <<assertSame($expected, $yaml); + } + + public function testDumpingTaggedValueMapRespectsInlineLevel() + { + $data = [ + 'user1' => new TaggedValue('user', [ + 'username' => 'jane', + ]), + 'user2' => new TaggedValue('user', [ + 'username' => 'john', + ]), + ]; + + $yaml = $this->dumper->dump($data, 2); + + $expected = <<assertSame($expected, $yaml); + } + + public function testDumpingTaggedValueMapWithInlinedTagValues() + { + $data = [ + 'user1' => new TaggedValue('user', [ + 'username' => 'jane', + ]), + 'user2' => new TaggedValue('user', [ + 'username' => 'john', + ]), + ]; + + $yaml = $this->dumper->dump($data, 1); + + $expected = <<assertSame($expected, $yaml); } diff --git a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php index c0acd55cea..075f73a2c1 100644 --- a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php +++ b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php @@ -505,6 +505,21 @@ abstract class HttpClientTestCase extends TestCase $response->getHeaders(); } + public function testCancelInStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + foreach ($client->stream($response) as $chunk) { + $response->cancel(); + } + + $this->expectException(TransportExceptionInterface::class); + + foreach ($client->stream($response) as $chunk) { + } + } + public function testOnProgressCancel() { $client = $this->getHttpClient(__FUNCTION__);