From 668f585df416d9481316eb89a75b9b80f59cd47f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 2 Apr 2019 21:03:08 +0200 Subject: [PATCH 01/53] bumped Symfony version to 3.4.25 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index f283f0c7bdb60..0f98fdf0d76d9 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -67,12 +67,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '3.4.24'; - const VERSION_ID = 30424; + const VERSION = '3.4.25-DEV'; + const VERSION_ID = 30425; const MAJOR_VERSION = 3; const MINOR_VERSION = 4; - const RELEASE_VERSION = 24; - const EXTRA_VERSION = ''; + const RELEASE_VERSION = 25; + const EXTRA_VERSION = 'DEV'; const END_OF_MAINTENANCE = '11/2020'; const END_OF_LIFE = '11/2021'; From ce38fe3e58096d12182fe919bd9fd4241c7f5b34 Mon Sep 17 00:00:00 2001 From: Wouter J Date: Tue, 2 Apr 2019 21:50:10 +0200 Subject: [PATCH 02/53] Skip Glob brace test when GLOB_BRACE is unavailable --- src/Symfony/Component/Finder/Tests/FinderTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index aac35bc234195..c0eac6da12b59 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -339,6 +339,10 @@ public function testInWithNonDirectoryGlob() public function testInWithGlobBrace() { + if (!\defined('GLOB_BRACE')) { + $this->markTestSkipped('Glob brace is not supported on this system.'); + } + $finder = $this->buildFinder(); $finder->in([__DIR__.'/Fixtures/{A,copy/A}/B/C'])->getIterator(); From 2e4020c4303b395c1997d4a472602e41a9216a03 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 3 Apr 2019 12:07:21 +0200 Subject: [PATCH 03/53] [HttpKernel] Fix DebugHandlersListener constructor docblock --- .../EventListener/DebugHandlersListener.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index fed9e7e3febe7..df9df09c0bc32 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -19,6 +19,7 @@ use Symfony\Component\Debug\ExceptionHandler; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -40,13 +41,13 @@ class DebugHandlersListener implements EventSubscriberInterface private $hasTerminatedWithException; /** - * @param callable|null $exceptionHandler A handler that will be called on Exception - * @param LoggerInterface|null $logger A PSR-3 logger - * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants - * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value - * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged - * @param string|array $fileLinkFormat The format for links to source files - * @param bool $scope Enables/disables scoping mode + * @param callable|null $exceptionHandler A handler that will be called on Exception + * @param LoggerInterface|null $logger A PSR-3 logger + * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged + * @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files + * @param bool $scope Enables/disables scoping mode */ public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, $throwAt = E_ALL, $scream = true, $fileLinkFormat = null, $scope = true) { From 03987f2ba5eff459f4a4165be367dcebdb4813c1 Mon Sep 17 00:00:00 2001 From: Jaik Dean Date: Fri, 5 Apr 2019 10:10:41 +0100 Subject: [PATCH 04/53] Define null return type for Constraint::getDefaultOption() --- src/Symfony/Component/Validator/Constraint.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/Constraint.php b/src/Symfony/Component/Validator/Constraint.php index 3f8b803f78972..349698eb1af59 100644 --- a/src/Symfony/Component/Validator/Constraint.php +++ b/src/Symfony/Component/Validator/Constraint.php @@ -232,12 +232,13 @@ public function addImplicitGroupName($group) * * Override this method to define a default option. * - * @return string + * @return string|null * * @see __construct() */ public function getDefaultOption() { + return null; } /** From 198b895eac309316424f63536be9ff4946a5e667 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Sat, 30 Mar 2019 18:26:16 +0100 Subject: [PATCH 05/53] [Console] Fix inconsistent result for choice questions in non-interactive mode --- src/Symfony/Component/Console/Helper/QuestionHelper.php | 8 +++++++- .../Component/Console/Tests/Helper/QuestionHelperTest.php | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index 9c200fe897ea9..2cd2adb5fe45c 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -49,7 +49,13 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu if (!$input->isInteractive()) { $default = $question->getDefault(); - if (null !== $default && $question instanceof ChoiceQuestion) { + if (null === $default) { + return $default; + } + + if ($validator = $question->getValidator()) { + return \call_user_func($question->getValidator(), $default); + } elseif ($question instanceof ChoiceQuestion) { $choices = $question->getChoices(); if (!$question->isMultiselect()) { diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 46cfe8184278d..56ba1c6891322 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -137,6 +137,9 @@ public function testAskChoiceNonInteractive() $question->setMultiselect(true); $this->assertNull($questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question)); + $question = new ChoiceQuestion('Who are your favorite superheros?', ['a' => 'Batman', 'b' => 'Superman'], 'a'); + $this->assertSame('a', $questionHelper->ask($this->createStreamableInputInterfaceMock('', false), $this->createOutputInterface(), $question), 'ChoiceQuestion validator returns the key if it\'s a string'); + try { $question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, ''); $question->setMultiselect(true); From f54389b95ce52f7ed1f49d8ec9be3be97e8bcc38 Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Sat, 6 Apr 2019 12:03:31 +0200 Subject: [PATCH 06/53] bugfix: the terminal state was wrong and not reseted --- src/Symfony/Component/Console/Helper/QuestionHelper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index 9c200fe897ea9..b722ee4b8843c 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -254,6 +254,7 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false. if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) { + shell_exec(sprintf('stty %s', $sttyMode)); throw new RuntimeException('Aborted.'); } elseif ("\177" === $c) { // Backspace Character if (0 === $numMatches && 0 !== $i) { From 56429a6f082c057880e88ca665910b04d26e76d8 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Tue, 12 Mar 2019 21:16:42 +0100 Subject: [PATCH 07/53] [Form] various minor fixes --- .../Component/Form/AbstractExtension.php | 4 +- .../Component/Form/AbstractRendererEngine.php | 2 +- src/Symfony/Component/Form/Button.php | 25 ++- src/Symfony/Component/Form/ButtonBuilder.php | 15 +- .../Component/Form/CallbackTransformer.php | 22 +-- .../Component/Form/DataMapperInterface.php | 40 +++- .../Form/DataTransformerInterface.php | 41 ++-- .../Form/Extension/Core/Type/ButtonType.php | 4 +- .../Form/Extension/Core/Type/ChoiceType.php | 4 +- src/Symfony/Component/Form/Form.php | 182 ++++++++---------- src/Symfony/Component/Form/FormBuilder.php | 28 +-- .../Component/Form/FormConfigBuilder.php | 91 ++------- .../Form/FormConfigBuilderInterface.php | 16 +- .../Component/Form/FormConfigInterface.php | 20 +- src/Symfony/Component/Form/FormError.php | 2 +- .../Component/Form/FormErrorIterator.php | 12 +- src/Symfony/Component/Form/FormEvent.php | 4 + src/Symfony/Component/Form/FormEvents.php | 29 ++- src/Symfony/Component/Form/FormInterface.php | 86 ++++++--- src/Symfony/Component/Form/FormRegistry.php | 2 +- .../Form/FormRendererEngineInterface.php | 4 +- .../Component/Form/FormTypeGuesserChain.php | 2 +- .../Form/FormTypeGuesserInterface.php | 4 +- .../Component/Form/NativeRequestHandler.php | 2 + .../Component/Form/Tests/CompoundFormTest.php | 25 +++ .../Component/Form/Tests/SimpleFormTest.php | 22 ++- 26 files changed, 370 insertions(+), 318 deletions(-) diff --git a/src/Symfony/Component/Form/AbstractExtension.php b/src/Symfony/Component/Form/AbstractExtension.php index f4ac2e5ce2b0f..d5ab6bd48b883 100644 --- a/src/Symfony/Component/Form/AbstractExtension.php +++ b/src/Symfony/Component/Form/AbstractExtension.php @@ -36,7 +36,7 @@ abstract class AbstractExtension implements FormExtensionInterface /** * The type guesser provided by this extension. * - * @var FormTypeGuesserInterface + * @var FormTypeGuesserInterface|null */ private $typeGuesser; @@ -136,7 +136,7 @@ protected function loadTypeExtensions() /** * Registers the type guesser. * - * @return FormTypeGuesserInterface|null A type guesser + * @return FormTypeGuesserInterface|null */ protected function loadTypeGuesser() { diff --git a/src/Symfony/Component/Form/AbstractRendererEngine.php b/src/Symfony/Component/Form/AbstractRendererEngine.php index 649b600ddcf23..00bc254daefd3 100644 --- a/src/Symfony/Component/Form/AbstractRendererEngine.php +++ b/src/Symfony/Component/Form/AbstractRendererEngine.php @@ -133,7 +133,7 @@ abstract protected function loadResourceForBlockName($cacheKey, FormView $view, * resource * @param FormView $view The form view for finding the applying * themes - * @param array $blockNameHierarchy The block hierarchy, with the most + * @param string[] $blockNameHierarchy The block hierarchy, with the most * specific block name at the end * @param int $hierarchyLevel The level in the block hierarchy that * should be loaded diff --git a/src/Symfony/Component/Form/Button.php b/src/Symfony/Component/Form/Button.php index 8ae4946c565e0..ed1106b467a21 100644 --- a/src/Symfony/Component/Form/Button.php +++ b/src/Symfony/Component/Form/Button.php @@ -22,7 +22,7 @@ class Button implements \IteratorAggregate, FormInterface { /** - * @var FormInterface|null + * @var FormInterface */ private $parent; @@ -111,6 +111,8 @@ public function setParent(FormInterface $parent = null) } $this->parent = $parent; + + return $this; } /** @@ -199,11 +201,13 @@ public function getErrors($deep = false, $flatten = true) * This method should not be invoked. * * @param mixed $modelData + * + * @return $this */ public function setData($modelData) { - // called during initialization of the form tree - // noop + // no-op, called during initialization of the form tree + return $this; } /** @@ -211,6 +215,7 @@ public function setData($modelData) */ public function getData() { + return null; } /** @@ -218,6 +223,7 @@ public function getData() */ public function getNormData() { + return null; } /** @@ -225,6 +231,7 @@ public function getNormData() */ public function getViewData() { + return null; } /** @@ -240,7 +247,7 @@ public function getExtraData() /** * Returns the button's configuration. * - * @return FormConfigInterface The configuration + * @return FormConfigInterface The configuration instance */ public function getConfig() { @@ -272,6 +279,7 @@ public function getName() */ public function getPropertyPath() { + return null; } /** @@ -309,11 +317,11 @@ public function isRequired() */ public function isDisabled() { - if (null === $this->parent || !$this->parent->isDisabled()) { - return $this->config->getDisabled(); + if ($this->parent && $this->parent->isDisabled()) { + return true; } - return true; + return $this->config->getDisabled(); } /** @@ -341,6 +349,7 @@ public function isSynchronized() */ public function getTransformationFailure() { + return null; } /** @@ -368,7 +377,7 @@ public function handleRequest($request = null) /** * Submits data to the button. * - * @param string|null $submittedData The data + * @param string|null $submittedData Not used * @param bool $clearMissing Not used * * @return $this diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index 903e842b9e8b8..598749eeac27d 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -22,9 +22,6 @@ */ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface { - /** - * @var bool - */ protected $locked = false; /** @@ -53,8 +50,6 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface private $options; /** - * Creates a new button builder. - * * @param string $name The name of the button * @param array $options The button's options * @@ -524,6 +519,7 @@ public function getFormConfig() */ public function getEventDispatcher() { + return null; } /** @@ -539,6 +535,7 @@ public function getName() */ public function getPropertyPath() { + return null; } /** @@ -606,6 +603,7 @@ public function getModelTransformers() */ public function getDataMapper() { + return null; } /** @@ -643,6 +641,7 @@ public function getErrorBubbling() */ public function getEmptyData() { + return null; } /** @@ -685,6 +684,7 @@ public function getAttribute($name, $default = null) */ public function getData() { + return null; } /** @@ -692,6 +692,7 @@ public function getData() */ public function getDataClass() { + return null; } /** @@ -709,6 +710,7 @@ public function getDataLocked() */ public function getFormFactory() { + throw new BadMethodCallException('Buttons do not support adding children.'); } /** @@ -716,6 +718,7 @@ public function getFormFactory() */ public function getAction() { + return null; } /** @@ -723,6 +726,7 @@ public function getAction() */ public function getMethod() { + return null; } /** @@ -730,6 +734,7 @@ public function getMethod() */ public function getRequestHandler() { + return null; } /** diff --git a/src/Symfony/Component/Form/CallbackTransformer.php b/src/Symfony/Component/Form/CallbackTransformer.php index 8155e4dca8ed1..6db5bfb183351 100644 --- a/src/Symfony/Component/Form/CallbackTransformer.php +++ b/src/Symfony/Component/Form/CallbackTransformer.php @@ -11,9 +11,6 @@ namespace Symfony\Component\Form; -use Symfony\Component\Form\Exception\TransformationFailedException; -use Symfony\Component\Form\Exception\UnexpectedTypeException; - class CallbackTransformer implements DataTransformerInterface { private $transform; @@ -30,14 +27,7 @@ public function __construct(callable $transform, callable $reverseTransform) } /** - * Transforms a value from the original representation to a transformed representation. - * - * @param mixed $data The value in the original representation - * - * @return mixed The value in the transformed representation - * - * @throws UnexpectedTypeException when the argument is not of the expected type - * @throws TransformationFailedException when the transformation fails + * {@inheritdoc} */ public function transform($data) { @@ -45,15 +35,7 @@ public function transform($data) } /** - * Transforms a value from the transformed representation to its original - * representation. - * - * @param mixed $data The value in the transformed representation - * - * @return mixed The value in the original representation - * - * @throws UnexpectedTypeException when the argument is not of the expected type - * @throws TransformationFailedException when the transformation fails + * {@inheritdoc} */ public function reverseTransform($data) { diff --git a/src/Symfony/Component/Form/DataMapperInterface.php b/src/Symfony/Component/Form/DataMapperInterface.php index bb262e7b8e6b8..dee8f784910ea 100644 --- a/src/Symfony/Component/Form/DataMapperInterface.php +++ b/src/Symfony/Component/Form/DataMapperInterface.php @@ -17,22 +17,46 @@ interface DataMapperInterface { /** - * Maps properties of some data to a list of forms. + * Maps the view data of a compound form to its children. * - * @param mixed $data Structured data - * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances + * The method is responsible for calling {@link FormInterface::setData()} + * on the children of compound forms, defining their underlying model data. + * + * @param mixed $viewData View data of the compound form being initialized + * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapDataToForms($data, $forms); + public function mapDataToForms($viewData, $forms); /** - * Maps the data of a list of forms into the properties of some data. + * Maps the model data of a list of children forms into the view data of their parent. + * + * This is the internal cascade call of FormInterface::submit for compound forms, since they + * cannot be bound to any input nor the request as scalar, but their children may: + * + * $compoundForm->submit($arrayOfChildrenViewData) + * // inside: + * $childForm->submit($childViewData); + * // for each entry, do the same and/or reverse transform + * $this->dataMapper->mapFormsToData($compoundForm, $compoundInitialViewData) + * // then reverse transform + * + * When a simple form is submitted the following is happening: + * + * $simpleForm->submit($submittedViewData) + * // inside: + * $this->viewData = $submittedViewData + * // then reverse transform + * + * The model data can be an array or an object, so this second argument is always passed + * by reference. * - * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances - * @param mixed $data Structured data + * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances + * @param mixed $viewData The compound form's view data that get mapped + * its children model data * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapFormsToData($forms, &$data); + public function mapFormsToData($forms, &$viewData); } diff --git a/src/Symfony/Component/Form/DataTransformerInterface.php b/src/Symfony/Component/Form/DataTransformerInterface.php index deb073c8128fe..e5ac5992944e5 100644 --- a/src/Symfony/Component/Form/DataTransformerInterface.php +++ b/src/Symfony/Component/Form/DataTransformerInterface.php @@ -23,23 +23,35 @@ interface DataTransformerInterface /** * Transforms a value from the original representation to a transformed representation. * - * This method is called on two occasions inside a form field: + * This method is called when the form field is initialized with its default data, on + * two occasions for two types of transformers: * - * 1. When the form field is initialized with the data attached from the datasource (object or array). - * 2. When data from a request is submitted using {@link Form::submit()} to transform the new input data - * back into the renderable format. For example if you have a date field and submit '2009-10-10' - * you might accept this value because its easily parsed, but the transformer still writes back - * "2009/10/10" onto the form field (for further displaying or other purposes). + * 1. Model transformers which normalize the model data. + * This is mainly useful when the same form type (the same configuration) + * has to handle different kind of underlying data, e.g The DateType can + * deal with strings or \DateTime objects as input. + * + * 2. View transformers which adapt the normalized data to the view format. + * a/ When the form is simple, the value returned by convention is used + * directly in the view and thus can only be a string or an array. In + * this case the data class should be null. + * + * b/ When the form is compound the returned value should be an array or + * an object to be mapped to the children. Each property of the compound + * data will be used as model data by each child and will be transformed + * too. In this case data class should be the class of the object, or null + * when it is an array. + * + * All transformers are called in a configured order from model data to view value. + * At the end of this chain the view data will be validated against the data class + * setting. * * This method must be able to deal with empty values. Usually this will * be NULL, but depending on your implementation other empty values are * possible as well (such as empty strings). The reasoning behind this is - * that value transformers must be chainable. If the transform() method - * of the first value transformer outputs NULL, the second value transformer - * must be able to process that value. - * - * By convention, transform() should return an empty string if NULL is - * passed. + * that data transformers must be chainable. If the transform() method + * of the first data transformer outputs NULL, the second must be able to + * process that value. * * @param mixed $value The value in the original representation * @@ -54,7 +66,10 @@ public function transform($value); * representation. * * This method is called when {@link Form::submit()} is called to transform the requests tainted data - * into an acceptable format for your data processing/model layer. + * into an acceptable format. + * + * The same transformers are called in the reverse order so the responsibility is to + * return one of the types that would be expected as input of transform(). * * This method must be able to deal with empty values. Usually this will * be an empty string, but depending on your implementation other empty diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php index 2b60f4f31e1e5..b05dcc018dc36 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php @@ -43,8 +43,6 @@ public function configureOptions(OptionsResolver $resolver) { parent::configureOptions($resolver); - $resolver->setDefaults([ - 'auto_initialize' => false, - ]); + $resolver->setDefault('auto_initialize', false); } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 28320ca38942e..2ad0859e880fa 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -328,8 +328,8 @@ public function configureOptions(OptionsResolver $resolver) 'placeholder' => $placeholderDefault, 'error_bubbling' => false, 'compound' => $compound, - // The view data is always a string, even if the "data" option - // is manually set to an object. + // The view data is always a string or an array of strings, + // even if the "data" option is manually set to an object. // See https://github.com/symfony/symfony/pull/5582 'data_class' => null, 'choice_translation_domain' => true, diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index cfa7eb5b6a6d8..5f417d6d90901 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -21,6 +21,7 @@ use Symfony\Component\Form\Util\InheritDataAwareIterator; use Symfony\Component\Form\Util\OrderedHashMap; use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\PropertyAccess\PropertyPathInterface; /** * Form represents a form. @@ -63,79 +64,57 @@ class Form implements \IteratorAggregate, FormInterface { /** - * The form's configuration. - * * @var FormConfigInterface */ private $config; /** - * The parent of this form. - * - * @var FormInterface + * @var FormInterface|null */ private $parent; /** - * The children of this form. - * - * @var FormInterface[] A map of FormInterface instances + * @var FormInterface[]|OrderedHashMap A map of FormInterface instances */ private $children; /** - * The errors of this form. - * * @var FormError[] An array of FormError instances */ private $errors = []; /** - * Whether this form was submitted. - * * @var bool */ private $submitted = false; /** - * The button that was used to submit the form. - * - * @var Button + * @var ClickableInterface|null The button that was used to submit the form */ private $clickedButton; /** - * The form data in model format. - * * @var mixed */ private $modelData; /** - * The form data in normalized format. - * * @var mixed */ private $normData; /** - * The form data in view format. - * * @var mixed */ private $viewData; /** - * The submitted values that don't belong to any children. - * - * @var array + * @var array The submitted values that don't belong to any children */ private $extraData = []; /** - * Returns the transformation failure generated during submission, if any. - * - * @var TransformationFailedException|null + * @var TransformationFailedException|null The transformation failure generated during submission, if any */ private $transformationFailure; @@ -161,8 +140,21 @@ class Form implements \IteratorAggregate, FormInterface private $lockSetData = false; /** - * Creates a new form based on the given configuration. - * + * @var string|int|null + */ + private $name; + + /** + * @var bool Whether the form inherits its underlying data from its parent + */ + private $inheritData; + + /** + * @var PropertyPathInterface|null + */ + private $propertyPath; + + /** * @throws LogicException if a data mapper is not provided for a compound form */ public function __construct(FormConfigInterface $config) @@ -176,12 +168,13 @@ public function __construct(FormConfigInterface $config) // If the form inherits the data from its parent, it is not necessary // to call setData() with the default data. - if ($config->getInheritData()) { + if ($this->inheritData = $config->getInheritData()) { $this->defaultDataSet = true; } $this->config = $config; $this->children = new OrderedHashMap(); + $this->name = $config->getName(); } public function __clone() @@ -206,7 +199,7 @@ public function getConfig() */ public function getName() { - return $this->config->getName(); + return $this->name; } /** @@ -214,11 +207,11 @@ public function getName() */ public function getPropertyPath() { - if (null !== $this->config->getPropertyPath()) { - return $this->config->getPropertyPath(); + if ($this->propertyPath || $this->propertyPath = $this->config->getPropertyPath()) { + return $this->propertyPath; } - if (null === $this->getName() || '' === $this->getName()) { + if (null === $this->name || '' === $this->name) { return null; } @@ -229,10 +222,12 @@ public function getPropertyPath() } if ($parent && null === $parent->getConfig()->getDataClass()) { - return new PropertyPath('['.$this->getName().']'); + $this->propertyPath = new PropertyPath('['.$this->name.']'); + } else { + $this->propertyPath = new PropertyPath($this->name); } - return new PropertyPath($this->getName()); + return $this->propertyPath; } /** @@ -268,7 +263,7 @@ public function setParent(FormInterface $parent = null) throw new AlreadySubmittedException('You cannot set the parent of a submitted form'); } - if (null !== $parent && '' === $this->config->getName()) { + if (null !== $parent && '' === $this->name) { throw new LogicException('A form with an empty name cannot have a parent form.'); } @@ -315,7 +310,7 @@ public function setData($modelData) // If the form inherits its parent's data, disallow data setting to // prevent merge conflicts - if ($this->config->getInheritData()) { + if ($this->inheritData) { throw new RuntimeException('You cannot change the data of a form inheriting its parent data.'); } @@ -335,7 +330,7 @@ public function setData($modelData) $this->lockSetData = true; $dispatcher = $this->config->getEventDispatcher(); - // Hook to change content of the data + // Hook to change content of the model data before transformation and mapping children if ($dispatcher->hasListeners(FormEvents::PRE_SET_DATA)) { $event = new FormEvent($this, $modelData); $dispatcher->dispatch(FormEvents::PRE_SET_DATA, $event); @@ -348,6 +343,7 @@ public function setData($modelData) } // Synchronize representations - must not change the content! + // Transformation exceptions are not caught on initialization $normData = $this->modelToNorm($modelData); $viewData = $this->normToView($normData); @@ -370,13 +366,10 @@ public function setData($modelData) $this->defaultDataSet = true; $this->lockSetData = false; - // It is not necessary to invoke this method if the form doesn't have children, - // even if the form is compound. + // Compound forms don't need to invoke this method if they don't have children if (\count($this->children) > 0) { - // Update child forms from the data - $iterator = new InheritDataAwareIterator($this->children); - $iterator = new \RecursiveIteratorIterator($iterator); - $this->config->getDataMapper()->mapDataToForms($viewData, $iterator); + // Update child forms from the data (unless their config data is locked) + $this->config->getDataMapper()->mapDataToForms($viewData, new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children))); } if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) { @@ -392,7 +385,7 @@ public function setData($modelData) */ public function getData() { - if ($this->config->getInheritData()) { + if ($this->inheritData) { if (!$this->parent) { throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); } @@ -416,7 +409,7 @@ public function getData() */ public function getNormData() { - if ($this->config->getInheritData()) { + if ($this->inheritData) { if (!$this->parent) { throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); } @@ -440,7 +433,7 @@ public function getNormData() */ public function getViewData() { - if ($this->config->getInheritData()) { + if ($this->inheritData) { if (!$this->parent) { throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); } @@ -505,8 +498,8 @@ public function submit($submittedData, $clearMissing = true) throw new AlreadySubmittedException('A form can only be submitted once'); } - // Initialize errors in the very beginning so that we don't lose any - // errors added during listeners + // Initialize errors in the very beginning so we're sure + // they are collectable during submission only $this->errors = []; // Obviously, a disabled form should not change its data upon submission. @@ -605,18 +598,16 @@ public function submit($submittedData, $clearMissing = true) // changes in the grandchildren (i.e. children of the form that inherits // its parent's data) into account. // (see InheritDataAwareIterator below) - if (!$this->config->getInheritData()) { - // If the form is compound, the default data in view format - // is reused. The data of the children is merged into this - // default data using the data mapper. - // If the form is not compound, the submitted data is also the data in view format. + if (!$this->inheritData) { + // If the form is compound, the view data is merged with the data + // of the children using the data mapper. + // If the form is not compound, the view data is assigned to the submitted data. $viewData = $this->config->getCompound() ? $this->viewData : $submittedData; if (FormUtil::isEmpty($viewData)) { $emptyData = $this->config->getEmptyData(); if ($emptyData instanceof \Closure) { - /* @var \Closure $emptyData */ $emptyData = $emptyData($this, $viewData); } @@ -631,9 +622,10 @@ public function submit($submittedData, $clearMissing = true) // descendants that inherit this form's data. // These descendants will not be submitted normally (see the check // for $this->config->getInheritData() above) - $childrenIterator = new InheritDataAwareIterator($this->children); - $childrenIterator = new \RecursiveIteratorIterator($childrenIterator); - $this->config->getDataMapper()->mapFormsToData($childrenIterator, $viewData); + $this->config->getDataMapper()->mapFormsToData( + new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children)), + $viewData + ); } // Normalize data to unified representation @@ -658,7 +650,7 @@ public function submit($submittedData, $clearMissing = true) // the erroneous data is accessible on the form. // Forms that inherit data never set any data, because the getters // forward to the parent form's getters anyway. - if (null === $viewData && !$this->config->getInheritData()) { + if (null === $viewData && !$this->inheritData) { $viewData = $submittedData; } } @@ -757,8 +749,7 @@ public function isValid() /** * Returns the button that was used to submit the form. * - * @return Button|null The clicked button or NULL if the form was not - * submitted + * @return ClickableInterface|null */ public function getClickedButton() { @@ -826,29 +817,6 @@ public function add($child, $type = null, array $options = []) throw new LogicException('You cannot add children to a simple form. Maybe you should set the option "compound" to true?'); } - // Obtain the view data - $viewData = null; - - // If setData() is currently being called, there is no need to call - // mapDataToForms() here, as mapDataToForms() is called at the end - // of setData() anyway. Not doing this check leads to an endless - // recursion when initializing the form lazily and an event listener - // (such as ResizeFormListener) adds fields depending on the data: - // - // * setData() is called, the form is not initialized yet - // * add() is called by the listener (setData() is not complete, so - // the form is still not initialized) - // * getViewData() is called - // * setData() is called since the form is not initialized yet - // * ... endless recursion ... - // - // Also skip data mapping if setData() has not been called yet. - // setData() will be called upon form initialization and data mapping - // will take place by then. - if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { - $viewData = $this->getViewData(); - } - if (!$child instanceof FormInterface) { if (!\is_string($child) && !\is_int($child)) { throw new UnexpectedTypeException($child, 'string, integer or Symfony\Component\Form\FormInterface'); @@ -878,10 +846,28 @@ public function add($child, $type = null, array $options = []) $child->setParent($this); - if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { - $iterator = new InheritDataAwareIterator(new \ArrayIterator([$child->getName() => $child])); - $iterator = new \RecursiveIteratorIterator($iterator); - $this->config->getDataMapper()->mapDataToForms($viewData, $iterator); + // If setData() is currently being called, there is no need to call + // mapDataToForms() here, as mapDataToForms() is called at the end + // of setData() anyway. Not doing this check leads to an endless + // recursion when initializing the form lazily and an event listener + // (such as ResizeFormListener) adds fields depending on the data: + // + // * setData() is called, the form is not initialized yet + // * add() is called by the listener (setData() is not complete, so + // the form is still not initialized) + // * getViewData() is called + // * setData() is called since the form is not initialized yet + // * ... endless recursion ... + // + // Also skip data mapping if setData() has not been called yet. + // setData() will be called upon form initialization and data mapping + // will take place by then. + if (!$this->lockSetData && $this->defaultDataSet && !$this->inheritData) { + $viewData = $this->getViewData(); + $this->config->getDataMapper()->mapDataToForms( + $viewData, + new \RecursiveIteratorIterator(new InheritDataAwareIterator(new \ArrayIterator([$child->getName() => $child]))) + ); } return $this; @@ -1030,13 +1016,13 @@ public function createView(FormView $parent = null) } /** - * Normalizes the value if a model transformer is set. + * Normalizes the underlying data if a model transformer is set. * * @param mixed $value The value to transform * * @return mixed * - * @throws TransformationFailedException If the value cannot be transformed to "normalized" format + * @throws TransformationFailedException If the underlying data cannot be transformed to "normalized" format */ private function modelToNorm($value) { @@ -1045,7 +1031,7 @@ private function modelToNorm($value) $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception); + throw new TransformationFailedException('Unable to transform data for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception); } return $value; @@ -1082,7 +1068,7 @@ private function normToModel($value) * * @return mixed * - * @throws TransformationFailedException If the value cannot be transformed to "view" format + * @throws TransformationFailedException If the normalized value cannot be transformed to "view" format */ private function normToView($value) { @@ -1091,12 +1077,12 @@ private function normToView($value) // Only do this for simple forms, as the resulting value in // compound forms is passed to the data mapper and thus should // not be converted to a string before. - if (!$this->config->getViewTransformers() && !$this->config->getCompound()) { + if (!($transformers = $this->config->getViewTransformers()) && !$this->config->getCompound()) { return null === $value || is_scalar($value) ? (string) $value : $value; } try { - foreach ($this->config->getViewTransformers() as $transformer) { + foreach ($transformers as $transformer) { $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { @@ -1113,13 +1099,11 @@ private function normToView($value) * * @return mixed * - * @throws TransformationFailedException If the value cannot be transformed to "normalized" format + * @throws TransformationFailedException If the submitted value cannot be transformed to "normalized" format */ private function viewToNorm($value) { - $transformers = $this->config->getViewTransformers(); - - if (!$transformers) { + if (!$transformers = $this->config->getViewTransformers()) { return '' === $value ? null : $value; } diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index 00affbf6d8c3d..13b1ea3b36420 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -38,8 +38,6 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB private $unresolvedChildren = []; /** - * Creates a new form builder. - * * @param string $name * @param string|null $dataClass * @param EventDispatcherInterface $dispatcher @@ -81,10 +79,7 @@ public function add($child, $type = null, array $options = []) // Add to "children" to maintain order $this->children[$child] = null; - $this->unresolvedChildren[$child] = [ - 'type' => $type, - 'options' => $options, - ]; + $this->unresolvedChildren[$child] = [$type, $options]; return $this; } @@ -152,15 +147,7 @@ public function has($name) throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - if (isset($this->unresolvedChildren[$name])) { - return true; - } - - if (isset($this->children[$name])) { - return true; - } - - return false; + return isset($this->unresolvedChildren[$name]) || isset($this->children[$name]); } /** @@ -232,7 +219,7 @@ public function getForm() /** * {@inheritdoc} * - * @return FormBuilderInterface[] + * @return FormBuilderInterface[]|\Traversable */ public function getIterator() { @@ -252,12 +239,11 @@ public function getIterator() */ private function resolveChild($name) { - $info = $this->unresolvedChildren[$name]; - $child = $this->create($name, $info['type'], $info['options']); - $this->children[$name] = $child; + list($type, $options) = $this->unresolvedChildren[$name]; + unset($this->unresolvedChildren[$name]); - return $child; + return $this->children[$name] = $this->create($name, $type, $options); } /** @@ -266,7 +252,7 @@ private function resolveChild($name) private function resolveChildren() { foreach ($this->unresolvedChildren as $name => $info) { - $this->children[$name] = $this->create($name, $info['type'], $info['options']); + $this->children[$name] = $this->create($name, $info[0], $info[1]); } $this->unresolvedChildren = []; diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index fc864fc617173..fea1d70cd8ee4 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -47,44 +47,19 @@ class FormConfigBuilder implements FormConfigBuilderInterface 'PATCH', ]; - /** - * @var bool - */ protected $locked = false; - /** - * @var EventDispatcherInterface - */ private $dispatcher; - - /** - * @var string - */ private $name; /** - * @var PropertyPathInterface + * @var PropertyPathInterface|string|null */ private $propertyPath; - /** - * @var bool - */ private $mapped = true; - - /** - * @var bool - */ private $byReference = true; - - /** - * @var bool - */ private $inheritData = false; - - /** - * @var bool - */ private $compound = false; /** @@ -92,34 +67,16 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $type; - /** - * @var array - */ private $viewTransformers = []; - - /** - * @var array - */ private $modelTransformers = []; /** - * @var DataMapperInterface + * @var DataMapperInterface|null */ private $dataMapper; - /** - * @var bool - */ private $required = true; - - /** - * @var bool - */ private $disabled = false; - - /** - * @var bool - */ private $errorBubbling = false; /** @@ -127,9 +84,6 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $emptyData; - /** - * @var array - */ private $attributes = []; /** @@ -142,39 +96,26 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $dataClass; - /** - * @var bool - */ - private $dataLocked; + private $dataLocked = false; /** - * @var FormFactoryInterface + * @var FormFactoryInterface|null */ private $formFactory; /** - * @var string + * @var string|null */ private $action; - /** - * @var string - */ private $method = 'POST'; /** - * @var RequestHandlerInterface + * @var RequestHandlerInterface|null */ private $requestHandler; - /** - * @var bool - */ private $autoInitialize = false; - - /** - * @var array - */ private $options; /** @@ -616,7 +557,7 @@ public function setErrorBubbling($errorBubbling) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->errorBubbling = null === $errorBubbling ? null : (bool) $errorBubbling; + $this->errorBubbling = (bool) $errorBubbling; return $this; } @@ -662,7 +603,7 @@ public function setMapped($mapped) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->mapped = $mapped; + $this->mapped = (bool) $mapped; return $this; } @@ -676,7 +617,7 @@ public function setByReference($byReference) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->byReference = $byReference; + $this->byReference = (bool) $byReference; return $this; } @@ -690,7 +631,7 @@ public function setInheritData($inheritData) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->inheritData = $inheritData; + $this->inheritData = (bool) $inheritData; return $this; } @@ -704,7 +645,7 @@ public function setCompound($compound) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->compound = $compound; + $this->compound = (bool) $compound; return $this; } @@ -746,7 +687,7 @@ public function setDataLocked($locked) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->dataLocked = $locked; + $this->dataLocked = (bool) $locked; return $this; } @@ -774,7 +715,7 @@ public function setAction($action) throw new BadMethodCallException('The config builder cannot be modified anymore.'); } - $this->action = $action; + $this->action = (string) $action; return $this; } @@ -790,7 +731,7 @@ public function setMethod($method) $upperCaseMethod = strtoupper($method); - if (!\in_array($upperCaseMethod, self::$allowedMethods)) { + if (!\in_array($upperCaseMethod, self::$allowedMethods, true)) { throw new InvalidArgumentException(sprintf('The form method is "%s", but should be one of "%s".', $method, implode('", "', self::$allowedMethods))); } @@ -846,7 +787,7 @@ public function getFormConfig() /** * Validates whether the given variable is a valid form name. * - * @param string|int $name The tested form name + * @param string|int|null $name The tested form name * * @throws UnexpectedTypeException if the name is not a string or an integer * @throws InvalidArgumentException if the name contains invalid characters @@ -872,7 +813,7 @@ public static function validateName($name) * * contains only letters, digits, numbers, underscores ("_"), * hyphens ("-") and colons (":") * - * @param string $name The tested form name + * @param string|null $name The tested form name * * @return bool Whether the name is valid */ diff --git a/src/Symfony/Component/Form/FormConfigBuilderInterface.php b/src/Symfony/Component/Form/FormConfigBuilderInterface.php index f422840a82c45..d516e41056ecc 100644 --- a/src/Symfony/Component/Form/FormConfigBuilderInterface.php +++ b/src/Symfony/Component/Form/FormConfigBuilderInterface.php @@ -108,7 +108,7 @@ public function setAttributes(array $attributes); public function setDataMapper(DataMapperInterface $dataMapper = null); /** - * Set whether the form is disabled. + * Sets whether the form is disabled. * * @param bool $disabled Whether the form is disabled * @@ -166,8 +166,7 @@ public function setMapped($mapped); /** * Sets whether the form's data should be modified by reference. * - * @param bool $byReference Whether the data should be - * modified by reference + * @param bool $byReference Whether the data should be modified by reference * * @return $this The configuration object */ @@ -194,7 +193,7 @@ public function setInheritData($inheritData); public function setCompound($compound); /** - * Set the types. + * Sets the resolved type. * * @return $this The configuration object */ @@ -203,7 +202,7 @@ public function setType(ResolvedFormTypeInterface $type); /** * Sets the initial data of the form. * - * @param mixed $data The data of the form in application format + * @param mixed $data The data of the form in model format * * @return $this The configuration object */ @@ -214,9 +213,12 @@ public function setData($data); * * A form with locked data is restricted to the data passed in * this configuration. The data can only be modified then by - * submitting the form. + * submitting the form or using PRE_SET_DATA event. * - * @param bool $locked Whether to lock the default data + * It means data passed to a factory method or mapped from the + * parent will be ignored. + * + * @param bool $locked Whether to lock the default configured data * * @return $this The configuration object */ diff --git a/src/Symfony/Component/Form/FormConfigInterface.php b/src/Symfony/Component/Form/FormConfigInterface.php index ce9171f3d496b..7dbda33033b55 100644 --- a/src/Symfony/Component/Form/FormConfigInterface.php +++ b/src/Symfony/Component/Form/FormConfigInterface.php @@ -70,15 +70,17 @@ public function getInheritData(); * This property is independent of whether the form actually has * children. A form can be compound and have no children at all, like * for example an empty collection form. + * The contrary is not possible, a form which is not compound + * cannot have any children. * * @return bool Whether the form is compound */ public function getCompound(); /** - * Returns the form types used to construct the form. + * Returns the resolved form type used to construct the form. * - * @return ResolvedFormTypeInterface The form's type + * @return ResolvedFormTypeInterface The form's resolved type */ public function getType(); @@ -97,7 +99,7 @@ public function getViewTransformers(); public function getModelTransformers(); /** - * Returns the data mapper of the form. + * Returns the data mapper of the compound form or null for a simple form. * * @return DataMapperInterface|null The data mapper */ @@ -125,9 +127,15 @@ public function getDisabled(); public function getErrorBubbling(); /** - * Returns the data that should be returned when the form is empty. + * Used when the view data is empty on submission. * - * @return mixed The data returned if the form is empty + * When the form is compound it will also be used to map the + * children data. + * + * The empty data must match the view format as it will passed to the first view transformer's + * "reverseTransform" method. + * + * @return mixed The data used when the submitted form is initially empty */ public function getEmptyData(); @@ -165,7 +173,7 @@ public function getAttribute($name, $default = null); public function getData(); /** - * Returns the class of the form data or null if the data is scalar or an array. + * Returns the class of the view data or null if the data is scalar or an array. * * @return string|null The data class or null */ diff --git a/src/Symfony/Component/Form/FormError.php b/src/Symfony/Component/Form/FormError.php index 7717c19019a3c..98a1e29a83a82 100644 --- a/src/Symfony/Component/Form/FormError.php +++ b/src/Symfony/Component/Form/FormError.php @@ -49,7 +49,7 @@ class FormError implements \Serializable */ public function __construct($message, $messageTemplate = null, array $messageParameters = [], $messagePluralization = null, $cause = null) { - $this->message = $message; + $this->message = (string) $message; $this->messageTemplate = $messageTemplate ?: $message; $this->messageParameters = $messageParameters; $this->messagePluralization = $messagePluralization; diff --git a/src/Symfony/Component/Form/FormErrorIterator.php b/src/Symfony/Component/Form/FormErrorIterator.php index 2cc53c0923f94..db1d311a30d7a 100644 --- a/src/Symfony/Component/Form/FormErrorIterator.php +++ b/src/Symfony/Component/Form/FormErrorIterator.php @@ -39,10 +39,9 @@ class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \Array private $errors; /** - * Creates a new iterator. - * - * @param FormInterface $form The erroneous form - * @param FormError[]|FormErrorIterator[] $errors The form errors + * @param FormInterface $form The erroneous form + * @param FormError[]|self[] $errors An array of form errors and instances + * of FormErrorIterator * * @throws InvalidArgumentException If the errors are invalid */ @@ -71,7 +70,7 @@ public function __toString() if ($error instanceof FormError) { $string .= 'ERROR: '.$error->getMessage()."\n"; } else { - /* @var $error FormErrorIterator */ + /** @var self $error */ $string .= $error->form->getName().":\n"; $string .= self::indent((string) $error); } @@ -93,8 +92,7 @@ public function getForm() /** * Returns the current element of the iterator. * - * @return FormError|FormErrorIterator an error or an iterator containing - * nested errors + * @return FormError|self An error or an iterator containing nested errors */ public function current() { diff --git a/src/Symfony/Component/Form/FormEvent.php b/src/Symfony/Component/Form/FormEvent.php index c688a19566b76..3b6d484e75803 100644 --- a/src/Symfony/Component/Form/FormEvent.php +++ b/src/Symfony/Component/Form/FormEvent.php @@ -21,6 +21,10 @@ class FormEvent extends Event private $form; protected $data; + /** + * @param FormInterface $form The associated form + * @param mixed $data The data + */ public function __construct(FormInterface $form, $data) { $this->form = $form; diff --git a/src/Symfony/Component/Form/FormEvents.php b/src/Symfony/Component/Form/FormEvents.php index b795f95dcfafc..c4c613f567f19 100644 --- a/src/Symfony/Component/Form/FormEvents.php +++ b/src/Symfony/Component/Form/FormEvents.php @@ -34,21 +34,35 @@ final class FormEvents const PRE_SUBMIT = 'form.pre_bind'; /** - * The SUBMIT event is dispatched just before the Form::submit() method - * transforms back the normalized data to the model and view data. + * The SUBMIT event is dispatched after the Form::submit() method + * has changed the view data by the request data, or submitted and mapped + * the children if the form is compound, and after reverse transformation + * to normalized representation. * - * It can be used to change data from the normalized representation of the data. + * It's also dispatched just before the Form::submit() method transforms back + * the normalized data to the model and view data. + * + * So at this stage children of compound forms are submitted and synchronized, unless + * their transformation failed, but a parent would still be at the PRE_SUBMIT level. + * + * Since the current form is not synchronized yet, it is still possible to add and + * remove fields. * * @Event("Symfony\Component\Form\FormEvent") */ const SUBMIT = 'form.bind'; /** - * The FormEvents::POST_SUBMIT event is dispatched after the Form::submit() - * once the model and view data have been denormalized. + * The FormEvents::POST_SUBMIT event is dispatched at the very end of the Form::submit(). + * + * It this stage the model and view data may have been denormalized. Otherwise the form + * is desynchronized because transformation failed during submission. * * It can be used to fetch data after denormalization. * + * The event attaches the current view data. To know whether this is the renormalized data + * or the invalid request data, call Form::isSynchronized() first. + * * @Event("Symfony\Component\Form\FormEvent") */ const POST_SUBMIT = 'form.post_bind'; @@ -58,7 +72,7 @@ final class FormEvents * * It can be used to: * - Modify the data given during pre-population; - * - Modify a form depending on the pre-populated data (adding or removing fields dynamically). + * - Keep synchronized the form depending on the data (adding or removing fields dynamically). * * @Event("Symfony\Component\Form\FormEvent") */ @@ -67,7 +81,8 @@ final class FormEvents /** * The FormEvents::POST_SET_DATA event is dispatched at the end of the Form::setData() method. * - * This event is mostly here for reading data after having pre-populated the form. + * This event can be used to modify the form depending on the final state of the underlying data + * accessible in every representation: model, normalized and view. * * @Event("Symfony\Component\Form\FormEvent") */ diff --git a/src/Symfony/Component/Form/FormInterface.php b/src/Symfony/Component/Form/FormInterface.php index 2add7938443aa..022b60aa1368d 100644 --- a/src/Symfony/Component/Form/FormInterface.php +++ b/src/Symfony/Component/Form/FormInterface.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Form; -use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\PropertyAccess\PropertyPathInterface; /** * A form group bundling multiple forms in a hierarchical structure. @@ -23,7 +23,9 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable /** * Sets the parent form. * - * @return self + * @param FormInterface|null $parent The parent form or null if it's the root + * + * @return $this * * @throws Exception\AlreadySubmittedException if the form has already been submitted * @throws Exception\LogicException when trying to set a parent for a form with @@ -45,7 +47,7 @@ public function getParent(); * @param string|null $type The child's type, if a name was passed * @param array $options The child's options, if a name was passed * - * @return self + * @return $this * * @throws Exception\AlreadySubmittedException if the form has already been submitted * @throws Exception\LogicException when trying to add a child to a non-compound form @@ -104,44 +106,70 @@ public function all(); public function getErrors($deep = false, $flatten = true); /** - * Updates the form with default data. + * Updates the form with default model data. * * @param mixed $modelData The data formatted as expected for the underlying object * * @return $this * - * @throws Exception\AlreadySubmittedException if the form has already been submitted - * @throws Exception\LogicException If listeners try to call setData in a cycle. Or if - * the view data does not match the expected type - * according to {@link FormConfigInterface::getDataClass}. + * @throws Exception\AlreadySubmittedException If the form has already been submitted + * @throws Exception\LogicException If the view data does not match the expected type + * according to {@link FormConfigInterface::getDataClass}. + * @throws Exception\RuntimeException If listeners try to call setData in a cycle or if + * the form inherits data from its parent + * @throws Exception\TransformationFailedException If the synchronization failed. */ public function setData($modelData); /** - * Returns the data in the format needed for the underlying object. + * Returns the model data in the format needed for the underlying object. * - * @return mixed + * @return mixed When the field is not submitted, the default data is returned. + * When the field is submitted, the default data has been bound + * to the submitted view data. + * + * @throws Exception\RuntimeException If the form inherits data but has no parent */ public function getData(); /** - * Returns the normalized data of the field. + * Returns the normalized data of the field, used as internal bridge + * between model data and view data. * * @return mixed When the field is not submitted, the default data is returned. - * When the field is submitted, the normalized submitted data is - * returned if the field is valid, null otherwise. + * When the field is submitted, the normalized submitted data + * is returned if the field is synchronized with the view data, + * null otherwise. + * + * @throws Exception\RuntimeException If the form inherits data but has no parent */ public function getNormData(); /** - * Returns the data transformed by the value transformer. + * Returns the view data of the field. + * + * It may be defined by {@link FormConfigInterface::getDataClass}. + * + * There are two cases: + * + * - When the form is compound the view data is mapped to the children. + * Each child will use its mapped data as model data. + * It can be an array, an object or null. + * + * - When the form is simple its view data is used to be bound + * to the submitted data. + * It can be a string or an array. + * + * In both cases the view data is the actual altered data on submission. * * @return mixed + * + * @throws Exception\RuntimeException If the form inherits data but has no parent */ public function getViewData(); /** - * Returns the extra data. + * Returns the extra submitted data. * * @return array The submitted data which do not belong to a child */ @@ -150,7 +178,7 @@ public function getExtraData(); /** * Returns the form's configuration. * - * @return FormConfigInterface The configuration + * @return FormConfigInterface The configuration instance */ public function getConfig(); @@ -164,6 +192,8 @@ public function isSubmitted(); /** * Returns the name by which the form is identified in forms. * + * Only root forms are allowed to have an empty name. + * * @return string The name of the form */ public function getName(); @@ -171,7 +201,7 @@ public function getName(); /** * Returns the property path that the form is mapped to. * - * @return \Symfony\Component\PropertyAccess\PropertyPathInterface|null The property path + * @return PropertyPathInterface|null The property path instance */ public function getPropertyPath(); @@ -230,14 +260,16 @@ public function isEmpty(); * If the data is not synchronized, you can get the transformation failure * by calling {@link getTransformationFailure()}. * + * If the form is not submitted, this method always returns true. + * * @return bool */ public function isSynchronized(); /** - * Returns the data transformation failure, if any. + * Returns the data transformation failure, if any, during submission. * - * @return TransformationFailedException|null The transformation failure + * @return Exception\TransformationFailedException|null The transformation failure or null */ public function getTransformationFailure(); @@ -247,6 +279,8 @@ public function getTransformationFailure(); * Should be called on the root form after constructing the tree. * * @return $this + * + * @throws Exception\RuntimeException If the form is not the root */ public function initialize(); @@ -265,11 +299,13 @@ public function initialize(); public function handleRequest($request = null); /** - * Submits data to the form, transforms and validates it. + * Submits data to the form. * - * @param mixed $submittedData The submitted data - * @param bool $clearMissing Whether to set fields to NULL when they - * are missing in the submitted data + * @param string|array|null $submittedData The submitted data + * @param bool $clearMissing Whether to set fields to NULL + * when they are missing in the + * submitted data. This argument + * is only used in compound form * * @return $this * @@ -280,7 +316,7 @@ public function submit($submittedData, $clearMissing = true); /** * Returns the root of the form tree. * - * @return self The root of the tree + * @return self The root of the tree, may be the instance itself */ public function getRoot(); @@ -292,8 +328,6 @@ public function getRoot(); public function isRoot(); /** - * Creates a view. - * * @return FormView The view */ public function createView(FormView $parent = null); diff --git a/src/Symfony/Component/Form/FormRegistry.php b/src/Symfony/Component/Form/FormRegistry.php index 1446976191c6d..cbb1d7a4174c7 100644 --- a/src/Symfony/Component/Form/FormRegistry.php +++ b/src/Symfony/Component/Form/FormRegistry.php @@ -26,7 +26,7 @@ class FormRegistry implements FormRegistryInterface /** * Extensions. * - * @var FormExtensionInterface[] An array of FormExtensionInterface + * @var FormExtensionInterface[] */ private $extensions = []; diff --git a/src/Symfony/Component/Form/FormRendererEngineInterface.php b/src/Symfony/Component/Form/FormRendererEngineInterface.php index 9ae5233f46273..2f0b868dd0098 100644 --- a/src/Symfony/Component/Form/FormRendererEngineInterface.php +++ b/src/Symfony/Component/Form/FormRendererEngineInterface.php @@ -74,7 +74,7 @@ public function getResourceForBlockName(FormView $view, $blockName); * First the themes attached directly to * the view with {@link setTheme()} are * considered, then the ones of its parent etc. - * @param array $blockNameHierarchy The block name hierarchy, with the root block + * @param string[] $blockNameHierarchy The block name hierarchy, with the root block * at the beginning * @param int $hierarchyLevel The level in the hierarchy at which to start * looking. Level 0 indicates the root block, i.e. @@ -112,7 +112,7 @@ public function getResourceForBlockNameHierarchy(FormView $view, array $blockNam * First the themes attached directly to * the view with {@link setTheme()} are * considered, then the ones of its parent etc. - * @param array $blockNameHierarchy The block name hierarchy, with the root block + * @param string[] $blockNameHierarchy The block name hierarchy, with the root block * at the beginning * @param int $hierarchyLevel The level in the hierarchy at which to start * looking. Level 0 indicates the root block, i.e. diff --git a/src/Symfony/Component/Form/FormTypeGuesserChain.php b/src/Symfony/Component/Form/FormTypeGuesserChain.php index f38ba8e5d431a..5ce8794248801 100644 --- a/src/Symfony/Component/Form/FormTypeGuesserChain.php +++ b/src/Symfony/Component/Form/FormTypeGuesserChain.php @@ -19,7 +19,7 @@ class FormTypeGuesserChain implements FormTypeGuesserInterface private $guessers = []; /** - * @param FormTypeGuesserInterface[] $guessers Guessers as instances of FormTypeGuesserInterface + * @param FormTypeGuesserInterface[] $guessers * * @throws UnexpectedTypeException if any guesser does not implement FormTypeGuesserInterface */ diff --git a/src/Symfony/Component/Form/FormTypeGuesserInterface.php b/src/Symfony/Component/Form/FormTypeGuesserInterface.php index 6521ea47ca767..3be9a0c9f8570 100644 --- a/src/Symfony/Component/Form/FormTypeGuesserInterface.php +++ b/src/Symfony/Component/Form/FormTypeGuesserInterface.php @@ -49,8 +49,8 @@ public function guessMaxLength($class, $property); /** * Returns a guess about the field's pattern. * - * - When you have a min value, you guess a min length of this min (LOW_CONFIDENCE) , lines below - * - If this value is a float type, this is wrong so you guess null with MEDIUM_CONFIDENCE to override the previous guess. + * - When you have a min value, you guess a min length of this min (LOW_CONFIDENCE) + * - Then line below, if this value is a float type, this is wrong so you guess null with MEDIUM_CONFIDENCE to override the previous guess. * Example: * You want a float greater than 5, 4.512313 is not valid but length(4.512314) > length(5) * diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index 246ea92ccee50..f3c0d839fec66 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -41,6 +41,8 @@ public function __construct(ServerParams $params = null) /** * {@inheritdoc} + * + * @throws Exception\UnexpectedTypeException If the $request is not null */ public function handleRequest(FormInterface $form, $request = null) { diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index ba4f26ecc9d8c..edc3aeda14b2c 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -450,6 +450,31 @@ public function testSetDataMapsViewDataToChildren() $form->setData('foo'); } + public function testSetDataDoesNotMapViewDataToChildrenWithLockedSetData() + { + $mapper = new PropertyPathMapper(); + $viewData = [ + 'firstName' => 'Fabien', + 'lastName' => 'Pot', + ]; + $form = $this->getBuilder() + ->setCompound(true) + ->setDataMapper($mapper) + ->addViewTransformer(new FixedDataTransformer([ + '' => '', + 'foo' => $viewData, + ])) + ->getForm(); + + $form->add($child1 = $this->getBuilder('firstName')->getForm()); + $form->add($child2 = $this->getBuilder('lastName')->setData('Potencier')->setDataLocked(true)->getForm()); + + $form->setData('foo'); + + $this->assertSame('Fabien', $form->get('firstName')->getData()); + $this->assertSame('Potencier', $form->get('lastName')->getData()); + } + public function testSubmitSupportsDynamicAdditionAndRemovalOfChildren() { $form = $this->form; diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 214ff3beab241..5233a8f1aae18 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -54,6 +54,26 @@ public function getIterator() class SimpleFormTest extends AbstractFormTest { + /** + * @dataProvider provideFormNames + */ + public function testGetPropertyPath($name, $propertyPath) + { + $config = new FormConfigBuilder($name, null, $this->dispatcher); + $form = new Form($config); + + $this->assertEquals(new PropertyPath($propertyPath), $form->getPropertyPath()); + } + + public function provideFormNames() + { + yield [null, null]; + yield ['', null]; + yield ['0', '0']; + yield [0, '0']; + yield ['name', 'name']; + } + public function testDataIsInitializedToConfiguredValue() { $model = new FixedDataTransformer([ @@ -76,7 +96,7 @@ public function testDataIsInitializedToConfiguredValue() /** * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException - * @expectedExceptionMessage Unable to transform value for property path "name": No mapping for value "arg" + * @expectedExceptionMessage Unable to transform data for property path "name": No mapping for value "arg" */ public function testDataTransformationFailure() { From 83c661d5cf18d572bdf4a286af8cec5a51e9c9b3 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 6 Apr 2019 16:32:25 +0200 Subject: [PATCH 08/53] fix tests --- src/Symfony/Component/Form/Tests/SimpleFormTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 5233a8f1aae18..32c21c8432b7a 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -62,16 +62,16 @@ public function testGetPropertyPath($name, $propertyPath) $config = new FormConfigBuilder($name, null, $this->dispatcher); $form = new Form($config); - $this->assertEquals(new PropertyPath($propertyPath), $form->getPropertyPath()); + $this->assertEquals($propertyPath, $form->getPropertyPath()); } public function provideFormNames() { yield [null, null]; yield ['', null]; - yield ['0', '0']; - yield [0, '0']; - yield ['name', 'name']; + yield ['0', new PropertyPath('0')]; + yield [0, new PropertyPath('0')]; + yield ['name', new PropertyPath('name')]; } public function testDataIsInitializedToConfiguredValue() From 5d4e3a2d57c1ea1be3a12e4695ac8eadf1a8209d Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Sat, 6 Apr 2019 14:30:12 +0200 Subject: [PATCH 09/53] [WIP] [DependencyInjection] Fix a wrong error when using a factory and a call --- .../Compiler/AutowirePass.php | 10 ++++- .../Compiler/ResolveBindingsPass.php | 9 +++- .../Tests/Compiler/AutowirePassTest.php | 13 ++++++ .../Compiler/ResolveBindingsPassTest.php | 44 +++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 252b304f118f5..c8e7a0f575f4e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -181,7 +181,15 @@ private function autowireCalls(\ReflectionClass $reflectionClass, array $methodC if ($method instanceof \ReflectionFunctionAbstract) { $reflectionMethod = $method; } else { - $reflectionMethod = $this->getReflectionMethod(new Definition($reflectionClass->name), $method); + $definition = new Definition($reflectionClass->name); + try { + $reflectionMethod = $this->getReflectionMethod($definition, $method); + } catch (RuntimeException $e) { + if ($definition->getFactory()) { + continue; + } + throw $e; + } } $arguments = $this->autowireMethod($reflectionMethod, $arguments); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php index 20b262b6d461c..bb67eb16794ae 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php @@ -115,7 +115,14 @@ protected function processValue($value, $isRoot = false) if ($method instanceof \ReflectionFunctionAbstract) { $reflectionMethod = $method; } else { - $reflectionMethod = $this->getReflectionMethod($value, $method); + try { + $reflectionMethod = $this->getReflectionMethod($value, $method); + } catch (RuntimeException $e) { + if ($value->getFactory()) { + continue; + } + throw $e; + } } foreach ($reflectionMethod->getParameters() as $key => $parameter) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 6bd49fa5c6f82..6235ed50be25c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -23,6 +23,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic; use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\HttpKernel\HttpKernelInterface; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; @@ -605,6 +606,18 @@ public function testSetterInjection() ); } + public function testWithNonExistingSetterAndAutowiring() + { + $container = new ContainerBuilder(); + + $definition = $container->register(HttpKernelInterface::class, HttpKernelInterface::class)->setAutowired(true); + $definition->addMethodCall('setLogger'); + $this->expectException(RuntimeException::class); + (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); + (new AutowirePass())->process($container); + } + public function testExplicitMethodInjection() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php index 5118b416f93fa..84b3d6c652fb6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php @@ -16,11 +16,14 @@ use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy; use Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists; use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\HttpKernel\HttpKernelInterface; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; @@ -111,4 +114,45 @@ public function testScalarSetter() $this->assertEquals([['setDefaultLocale', ['fr']]], $definition->getMethodCalls()); } + + public function testWithNonExistingSetterAndBinding() + { + $container = new ContainerBuilder(); + + $bindings = [ + '$c' => (new Definition('logger'))->setFactory('logger'), + ]; + + $definition = $container->register(HttpKernelInterface::class, HttpKernelInterface::class); + $definition->addMethodCall('setLogger'); + $definition->setBindings($bindings); + $this->expectException(RuntimeException::class); + + $pass = new ResolveBindingsPass(); + $pass->process($container); + } + + public function testTupleBinding() + { + $container = new ContainerBuilder(); + + $bindings = [ + '$c' => new BoundArgument(new Reference('bar')), + CaseSensitiveClass::class.'$c' => new BoundArgument(new Reference('foo')), + ]; + + $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); + $definition->addMethodCall('setSensitiveClass'); + $definition->addMethodCall('setAnotherC'); + $definition->setBindings($bindings); + + $pass = new ResolveBindingsPass(); + $pass->process($container); + + $expected = [ + ['setSensitiveClass', [new Reference('foo')]], + ['setAnotherC', [new Reference('bar')]], + ]; + $this->assertEquals($expected, $definition->getMethodCalls()); + } } From 1a21ca73625fe19d056fde1e7ca1ebd5919bd597 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 6 Apr 2019 14:28:07 +0200 Subject: [PATCH 10/53] turn failed file uploads into form errors --- .../Form/Extension/Core/Type/FileType.php | 115 +++++++++++++++++ .../HttpFoundationRequestHandler.php | 13 ++ .../Component/Form/NativeRequestHandler.php | 24 ++++ .../Form/Tests/AbstractRequestHandlerTest.php | 24 ++++ .../Extension/Core/Type/FileTypeTest.php | 122 ++++++++++++++++++ .../HttpFoundationRequestHandlerTest.php | 5 + .../Form/Tests/NativeRequestHandlerTest.php | 11 ++ 7 files changed, 314 insertions(+) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 59c72889d6683..a7f912d5e3109 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -13,6 +13,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; @@ -22,6 +23,15 @@ class FileType extends AbstractType { + const KIB_BYTES = 1024; + const MIB_BYTES = 1048576; + + private static $suffixes = [ + 1 => 'bytes', + self::KIB_BYTES => 'KiB', + self::MIB_BYTES => 'MiB', + ]; + /** * {@inheritdoc} */ @@ -43,6 +53,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) foreach ($files as $file) { if ($requestHandler->isFileUpload($file)) { $data[] = $file; + + if (method_exists($requestHandler, 'getUploadFileError') && null !== $errorCode = $requestHandler->getUploadFileError($file)) { + $form->addError($this->getFileUploadError($errorCode)); + } } } @@ -54,6 +68,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) } $event->setData($data); + } elseif ($requestHandler->isFileUpload($event->getData()) && method_exists($requestHandler, 'getUploadFileError') && null !== $errorCode = $requestHandler->getUploadFileError($event->getData())) { + $form->addError($this->getFileUploadError($errorCode)); } elseif (!$requestHandler->isFileUpload($event->getData())) { $event->setData(null); } @@ -116,4 +132,103 @@ public function getBlockPrefix() { return 'file'; } + + private function getFileUploadError($errorCode) + { + $messageParameters = []; + + if (UPLOAD_ERR_INI_SIZE === $errorCode) { + list($limitAsString, $suffix) = $this->factorizeSizes(0, self::getMaxFilesize()); + $messageTemplate = 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.'; + $messageParameters = [ + '{{ limit }}' => $limitAsString, + '{{ suffix }}' => $suffix, + ]; + } elseif (UPLOAD_ERR_FORM_SIZE === $errorCode) { + $messageTemplate = 'The file is too large.'; + } else { + $messageTemplate = 'The file could not be uploaded.'; + } + + return new FormError($messageTemplate, $messageTemplate, $messageParameters); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini. + * + * This method should be kept in sync with Symfony\Component\HttpFoundation\File\UploadedFile::getMaxFilesize(). + * + * @return int The maximum size of an uploaded file in bytes + */ + private static function getMaxFilesize() + { + $iniMax = strtolower(ini_get('upload_max_filesize')); + + if ('' === $iniMax) { + return PHP_INT_MAX; + } + + $max = ltrim($iniMax, '+'); + if (0 === strpos($max, '0x')) { + $max = \intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($iniMax, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Converts the limit to the smallest possible number + * (i.e. try "MB", then "kB", then "bytes"). + * + * This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::factorizeSizes(). + */ + private function factorizeSizes($size, $limit) + { + $coef = self::MIB_BYTES; + $coefFactor = self::KIB_BYTES; + + $limitAsString = (string) ($limit / $coef); + + // Restrict the limit to 2 decimals (without rounding! we + // need the precise value) + while (self::moreDecimalsThan($limitAsString, 2)) { + $coef /= $coefFactor; + $limitAsString = (string) ($limit / $coef); + } + + // Convert size to the same measure, but round to 2 decimals + $sizeAsString = (string) round($size / $coef, 2); + + // If the size and limit produce the same string output + // (due to rounding), reduce the coefficient + while ($sizeAsString === $limitAsString) { + $coef /= $coefFactor; + $limitAsString = (string) ($limit / $coef); + $sizeAsString = (string) round($size / $coef, 2); + } + + return [$limitAsString, self::$suffixes[$coef]]; + } + + /** + * This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::moreDecimalsThan(). + */ + private static function moreDecimalsThan($double, $numberOfDecimals) + { + return \strlen((string) $double) > \strlen(round($double, $numberOfDecimals)); + } } diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php index 75ee65443f450..cf255792fee83 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -17,6 +17,7 @@ use Symfony\Component\Form\RequestHandlerInterface; use Symfony\Component\Form\Util\ServerParams; use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; /** @@ -115,4 +116,16 @@ public function isFileUpload($data) { return $data instanceof File; } + + /** + * @return int|null + */ + public function getUploadFileError($data) + { + if (!$data instanceof UploadedFile || $data->isValid()) { + return null; + } + + return $data->getError(); + } } diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index f3c0d839fec66..5997fba67df15 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -135,6 +135,30 @@ public function isFileUpload($data) return \is_array($data) && isset($data['error']) && \is_int($data['error']); } + /** + * @return int|null + */ + public function getUploadFileError($data) + { + if (!\is_array($data)) { + return null; + } + + if (!isset($data['error'])) { + return null; + } + + if (!\is_int($data['error'])) { + return null; + } + + if (UPLOAD_ERR_OK === $data['error']) { + return null; + } + + return $data['error']; + } + /** * Returns the method used to submit the request to the server. * diff --git a/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php index 16d4045e6d580..b470769344bb2 100644 --- a/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php @@ -360,6 +360,28 @@ public function testInvalidFilesAreRejected() $this->assertFalse($this->requestHandler->isFileUpload($this->getInvalidFile())); } + /** + * @dataProvider uploadFileErrorCodes + */ + public function testFailedFileUploadIsTurnedIntoFormError($errorCode, $expectedErrorCode) + { + $this->assertSame($expectedErrorCode, $this->requestHandler->getUploadFileError($this->getFailedUploadedFile($errorCode))); + } + + public function uploadFileErrorCodes() + { + return [ + 'no error' => [UPLOAD_ERR_OK, null], + 'upload_max_filesize ini directive' => [UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_INI_SIZE], + 'MAX_FILE_SIZE from form' => [UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_FORM_SIZE], + 'partially uploaded' => [UPLOAD_ERR_PARTIAL, UPLOAD_ERR_PARTIAL], + 'no file upload' => [UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_FILE], + 'missing temporary directory' => [UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_NO_TMP_DIR], + 'write failure' => [UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_CANT_WRITE], + 'stopped by extension' => [UPLOAD_ERR_EXTENSION, UPLOAD_ERR_EXTENSION], + ]; + } + abstract protected function setRequestData($method, $data, $files = []); abstract protected function getRequestHandler(); @@ -368,6 +390,8 @@ abstract protected function getUploadedFile($suffix = ''); abstract protected function getInvalidFile(); + abstract protected function getFailedUploadedFile($errorCode); + protected function createForm($name, $method = null, $compound = false) { $config = $this->createBuilder($name, $compound); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php index ea012c451e885..73d39ace025db 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php @@ -184,6 +184,128 @@ public function requestHandlerProvider() ]; } + /** + * @dataProvider uploadFileErrorCodes + */ + public function testFailedFileUploadIsTurnedIntoFormErrorUsingHttpFoundationRequestHandler($errorCode, $expectedErrorMessage) + { + $form = $this->factory + ->createBuilder(static::TESTED_TYPE) + ->setRequestHandler(new HttpFoundationRequestHandler()) + ->getForm(); + $form->submit(new UploadedFile(__DIR__.'/../../../Fixtures/foo', 'foo', null, null, $errorCode, true)); + + if (UPLOAD_ERR_OK === $errorCode) { + $this->assertTrue($form->isValid()); + } else { + $this->assertFalse($form->isValid()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[0]->getMessage()); + } + } + + /** + * @dataProvider uploadFileErrorCodes + */ + public function testFailedFileUploadIsTurnedIntoFormErrorUsingNativeRequestHandler($errorCode, $expectedErrorMessage) + { + $form = $this->factory + ->createBuilder(static::TESTED_TYPE) + ->setRequestHandler(new NativeRequestHandler()) + ->getForm(); + $form->submit([ + 'name' => 'foo.txt', + 'type' => 'text/plain', + 'tmp_name' => 'owfdskjasdfsa', + 'error' => $errorCode, + 'size' => 100, + ]); + + if (UPLOAD_ERR_OK === $errorCode) { + $this->assertTrue($form->isValid()); + } else { + $this->assertFalse($form->isValid()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[0]->getMessage()); + } + } + + /** + * @dataProvider uploadFileErrorCodes + */ + public function testMultipleSubmittedFailedFileUploadsAreTurnedIntoFormErrorUsingHttpFoundationRequestHandler($errorCode, $expectedErrorMessage) + { + $form = $this->factory + ->createBuilder(static::TESTED_TYPE, null, [ + 'multiple' => true, + ]) + ->setRequestHandler(new HttpFoundationRequestHandler()) + ->getForm(); + $form->submit([ + new UploadedFile(__DIR__.'/../../../Fixtures/foo', 'foo', null, null, $errorCode, true), + new UploadedFile(__DIR__.'/../../../Fixtures/foo', 'bar', null, null, $errorCode, true), + ]); + + if (UPLOAD_ERR_OK === $errorCode) { + $this->assertTrue($form->isValid()); + } else { + $this->assertFalse($form->isValid()); + $this->assertCount(2, $form->getErrors()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[0]->getMessage()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[1]->getMessage()); + } + } + + /** + * @dataProvider uploadFileErrorCodes + */ + public function testMultipleSubmittedFailedFileUploadsAreTurnedIntoFormErrorUsingNativeRequestHandler($errorCode, $expectedErrorMessage) + { + $form = $this->factory + ->createBuilder(static::TESTED_TYPE, null, [ + 'multiple' => true, + ]) + ->setRequestHandler(new NativeRequestHandler()) + ->getForm(); + $form->submit([ + [ + 'name' => 'foo.txt', + 'type' => 'text/plain', + 'tmp_name' => 'owfdskjasdfsa', + 'error' => $errorCode, + 'size' => 100, + ], + [ + 'name' => 'bar.txt', + 'type' => 'text/plain', + 'tmp_name' => 'owfdskjasdfsa', + 'error' => $errorCode, + 'size' => 100, + ], + ]); + + if (UPLOAD_ERR_OK === $errorCode) { + $this->assertTrue($form->isValid()); + } else { + $this->assertFalse($form->isValid()); + $this->assertCount(2, $form->getErrors()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[0]->getMessage()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[1]->getMessage()); + } + } + + public function uploadFileErrorCodes() + { + return [ + 'no error' => [UPLOAD_ERR_OK, null], + 'upload_max_filesize ini directive' => [UPLOAD_ERR_INI_SIZE, 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.'], + 'MAX_FILE_SIZE from form' => [UPLOAD_ERR_FORM_SIZE, 'The file is too large.'], + 'partially uploaded' => [UPLOAD_ERR_PARTIAL, 'The file could not be uploaded.'], + 'no file upload' => [UPLOAD_ERR_NO_FILE, 'The file could not be uploaded.'], + 'missing temporary directory' => [UPLOAD_ERR_NO_TMP_DIR, 'The file could not be uploaded.'], + 'write failure' => [UPLOAD_ERR_CANT_WRITE, 'The file could not be uploaded.'], + 'stopped by extension' => [UPLOAD_ERR_EXTENSION, 'The file could not be uploaded.'], + ]; + } + private function createUploadedFile(RequestHandlerInterface $requestHandler, $path, $originalName) { if ($requestHandler instanceof HttpFoundationRequestHandler) { diff --git a/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php index 2b134511830e7..0e5389568e5ce 100644 --- a/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php @@ -56,4 +56,9 @@ protected function getInvalidFile() { return 'file:///etc/passwd'; } + + protected function getFailedUploadedFile($errorCode) + { + return new UploadedFile(__DIR__.'/../../Fixtures/foo', 'foo', null, null, $errorCode, true); + } } diff --git a/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php index 25a4716650755..36638a124f072 100644 --- a/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php @@ -275,4 +275,15 @@ protected function getInvalidFile() 'size' => '100', ]; } + + protected function getFailedUploadedFile($errorCode) + { + return [ + 'name' => 'upload.txt', + 'type' => 'text/plain', + 'tmp_name' => 'owfdskjasdfsa', + 'error' => $errorCode, + 'size' => 100, + ]; + } } From a5f1afca15e26b1b886d85a357e1370d7c479e47 Mon Sep 17 00:00:00 2001 From: Titouan Galopin Date: Sat, 6 Apr 2019 19:05:56 +0200 Subject: [PATCH 11/53] [Translator] Warm up the translations cache in dev --- .../Component/Translation/DataCollectorTranslator.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Translation/DataCollectorTranslator.php b/src/Symfony/Component/Translation/DataCollectorTranslator.php index 6f826dfaa6ff4..c0cc05996aa3e 100644 --- a/src/Symfony/Component/Translation/DataCollectorTranslator.php +++ b/src/Symfony/Component/Translation/DataCollectorTranslator.php @@ -11,12 +11,13 @@ namespace Symfony\Component\Translation; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\Translation\Exception\InvalidArgumentException; /** * @author Abdellatif Ait boudad */ -class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface +class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, WarmableInterface { const MESSAGE_DEFINED = 0; const MESSAGE_MISSING = 1; @@ -87,6 +88,14 @@ public function getCatalogue($locale = null) return $this->translator->getCatalogue($locale); } + /** + * {@inheritdoc} + */ + public function warmUp($cacheDir) + { + return $this->translator->warmUp($cacheDir); + } + /** * Gets the fallback locales. * From b54abfc64305a9aab2d9570a159d17587af3e2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20de=20Guillebon?= Date: Sat, 6 Apr 2019 18:57:58 +0200 Subject: [PATCH 12/53] Fix wrong dump for PO files --- src/Symfony/Component/Translation/Dumper/PoFileDumper.php | 2 +- .../Component/Translation/Tests/Dumper/PoFileDumperTest.php | 2 +- .../Component/Translation/Tests/Loader/PoFileLoaderTest.php | 2 +- .../Component/Translation/Tests/fixtures/resources.po | 5 ++++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Translation/Dumper/PoFileDumper.php b/src/Symfony/Component/Translation/Dumper/PoFileDumper.php index e9673b6d29908..0f7e6fa834bc2 100644 --- a/src/Symfony/Component/Translation/Dumper/PoFileDumper.php +++ b/src/Symfony/Component/Translation/Dumper/PoFileDumper.php @@ -40,7 +40,7 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti $newLine = true; } $output .= sprintf('msgid "%s"'."\n", $this->escape($source)); - $output .= sprintf('msgstr "%s"', $this->escape($target)); + $output .= sprintf('msgstr "%s"'."\n", $this->escape($target)); } return $output; diff --git a/src/Symfony/Component/Translation/Tests/Dumper/PoFileDumperTest.php b/src/Symfony/Component/Translation/Tests/Dumper/PoFileDumperTest.php index d694a6dd3fb11..960ec2df6500c 100644 --- a/src/Symfony/Component/Translation/Tests/Dumper/PoFileDumperTest.php +++ b/src/Symfony/Component/Translation/Tests/Dumper/PoFileDumperTest.php @@ -20,7 +20,7 @@ class PoFileDumperTest extends TestCase public function testFormatCatalogue() { $catalogue = new MessageCatalogue('en'); - $catalogue->add(['foo' => 'bar']); + $catalogue->add(['foo' => 'bar', 'bar' => 'foo']); $dumper = new PoFileDumper(); diff --git a/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php index 4bf2ee6545b6f..d8e2c1993ba1c 100644 --- a/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php +++ b/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php @@ -23,7 +23,7 @@ public function testLoad() $resource = __DIR__.'/../fixtures/resources.po'; $catalogue = $loader->load($resource, 'en', 'domain1'); - $this->assertEquals(['foo' => 'bar'], $catalogue->all('domain1')); + $this->assertEquals(['foo' => 'bar', 'bar' => 'foo'], $catalogue->all('domain1')); $this->assertEquals('en', $catalogue->getLocale()); $this->assertEquals([new FileResource($resource)], $catalogue->getResources()); } diff --git a/src/Symfony/Component/Translation/Tests/fixtures/resources.po b/src/Symfony/Component/Translation/Tests/fixtures/resources.po index ccfce6bb98d46..a20e619828ce6 100644 --- a/src/Symfony/Component/Translation/Tests/fixtures/resources.po +++ b/src/Symfony/Component/Translation/Tests/fixtures/resources.po @@ -5,4 +5,7 @@ msgstr "" "Language: en\n" msgid "foo" -msgstr "bar" \ No newline at end of file +msgstr "bar" + +msgid "bar" +msgstr "foo" From 7113a53e197193537eabea85739a61d7304c5148 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 6 Apr 2019 19:28:56 +0200 Subject: [PATCH 13/53] fix horizontal spacing of inlined Bootstrap forms --- .../Resources/views/Form/bootstrap_3_layout.html.twig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 e1164cdfbce1b..708e149bce82b 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 @@ -108,10 +108,10 @@ {% block form_row -%}
- {{- form_label(form) -}} - {{- form_widget(form) -}} - {{- form_errors(form) -}} -
+ {{- form_label(form) }} {# -#} + {{ form_widget(form) }} {# -#} + {{ form_errors(form) }} {# -#} + {# -#} {%- endblock form_row %} {% block button_row -%} From a53e0fe1cdce66ad9ebb9a25e661337e006dfb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sat, 6 Apr 2019 21:12:52 +0200 Subject: [PATCH 14/53] Use a class name that does not actually exist Using "Foo", a class name that corresponds to no less than 22 fixture classes, results in the first found "Foo" being loaded when one is found by the ClassNotFoundFatalErrorHandler error handler, I am not sure exactly why, but it is not really a big issue because this is a fatal error handler and execution is not supposed to continue after that. Except that is very much the case when running the whole test suite sequentially with ./phpunit . Then we arrive to the DI component test suite, and a failure happens because \\foo was not supposed to be defined: > Failed asserting that exception message 'The definition for "\foo" has > no class attribute, and appears to reference a class or interface in the > global namespace. Leaving out the "class" attribute is only allowed for > namespaced classes. Please specify the class attribute explicitly to get > rid of this error.' contains 'The definition for "\foo" has no class.'. --- src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php index c1dea75bbd90e..15de37c7b7808 100644 --- a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -525,7 +525,7 @@ public function testHandleFatalError() */ public function testHandleErrorException() { - $exception = new \Error("Class 'Foo' not found"); + $exception = new \Error("Class 'IReallyReallyDoNotExistAnywhereInTheRepositoryISwear' not found"); $handler = new ErrorHandler(); $handler->setExceptionHandler(function () use (&$args) { @@ -535,7 +535,7 @@ public function testHandleErrorException() $handler->handleException($exception); $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]); - $this->assertStringStartsWith("Attempted to load class \"Foo\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage()); + $this->assertStringStartsWith("Attempted to load class \"IReallyReallyDoNotExistAnywhereInTheRepositoryISwear\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage()); } /** From 6d51a04b11ee6cf85cdc5c1b6fb36ab1cb456308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sat, 6 Apr 2019 18:22:13 +0200 Subject: [PATCH 15/53] Run test in separate process This test calls code that defines some environment variables, which in turn trigger the registration of a the deprecation error handler, which causes unexpected issues when testing other components. --- src/Symfony/Component/BrowserKit/Tests/ClientTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php index ab6d118589c11..a21a9481a7ac6 100644 --- a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php @@ -621,6 +621,9 @@ public function testRestart() $this->assertEquals([], $client->getCookieJar()->all(), '->restart() clears the cookies'); } + /** + * @runInSeparateProcess + */ public function testInsulatedRequests() { $client = new TestClient(); From e55f6e6f5e63cf840caaf592e8b5d7934ffb953c Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 6 Apr 2019 20:39:34 +0200 Subject: [PATCH 16/53] fix tests --- .../Tests/Compiler/AutowirePassTest.php | 10 ++++-- .../Compiler/ResolveBindingsPassTest.php | 33 +++---------------- .../Translation/DataCollectorTranslator.php | 4 ++- .../Component/Translation/composer.json | 2 ++ 4 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 6235ed50be25c..31fa665ae7a85 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -21,9 +21,9 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic; use Symfony\Component\DependencyInjection\TypedReference; -use Symfony\Component\HttpKernel\HttpKernelInterface; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; @@ -606,13 +606,17 @@ public function testSetterInjection() ); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @exceptedExceptionMessage Invalid service "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy": method "setLogger()" does not exist. + */ public function testWithNonExistingSetterAndAutowiring() { $container = new ContainerBuilder(); - $definition = $container->register(HttpKernelInterface::class, HttpKernelInterface::class)->setAutowired(true); + $definition = $container->register(CaseSensitiveClass::class, CaseSensitiveClass::class)->setAutowired(true); $definition->addMethodCall('setLogger'); - $this->expectException(RuntimeException::class); + (new ResolveClassPass())->process($container); (new AutowireRequiredMethodsPass())->process($container); (new AutowirePass())->process($container); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php index 84b3d6c652fb6..7bbecf6207f46 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php @@ -17,13 +17,11 @@ use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy; use Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists; use Symfony\Component\DependencyInjection\TypedReference; -use Symfony\Component\HttpKernel\HttpKernelInterface; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; @@ -115,6 +113,10 @@ public function testScalarSetter() $this->assertEquals([['setDefaultLocale', ['fr']]], $definition->getMethodCalls()); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @exceptedExceptionMessage Invalid service "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy": method "setLogger()" does not exist. + */ public function testWithNonExistingSetterAndBinding() { $container = new ContainerBuilder(); @@ -123,36 +125,11 @@ public function testWithNonExistingSetterAndBinding() '$c' => (new Definition('logger'))->setFactory('logger'), ]; - $definition = $container->register(HttpKernelInterface::class, HttpKernelInterface::class); - $definition->addMethodCall('setLogger'); - $definition->setBindings($bindings); - $this->expectException(RuntimeException::class); - - $pass = new ResolveBindingsPass(); - $pass->process($container); - } - - public function testTupleBinding() - { - $container = new ContainerBuilder(); - - $bindings = [ - '$c' => new BoundArgument(new Reference('bar')), - CaseSensitiveClass::class.'$c' => new BoundArgument(new Reference('foo')), - ]; - $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); - $definition->addMethodCall('setSensitiveClass'); - $definition->addMethodCall('setAnotherC'); + $definition->addMethodCall('setLogger'); $definition->setBindings($bindings); $pass = new ResolveBindingsPass(); $pass->process($container); - - $expected = [ - ['setSensitiveClass', [new Reference('foo')]], - ['setAnotherC', [new Reference('bar')]], - ]; - $this->assertEquals($expected, $definition->getMethodCalls()); } } diff --git a/src/Symfony/Component/Translation/DataCollectorTranslator.php b/src/Symfony/Component/Translation/DataCollectorTranslator.php index c0cc05996aa3e..7eaf928e7ff93 100644 --- a/src/Symfony/Component/Translation/DataCollectorTranslator.php +++ b/src/Symfony/Component/Translation/DataCollectorTranslator.php @@ -93,7 +93,9 @@ public function getCatalogue($locale = null) */ public function warmUp($cacheDir) { - return $this->translator->warmUp($cacheDir); + if ($this->translator instanceof WarmableInterface) { + $this->translator->warmUp($cacheDir); + } } /** diff --git a/src/Symfony/Component/Translation/composer.json b/src/Symfony/Component/Translation/composer.json index 45ff664ca4391..93c1236f27323 100644 --- a/src/Symfony/Component/Translation/composer.json +++ b/src/Symfony/Component/Translation/composer.json @@ -22,7 +22,9 @@ "require-dev": { "symfony/config": "~2.8|~3.0|~4.0", "symfony/dependency-injection": "~3.4|~4.0", + "symfony/http-kernel": "~3.4|~4.0", "symfony/intl": "^2.8.18|^3.2.5|~4.0", + "symfony/var-dumper": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0", "symfony/finder": "~2.8|~3.0|~4.0", "psr/log": "~1.0" From 6a732dc0316b51812b5353eb5a2bc211d383c375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Egyed?= Date: Sat, 6 Apr 2019 23:15:33 +0200 Subject: [PATCH 17/53] [Validator] Translate unique collection message to Hungarian --- .../Validator/Resources/translations/validators.hu.xlf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf index c527c58d2d9e9..50d433f665ec5 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf @@ -334,6 +334,10 @@ This value should be valid JSON. Ez az érték érvényes JSON kell, hogy legyen. + + This collection should contain only unique elements. + Ez a gyűjtemény csak egyedi elemeket tartalmazhat. + From 79b1fb83338b73af25f1949bfcea54a6b74905c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sun, 7 Apr 2019 09:54:46 +0200 Subject: [PATCH 18/53] Handle case where no translations were found Right now, the program emits a warning when trying to use max() on an empty array. --- .../Translation/Resources/bin/translation-status.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Symfony/Component/Translation/Resources/bin/translation-status.php b/src/Symfony/Component/Translation/Resources/bin/translation-status.php index acc8b4e227222..0d37c3e0aa38b 100644 --- a/src/Symfony/Component/Translation/Resources/bin/translation-status.php +++ b/src/Symfony/Component/Translation/Resources/bin/translation-status.php @@ -159,6 +159,11 @@ function printTitle($title) function printTable($translations, $verboseOutput) { + if (0 === count($translations)) { + echo 'No translations found'; + + return; + } $longestLocaleNameLength = max(array_map('strlen', array_keys($translations))); foreach ($translations as $locale => $translation) { From 7db9200279319a5730d7bd44cd2db137d35dde43 Mon Sep 17 00:00:00 2001 From: Philipp Cordes Date: Sun, 6 Jan 2019 15:04:00 +0100 Subject: [PATCH 19/53] [Validator] Only traverse arrays that are cascaded into --- .../Validator/Constraints/Collection.php | 2 +- .../Tests/Validator/AbstractValidatorTest.php | 24 +++++++++++++++++++ .../RecursiveContextualValidator.php | 17 +++++-------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Validator/Constraints/Collection.php b/src/Symfony/Component/Validator/Constraints/Collection.php index 21427722d9491..86418690b8d2b 100644 --- a/src/Symfony/Component/Validator/Constraints/Collection.php +++ b/src/Symfony/Component/Validator/Constraints/Collection.php @@ -68,7 +68,7 @@ protected function initializeNestedConstraints() } if (!$field instanceof Optional && !$field instanceof Required) { - $this->fields[$fieldName] = $field = new Required($field); + $this->fields[$fieldName] = new Required($field); } } } diff --git a/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php index ecfc10feb643a..884ccc7da0f95 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php @@ -589,6 +589,30 @@ public function testRecursiveArrayReference() $this->assertNull($violations[0]->getCode()); } + public function testOnlyCascadedArraysAreTraversed() + { + $entity = new Entity(); + $entity->reference = ['key' => new Reference()]; + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addPropertyConstraint('reference', new Callback([ + 'callback' => function () {}, + 'groups' => 'Group', + ])); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(0, $violations); + } + public function testArrayTraversalCannotBeDisabled() { $entity = new Entity(); diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index 50253c50e1bb4..dc139c36d4254 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -352,24 +352,18 @@ private function validateObject($object, $propertyPath, array $groups, $traversa * Validates each object in a collection against the constraints defined * for their classes. * - * If the parameter $recursive is set to true, nested {@link \Traversable} - * objects are iterated as well. Nested arrays are always iterated, - * regardless of the value of $recursive. + * Nested arrays are also iterated. * * @param iterable $collection The collection * @param string $propertyPath The current property path * @param (string|GroupSequence)[] $groups The validated groups * @param ExecutionContextInterface $context The current execution context - * - * @see ClassNode - * @see CollectionNode */ private function validateEachObjectIn($collection, $propertyPath, array $groups, ExecutionContextInterface $context) { foreach ($collection as $key => $value) { if (\is_array($value)) { - // Arrays are always cascaded, independent of the specified - // traversal strategy + // Also traverse nested arrays $this->validateEachObjectIn( $value, $propertyPath.'['.$key.']', @@ -599,7 +593,8 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m * in the passed metadata object. Then, if the value is an instance of * {@link \Traversable} and the selected traversal strategy permits it, * the value is traversed and each nested object validated against its own - * constraints. Arrays are always traversed. + * constraints. If the value is an array, it is traversed regardless of + * the given strategy. * * @param mixed $value The validated value * @param object|null $object The current object @@ -658,8 +653,8 @@ private function validateGenericNode($value, $object, $cacheKey, MetadataInterfa $cascadingStrategy = $metadata->getCascadingStrategy(); - // Quit unless we have an array or a cascaded object - if (!\is_array($value) && !($cascadingStrategy & CascadingStrategy::CASCADE)) { + // Quit unless we cascade + if (!($cascadingStrategy & CascadingStrategy::CASCADE)) { return; } From 331b601fed1cec3d323b7f365878bb830c2d878c Mon Sep 17 00:00:00 2001 From: Hugo Hamon Date: Sun, 7 Apr 2019 09:35:09 +0200 Subject: [PATCH 20/53] [3.4] [Validator] Add missing french validation translations. --- .../Resources/translations/validators.fr.xlf | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf index 7b1799d53315c..d1601f72df95e 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf @@ -334,6 +334,34 @@ This value should be valid JSON. Cette valeur doit être un JSON valide. + + This collection should contain only unique elements. + Cette collection ne doit pas comporter de doublons. + + + This value should be positive. + Cette valeur doit être strictement positive. + + + This value should be either positive or zero. + Cette valeur doit être supérieure ou égale à zéro. + + + This value should be negative. + Cette valeur doit être strictement négative. + + + This value should be either negative or zero. + Cette valeur doit être inférieure ou égale à zéro. + + + This value is not a valid timezone. + Cette valeur n'est pas un fuseau horaire valide. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ce mot de passe a été révélé par une faille de sécurité et ne doit plus être utilisé. Veuillez utiliser un autre mot de passe. + From 98b9be9b6accfbb543dd57fedf9da980f5addb2c Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sat, 6 Apr 2019 18:45:14 +0200 Subject: [PATCH 21/53] [Cache] fix using ProxyAdapter inside TagAwareAdapter --- .../Component/Cache/Adapter/ProxyAdapter.php | 24 +++++++++--- .../Cache/Adapter/TagAwareAdapter.php | 1 - ...TagAwareAndProxyAdapterIntegrationTest.php | 38 +++++++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index 9060523609159..0b7918287e90a 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -45,12 +45,15 @@ public function __construct(CacheItemPoolInterface $pool, $namespace = '', $defa function ($key, $innerItem) use ($defaultLifetime, $poolHash) { $item = new CacheItem(); $item->key = $key; - $item->value = $innerItem->get(); - $item->isHit = $innerItem->isHit(); $item->defaultLifetime = $defaultLifetime; - $item->innerItem = $innerItem; $item->poolHash = $poolHash; - $innerItem->set(null); + + if (null !== $innerItem) { + $item->value = $innerItem->get(); + $item->isHit = $innerItem->isHit(); + $item->innerItem = $innerItem; + $innerItem->set(null); + } return $item; }, @@ -156,7 +159,18 @@ private function doSave(CacheItemInterface $item, $method) if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) { $expiry = time() + $item["\0*\0defaultLifetime"]; } - $innerItem = $item["\0*\0poolHash"] === $this->poolHash ? $item["\0*\0innerItem"] : $this->pool->getItem($this->namespace.$item["\0*\0key"]); + + if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) { + $innerItem = $item["\0*\0innerItem"]; + } elseif ($this->pool instanceof AdapterInterface) { + // this is an optimization specific for AdapterInterface implementations + // so we can save a round-trip to the backend by just creating a new item + $f = $this->createCacheItem; + $innerItem = $f($this->namespace.$item["\0*\0key"], null); + } else { + $innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]); + } + $innerItem->set($item["\0*\0value"]); $innerItem->expiresAt(null !== $expiry ? \DateTime::createFromFormat('U', $expiry) : null); diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index d453e271aef4c..362aceed0eb18 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -48,7 +48,6 @@ function ($key, $value, CacheItem $protoItem) { $item->value = $value; $item->defaultLifetime = $protoItem->defaultLifetime; $item->expiry = $protoItem->expiry; - $item->innerItem = $protoItem->innerItem; $item->poolHash = $protoItem->poolHash; return $item; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php new file mode 100644 index 0000000000000..b11c1f2870545 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php @@ -0,0 +1,38 @@ +getItem('foo'); + $item->tag(['tag1', 'tag2']); + $item->set('bar'); + $cache->save($item); + + $this->assertSame('bar', $cache->getItem('foo')->get()); + } + + public function dataProvider() + { + return [ + [new ArrayAdapter()], + // also testing with a non-AdapterInterface implementation + // because the ProxyAdapter behaves slightly different for those + [new ExternalAdapter()], + ]; + } +} From 62e5a91150968f03117f3b85045d9b11f1a057ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Egyed?= Date: Sat, 6 Apr 2019 23:31:05 +0200 Subject: [PATCH 22/53] [Validator] Add missing Hungarian translations --- .../Resources/translations/validators.hu.xlf | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf index 50d433f665ec5..300eb5f68fb97 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf @@ -335,9 +335,29 @@ Ez az érték érvényes JSON kell, hogy legyen. + This value should be positive. + Ennek az értéknek pozitívnak kell lennie. + + + This value should be either positive or zero. + Ennek az értéknek pozitívnak vagy nullának kell lennie. + + + This value should be negative. + Ennek az értéknek negatívnak kell lennie. + + + This value should be either negative or zero. + Ennek az értéknek negatívnak vagy nullának kell lennie. + + This collection should contain only unique elements. Ez a gyűjtemény csak egyedi elemeket tartalmazhat. + + This value is not a valid timezone. + Ez az érték nem egy érvényes időzóna. + From 9c41842756d4313025c0d2cecbf74daf3c1494a9 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sun, 7 Apr 2019 11:15:59 +0200 Subject: [PATCH 23/53] fix translating file validation error message --- .../FrameworkBundle/Resources/config/form.xml | 2 +- .../Form/Extension/Core/Type/FileType.php | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index 7dd51bbed6a6a..5088554ff1947 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -93,7 +93,7 @@ The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. + The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index a7f912d5e3109..f8afce2ee5a4d 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -20,6 +20,7 @@ use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Translation\TranslatorInterface; class FileType extends AbstractType { @@ -32,6 +33,13 @@ class FileType extends AbstractType self::MIB_BYTES => 'MiB', ]; + private $translator; + + public function __construct(TranslatorInterface $translator = null) + { + $this->translator = $translator; + } + /** * {@inheritdoc} */ @@ -150,7 +158,13 @@ private function getFileUploadError($errorCode) $messageTemplate = 'The file could not be uploaded.'; } - return new FormError($messageTemplate, $messageTemplate, $messageParameters); + if (null !== $this->translator) { + $message = $this->translator->trans($messageTemplate, $messageParameters); + } else { + $message = strtr($messageTemplate, $messageParameters); + } + + return new FormError($message, $messageTemplate, $messageParameters); } /** From 83f675d9f3411065a7181364b2d5aec051bfda22 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sun, 7 Apr 2019 09:35:31 +0200 Subject: [PATCH 24/53] sync validator translations --- .../Resources/translations/validators.de.xlf | 24 +++++++++++++++++++ .../Resources/translations/validators.en.xlf | 24 +++++++++++++++++++ .../Resources/translations/validators.vi.xlf | 24 +++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf index ada215c1c6b17..f33e4d602ca15 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf @@ -338,6 +338,30 @@ This collection should contain only unique elements. Diese Sammlung darf keine doppelten Elemente enthalten. + + This value should be positive. + Diese Zahl sollte positiv sein. + + + This value should be either positive or zero. + Diese Zahl sollte entweder positiv oder 0 sein. + + + This value should be negative. + Diese Zahl sollte negativ sein. + + + This value should be either negative or zero. + Diese Zahl sollte entweder negativ oder 0 sein. + + + This value is not a valid timezone. + Dieser Wert ist keine gültige Zeitzone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Dieses Passwort ist Teil eines Datenlecks, es darf nicht verwendet werden. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf index a2dc22f5cb8e9..d5d9d20997fc0 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf @@ -338,6 +338,30 @@ This collection should contain only unique elements. This collection should contain only unique elements. + + This value should be positive. + This value should be positive. + + + This value should be either positive or zero. + This value should be either positive or zero. + + + This value should be negative. + This value should be negative. + + + This value should be either negative or zero. + This value should be either negative or zero. + + + This value is not a valid timezone. + This value is not a valid timezone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + This password has been leaked in a data breach, it must not be used. Please use another password. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf index 89c0ecc90dc98..95dd7d6665997 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.vi.xlf @@ -338,6 +338,30 @@ This collection should contain only unique elements. Danh sách này chỉ nên chứa các phần tử khác nhau. + + This value should be positive. + Giá trị này có thể thực hiện được. + + + This value should be either positive or zero. + Giá trị này có thể thực hiện được hoặc bằng không. + + + This value should be negative. + Giá trị này nên bị từ chối. + + + This value should be either negative or zero. + Giá trị này nên bị từ chối hoặc bằng không. + + + This value is not a valid timezone. + Giá trị này không phải là múi giờ hợp lệ. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Mật khẩu này đã bị rò rỉ dữ liệu, không được sử dụng nữa. Xin vui lòng sử dụng mật khẩu khác. + From a36c7315f42ce7c1ac4263b1b959727e601c470b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Sun, 7 Apr 2019 14:43:17 +0200 Subject: [PATCH 25/53] [Debug] Fixed error handling when an error is already handled when another error is already handled (5) --- src/Symfony/Component/Debug/ExceptionHandler.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index 2f3d56601271c..6abe6bb7eb685 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -372,7 +372,7 @@ private function formatPath($path, $line) $fmt = $this->fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); if (!$fmt) { - return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); + return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); } if (\is_string($fmt)) { @@ -388,7 +388,11 @@ private function formatPath($path, $line) $link = strtr($fmt[0], ['%f' => $path, '%l' => $line]); } else { - $link = $fmt->format($path, $line); + try { + $link = $fmt->format($path, $line); + } catch (\Exception $e) { + return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); + } } return sprintf('in %s%s', $this->escapeHtml($link), $file, 0 < $line ? ' line '.$line : ''); From fdb668e051ce45177cbb6eb7ee6f0e03c248e321 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sun, 7 Apr 2019 14:41:46 +0200 Subject: [PATCH 26/53] prevent mixup of the object to populate --- .../Normalizer/AbstractNormalizer.php | 2 ++ .../Tests/Normalizer/ObjectNormalizerTest.php | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index f99c61a36bd32..ef1166a493497 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -330,6 +330,8 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref return $object; } + // clean up even if no match + unset($context[static::OBJECT_TO_POPULATE]); $constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes); if ($constructor) { diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 4068479c87001..4b72e2a4b5032 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -315,6 +315,30 @@ public function testGroupsDenormalizeWithNameConverter() ); } + public function testObjectToPopulateNoMatch() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $this->normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); + new Serializer([$this->normalizer]); + + $objectToPopulate = new ObjectInner(); + $objectToPopulate->foo = 'foo'; + + $outer = $this->normalizer->denormalize([ + 'foo' => 'foo', + 'inner' => [ + 'bar' => 'bar', + ], + ], ObjectOuter::class, null, [ObjectNormalizer::OBJECT_TO_POPULATE => $objectToPopulate]); + + $this->assertInstanceOf(ObjectOuter::class, $outer); + $inner = $outer->getInner(); + $this->assertInstanceOf(ObjectInner::class, $inner); + $this->assertNotSame($objectToPopulate, $inner); + $this->assertSame('bar', $inner->bar); + $this->assertNull($inner->foo); + } + /** * @dataProvider provideCallbacks */ @@ -936,6 +960,9 @@ class ObjectOuter { public $foo; public $bar; + /** + * @var ObjectInner + */ private $inner; private $date; From 934118b131f21fb03c6456d2bc0b089e744b4e04 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Sun, 7 Apr 2019 14:44:08 +0200 Subject: [PATCH 27/53] Fix the configurability of CoreExtension deps in standalone usage --- .../Form/Extension/Core/CoreExtension.php | 2 +- .../Component/Form/FormFactoryBuilder.php | 27 +++++++++++++++++++ src/Symfony/Component/Form/Forms.php | 7 +---- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php index 4eb980bf9cdff..0db802501c7d0 100644 --- a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php +++ b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php @@ -69,7 +69,7 @@ protected function loadTypes() new Type\TimeType(), new Type\TimezoneType(), new Type\UrlType(), - new Type\FileType(), + new Type\FileType($this->translator), new Type\ButtonType(), new Type\SubmitType(), new Type\ResetType(), diff --git a/src/Symfony/Component/Form/FormFactoryBuilder.php b/src/Symfony/Component/Form/FormFactoryBuilder.php index bccb86f40791d..759affa3c02f1 100644 --- a/src/Symfony/Component/Form/FormFactoryBuilder.php +++ b/src/Symfony/Component/Form/FormFactoryBuilder.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Form; +use Symfony\Component\Form\Extension\Core\CoreExtension; + /** * The default implementation of FormFactoryBuilderInterface. * @@ -18,6 +20,8 @@ */ class FormFactoryBuilder implements FormFactoryBuilderInterface { + private $forceCoreExtension; + /** * @var ResolvedFormTypeFactoryInterface */ @@ -43,6 +47,14 @@ class FormFactoryBuilder implements FormFactoryBuilderInterface */ private $typeGuessers = []; + /** + * @param bool $forceCoreExtension + */ + public function __construct($forceCoreExtension = false) + { + $this->forceCoreExtension = $forceCoreExtension; + } + /** * {@inheritdoc} */ @@ -144,6 +156,21 @@ public function getFormFactory() { $extensions = $this->extensions; + if ($this->forceCoreExtension) { + $hasCoreExtension = false; + + foreach ($extensions as $extension) { + if ($extension instanceof CoreExtension) { + $hasCoreExtension = true; + break; + } + } + + if (!$hasCoreExtension) { + array_unshift($extensions, new CoreExtension()); + } + } + if (\count($this->types) > 0 || \count($this->typeExtensions) > 0 || \count($this->typeGuessers) > 0) { if (\count($this->typeGuessers) > 1) { $typeGuesser = new FormTypeGuesserChain($this->typeGuessers); diff --git a/src/Symfony/Component/Form/Forms.php b/src/Symfony/Component/Form/Forms.php index 7c61d640e21ca..b8e2d27fec3e8 100644 --- a/src/Symfony/Component/Form/Forms.php +++ b/src/Symfony/Component/Form/Forms.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Form; -use Symfony\Component\Form\Extension\Core\CoreExtension; - /** * Entry point of the Form component. * @@ -105,10 +103,7 @@ public static function createFormFactory() */ public static function createFormFactoryBuilder() { - $builder = new FormFactoryBuilder(); - $builder->addExtension(new CoreExtension()); - - return $builder; + return new FormFactoryBuilder(true); } /** From 35418be8c40246dc8f1d07be80a12921242a0d3d Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Sun, 7 Apr 2019 13:44:37 +0200 Subject: [PATCH 28/53] [Form] Fix tests --- .../Form/Tests/Extension/Core/Type/FileTypeTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php index 73d39ace025db..777cb883a07b4 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php @@ -11,16 +11,26 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; +use Symfony\Component\Form\Extension\Core\CoreExtension; use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; use Symfony\Component\Form\NativeRequestHandler; use Symfony\Component\Form\RequestHandlerInterface; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\Translation\TranslatorInterface; class FileTypeTest extends BaseTypeTest { const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\FileType'; + protected function getExtensions() + { + $translator = $this->createMock(TranslatorInterface::class); + $translator->expects($this->any())->method('trans')->willReturnArgument(0); + + return array_merge(parent::getExtensions(), [new CoreExtension(null, null, $translator)]); + } + // https://github.com/symfony/symfony/pull/5028 public function testSetData() { From 64fa13bf44ea838ab95afd673f6baa68203d76ab Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sun, 7 Apr 2019 19:26:29 +0200 Subject: [PATCH 29/53] fix PHPUnit 4.8 compatibility --- .../Component/Form/Tests/Extension/Core/Type/FileTypeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php index 777cb883a07b4..c566786c8cf17 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php @@ -25,7 +25,7 @@ class FileTypeTest extends BaseTypeTest protected function getExtensions() { - $translator = $this->createMock(TranslatorInterface::class); + $translator = $this->getMockBuilder(TranslatorInterface::class)->getMock(); $translator->expects($this->any())->method('trans')->willReturnArgument(0); return array_merge(parent::getExtensions(), [new CoreExtension(null, null, $translator)]); From 00883fc4099e7155f709a19b4d98085c282d8df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sun, 7 Apr 2019 15:06:33 +0200 Subject: [PATCH 30/53] Make tests independent from each other Environment variables set in a test need to be restored to their previous values or unset if we want to be able to run tests independently. --- .../Component/Console/Tests/ApplicationTest.php | 10 ++++++++++ .../Console/Tests/Helper/ProgressBarTest.php | 13 +++++++++++++ .../Console/Tests/Style/SymfonyStyleTest.php | 4 +++- .../Component/Console/Tests/TerminalTest.php | 15 +++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index ae9d130f502ab..85484b5c5a7b4 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -41,6 +41,13 @@ class ApplicationTest extends TestCase { protected static $fixturesPath; + private $colSize; + + protected function setUp() + { + $this->colSize = getenv('COLUMNS'); + } + public static function setUpBeforeClass() { self::$fixturesPath = realpath(__DIR__.'/Fixtures/'); @@ -383,6 +390,7 @@ public function testFindWithCommandLoader() */ public function testFindWithAmbiguousAbbreviations($abbreviation, $expectedExceptionMessage) { + putenv('COLUMNS=120'); if (method_exists($this, 'expectException')) { $this->expectException('Symfony\Component\Console\Exception\CommandNotFoundException'); $this->expectExceptionMessage($expectedExceptionMessage); @@ -468,6 +476,7 @@ public function provideInvalidCommandNamesSingle() public function testFindAlternativeExceptionMessageMultiple() { + putenv('COLUMNS=120'); $application = new Application(); $application->add(new \FooCommand()); $application->add(new \Foo1Command()); @@ -1692,6 +1701,7 @@ public function testErrorIsRethrownIfNotHandledByConsoleErrorEventWithCatchingEn protected function tearDown() { + putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize); putenv('SHELL_VERBOSITY'); unset($_ENV['SHELL_VERBOSITY']); unset($_SERVER['SHELL_VERBOSITY']); diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index e352311ccad09..214e943d7319e 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -21,6 +21,19 @@ */ class ProgressBarTest extends TestCase { + private $colSize; + + protected function setUp() + { + $this->colSize = getenv('COLUMNS'); + putenv('COLUMNS=120'); + } + + protected function tearDown() + { + putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize); + } + public function testMultipleStart() { $bar = new ProgressBar($output = $this->getOutputStream()); diff --git a/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php b/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php index e6e061f4416b6..a6feb122a12a2 100644 --- a/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php +++ b/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php @@ -26,9 +26,11 @@ class SymfonyStyleTest extends TestCase protected $command; /** @var CommandTester */ protected $tester; + private $colSize; protected function setUp() { + $this->colSize = getenv('COLUMNS'); putenv('COLUMNS=121'); $this->command = new Command('sfstyle'); $this->tester = new CommandTester($this->command); @@ -36,7 +38,7 @@ protected function setUp() protected function tearDown() { - putenv('COLUMNS'); + putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize); $this->command = null; $this->tester = null; } diff --git a/src/Symfony/Component/Console/Tests/TerminalTest.php b/src/Symfony/Component/Console/Tests/TerminalTest.php index 91af1d0ab459d..ca3b874374b61 100644 --- a/src/Symfony/Component/Console/Tests/TerminalTest.php +++ b/src/Symfony/Component/Console/Tests/TerminalTest.php @@ -16,6 +16,15 @@ class TerminalTest extends TestCase { + private $colSize; + private $lineSize; + + protected function setUp() + { + $this->colSize = getenv('COLUMNS'); + $this->lineSize = getenv('LINES'); + } + public function test() { putenv('COLUMNS=100'); @@ -31,6 +40,12 @@ public function test() $this->assertSame(60, $terminal->getHeight()); } + protected function tearDown() + { + putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize); + putenv($this->lineSize ? 'LINES' : 'LINES='.$this->lineSize); + } + public function test_zero_values() { putenv('COLUMNS=0'); From a0c66a399ddc04ede9c2f053044e2bb898df2c9e Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Wed, 6 Feb 2019 00:57:29 +0100 Subject: [PATCH 31/53] Fix TestRunner compatibility to PhpUnit 8 --- .../Bridge/PhpUnit/Legacy/CommandForV5.php | 20 +++++++- .../Bridge/PhpUnit/Legacy/CommandForV6.php | 22 ++++++++- .../Bridge/PhpUnit/Legacy/TestRunnerForV5.php | 48 ------------------ .../Bridge/PhpUnit/Legacy/TestRunnerForV6.php | 49 ------------------- .../Bridge/PhpUnit/Legacy/TestRunnerForV7.php | 49 ------------------- .../Bridge/PhpUnit/TextUI/TestRunner.php | 26 ---------- 6 files changed, 39 insertions(+), 175 deletions(-) delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV5.php delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV6.php delete mode 100644 src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV7.php delete mode 100644 src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php index d4b5ea26d8cd8..95dcb1e5541fc 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php @@ -23,6 +23,24 @@ class CommandForV5 extends \PHPUnit_TextUI_Command */ protected function createRunner() { - return new TestRunnerForV5($this->arguments['loader']); + $listener = new SymfonyTestsListenerForV5(); + + $this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : array(); + + $registeredLocally = false; + + foreach ($this->arguments['listeners'] as $registeredListener) { + if ($registeredListener instanceof SymfonyTestsListenerForV5) { + $registeredListener->globalListenerDisabled(); + $registeredLocally = true; + break; + } + } + + if (!$registeredLocally) { + $this->arguments['listeners'][] = $listener; + } + + return parent::createRunner(); } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php index fc717ef415cb3..f8f75bb09a93b 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php @@ -13,7 +13,7 @@ use PHPUnit\TextUI\Command as BaseCommand; use PHPUnit\TextUI\TestRunner as BaseRunner; -use Symfony\Bridge\PhpUnit\TextUI\TestRunner; +use Symfony\Bridge\PhpUnit\SymfonyTestsListener; /** * {@inheritdoc} @@ -27,6 +27,24 @@ class CommandForV6 extends BaseCommand */ protected function createRunner(): BaseRunner { - return new TestRunner($this->arguments['loader']); + $listener = new SymfonyTestsListener(); + + $this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : []; + + $registeredLocally = false; + + foreach ($this->arguments['listeners'] as $registeredListener) { + if ($registeredListener instanceof SymfonyTestsListener) { + $registeredListener->globalListenerDisabled(); + $registeredLocally = true; + break; + } + } + + if (!$registeredLocally) { + $this->arguments['listeners'][] = $listener; + } + + return parent::createRunner(); } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV5.php deleted file mode 100644 index 7897861cf52f7..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV5.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -/** - * {@inheritdoc} - * - * @internal - */ -class TestRunnerForV5 extends \PHPUnit_TextUI_TestRunner -{ - /** - * {@inheritdoc} - */ - protected function handleConfiguration(array &$arguments) - { - $listener = new SymfonyTestsListenerForV5(); - - $result = parent::handleConfiguration($arguments); - - $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); - - $registeredLocally = false; - - foreach ($arguments['listeners'] as $registeredListener) { - if ($registeredListener instanceof SymfonyTestsListenerForV5) { - $registeredListener->globalListenerDisabled(); - $registeredLocally = true; - break; - } - } - - if (!$registeredLocally) { - $arguments['listeners'][] = $listener; - } - - return $result; - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV6.php deleted file mode 100644 index 6da7c65448532..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV6.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -use PHPUnit\TextUI\TestRunner as BaseRunner; -use Symfony\Bridge\PhpUnit\SymfonyTestsListener; - -/** - * {@inheritdoc} - * - * @internal - */ -class TestRunnerForV6 extends BaseRunner -{ - /** - * {@inheritdoc} - */ - protected function handleConfiguration(array &$arguments) - { - $listener = new SymfonyTestsListener(); - - parent::handleConfiguration($arguments); - - $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); - - $registeredLocally = false; - - foreach ($arguments['listeners'] as $registeredListener) { - if ($registeredListener instanceof SymfonyTestsListener) { - $registeredListener->globalListenerDisabled(); - $registeredLocally = true; - break; - } - } - - if (!$registeredLocally) { - $arguments['listeners'][] = $listener; - } - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV7.php deleted file mode 100644 index a175fb65d7f5a..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV7.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -use PHPUnit\TextUI\TestRunner as BaseRunner; -use Symfony\Bridge\PhpUnit\SymfonyTestsListener; - -/** - * {@inheritdoc} - * - * @internal - */ -class TestRunnerForV7 extends BaseRunner -{ - /** - * {@inheritdoc} - */ - protected function handleConfiguration(array &$arguments): void - { - $listener = new SymfonyTestsListener(); - - parent::handleConfiguration($arguments); - - $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); - - $registeredLocally = false; - - foreach ($arguments['listeners'] as $registeredListener) { - if ($registeredListener instanceof SymfonyTestsListener) { - $registeredListener->globalListenerDisabled(); - $registeredLocally = true; - break; - } - } - - if (!$registeredLocally) { - $arguments['listeners'][] = $listener; - } - } -} diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php deleted file mode 100644 index cda59209790d5..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\TextUI; - -if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunnerForV5', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner'); -} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunnerForV6', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner'); -} else { - class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunnerForV7', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner'); -} - -if (false) { - class TestRunner - { - } -} From 8fb2074972b720c5b5f64845870bfb475fe9fef3 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 8 Apr 2019 11:27:09 +0200 Subject: [PATCH 32/53] fix resetting the COLUMN environment variable --- .../Component/Console/Tests/ApplicationTest.php | 16 ++++++++-------- .../Console/Tests/Helper/ProgressBarTest.php | 2 +- .../Console/Tests/Style/SymfonyStyleTest.php | 2 +- .../Component/Console/Tests/TerminalTest.php | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 85484b5c5a7b4..200568f0701d7 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -48,6 +48,14 @@ protected function setUp() $this->colSize = getenv('COLUMNS'); } + protected function tearDown() + { + putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS'); + putenv('SHELL_VERBOSITY'); + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + } + public static function setUpBeforeClass() { self::$fixturesPath = realpath(__DIR__.'/Fixtures/'); @@ -1698,14 +1706,6 @@ public function testErrorIsRethrownIfNotHandledByConsoleErrorEventWithCatchingEn $this->assertSame($e->getMessage(), 'Class \'UnknownClass\' not found'); } } - - protected function tearDown() - { - putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize); - putenv('SHELL_VERBOSITY'); - unset($_ENV['SHELL_VERBOSITY']); - unset($_SERVER['SHELL_VERBOSITY']); - } } class CustomApplication extends Application diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index 214e943d7319e..a0be9b8a6d94d 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -31,7 +31,7 @@ protected function setUp() protected function tearDown() { - putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize); + putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS'); } public function testMultipleStart() diff --git a/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php b/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php index a6feb122a12a2..88d00c8a9926b 100644 --- a/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php +++ b/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php @@ -38,7 +38,7 @@ protected function setUp() protected function tearDown() { - putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize); + putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS'); $this->command = null; $this->tester = null; } diff --git a/src/Symfony/Component/Console/Tests/TerminalTest.php b/src/Symfony/Component/Console/Tests/TerminalTest.php index ca3b874374b61..93b8c44a78158 100644 --- a/src/Symfony/Component/Console/Tests/TerminalTest.php +++ b/src/Symfony/Component/Console/Tests/TerminalTest.php @@ -25,6 +25,12 @@ protected function setUp() $this->lineSize = getenv('LINES'); } + protected function tearDown() + { + putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS'); + putenv($this->lineSize ? 'LINES' : 'LINES='.$this->lineSize); + } + public function test() { putenv('COLUMNS=100'); @@ -40,12 +46,6 @@ public function test() $this->assertSame(60, $terminal->getHeight()); } - protected function tearDown() - { - putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize); - putenv($this->lineSize ? 'LINES' : 'LINES='.$this->lineSize); - } - public function test_zero_values() { putenv('COLUMNS=0'); From 926d228877deb81c6edf86d3802894c4877b9e92 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Sat, 6 Apr 2019 16:28:08 +0200 Subject: [PATCH 33/53] [Serializer] Respect ignored attributes in cache key of normalizer --- .../Normalizer/AbstractNormalizer.php | 9 ++-- .../Normalizer/AbstractObjectNormalizer.php | 48 +++++++++++++++---- .../Tests/Normalizer/ObjectNormalizerTest.php | 10 ++++ 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index ef1166a493497..c5bd3ffd97202 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -397,7 +397,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara } $parameterClass = $parameter->getClass()->getName(); - return $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $parameterName)); + return $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $parameterName, $format)); } return $parameterData; @@ -407,14 +407,15 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara } /** - * @param array $parentContext - * @param string $attribute + * @param array $parentContext + * @param string $attribute Attribute name + * @param string|null $format * * @return array * * @internal */ - protected function createChildContext(array $parentContext, $attribute) + protected function createChildContext(array $parentContext, $attribute/*, string $format = null */) { if (isset($parentContext[self::ATTRIBUTES][$attribute])) { $parentContext[self::ATTRIBUTES] = $parentContext[self::ATTRIBUTES][$attribute]; diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 17c31b132b29c..38908a9323ba1 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -94,7 +94,7 @@ public function normalize($object, $format = null, array $context = []) throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer', $attribute)); } - $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute))); + $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute, $format))); } return $data; @@ -128,15 +128,13 @@ protected function getAttributes($object, $format = null, array $context) return $allowedAttributes; } - if (isset($context['attributes'])) { - return $this->extractAttributes($object, $format, $context); - } + $attributes = $this->extractAttributes($object, $format, $context); - if (isset($this->attributesCache[$class])) { - return $this->attributesCache[$class]; + if ($context['cache_key']) { + $this->attributesCache[$key] = $attributes; } - return $this->attributesCache[$class] = $this->extractAttributes($object, $format, $context); + return $attributes; } /** @@ -276,7 +274,7 @@ private function validateAndDenormalize($currentClass, $attribute, $data, $forma throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer', $attribute, $class)); } - $childContext = $this->createChildContext($context, $attribute); + $childContext = $this->createChildContext($context, $attribute, $format); if ($this->serializer->supportsDenormalization($data, $class, $format, $childContext)) { return $this->serializer->denormalize($data, $class, $format, $childContext); } @@ -373,7 +371,32 @@ private function isMaxDepthReached(array $attributesMetadata, $class, $attribute } /** - * Gets the cache key to use. + * Overwritten to update the cache key for the child. + * + * We must not mix up the attribute cache between parent and children. + * + * {@inheritdoc} + */ + protected function createChildContext(array $parentContext, $attribute/*, string $format = null */) + { + if (\func_num_args() >= 3) { + $format = \func_get_arg(2); + } else { + // will be deprecated in version 4 + $format = null; + } + + $context = parent::createChildContext($parentContext, $attribute, $format); + // format is already included in the cache_key of the parent. + $context['cache_key'] = $this->getCacheKey($format, $context); + + return $context; + } + + /** + * Builds the cache key for the attributes cache. + * + * The key must be different for every option in the context that could change which attributes should be handled. * * @param string|null $format * @param array $context @@ -382,8 +405,13 @@ private function isMaxDepthReached(array $attributesMetadata, $class, $attribute */ private function getCacheKey($format, array $context) { + unset($context['cache_key']); // avoid artificially different keys try { - return md5($format.serialize($context)); + return md5($format.serialize([ + 'context' => $context, + 'ignored' => $this->ignoredAttributes, + 'camelized' => $this->camelizedAttributes, + ])); } catch (\Exception $exception) { // The context cannot be serialized, skip the cache return false; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 4b72e2a4b5032..b30739714b663 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -380,6 +380,16 @@ public function testIgnoredAttributes() ['fooBar' => 'foobar'], $this->normalizer->normalize($obj, 'any') ); + + $this->normalizer->setIgnoredAttributes(['foo', 'baz', 'camelCase', 'object']); + + $this->assertEquals( + [ + 'fooBar' => 'foobar', + 'bar' => 'bar', + ], + $this->normalizer->normalize($obj, 'any') + ); } public function testIgnoredAttributesDenormalize() From e238c893e975598f66f66349a3b1e2c374cb8c0b Mon Sep 17 00:00:00 2001 From: Florian Morello Date: Mon, 8 Apr 2019 17:40:55 +0200 Subject: [PATCH 34/53] Fix missing $extraDirs when open_basedir returns --- src/Symfony/Component/Process/ExecutableFinder.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Process/ExecutableFinder.php b/src/Symfony/Component/Process/ExecutableFinder.php index a621fc6918e5d..11ef5c8b22813 100644 --- a/src/Symfony/Component/Process/ExecutableFinder.php +++ b/src/Symfony/Component/Process/ExecutableFinder.php @@ -51,7 +51,10 @@ public function addSuffix($suffix) public function find($name, $default = null, array $extraDirs = []) { if (ini_get('open_basedir')) { - $searchPath = explode(PATH_SEPARATOR, ini_get('open_basedir')); + $searchPath = array_merge( + explode(PATH_SEPARATOR, ini_get('open_basedir')), + $extraDirs + ); $dirs = []; foreach ($searchPath as $path) { // Silencing against https://bugs.php.net/69240 From b93d2bf9415c790347d677adee268865bc786fe1 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 8 Apr 2019 18:15:54 +0200 Subject: [PATCH 35/53] fixed CS --- src/Symfony/Component/Process/ExecutableFinder.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Symfony/Component/Process/ExecutableFinder.php b/src/Symfony/Component/Process/ExecutableFinder.php index 11ef5c8b22813..cb4345e7bb424 100644 --- a/src/Symfony/Component/Process/ExecutableFinder.php +++ b/src/Symfony/Component/Process/ExecutableFinder.php @@ -51,10 +51,7 @@ public function addSuffix($suffix) public function find($name, $default = null, array $extraDirs = []) { if (ini_get('open_basedir')) { - $searchPath = array_merge( - explode(PATH_SEPARATOR, ini_get('open_basedir')), - $extraDirs - ); + $searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs); $dirs = []; foreach ($searchPath as $path) { // Silencing against https://bugs.php.net/69240 From 5790859275d2d4164eb7cc33ffdc52d79b60a52b Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 1 Mar 2019 19:12:11 +0200 Subject: [PATCH 36/53] Rework firewall access denied rule --- .../Http/Firewall/ExceptionListener.php | 4 +- .../Tests/Firewall/ExceptionListenerTest.php | 63 ++++++++++++++++--- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php index d107721471533..b3b5ccefec783 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php @@ -131,8 +131,6 @@ private function handleAccessDeniedException(GetResponseForExceptionEvent $event } catch (\Exception $e) { $event->setException($e); } - - return; } if (null !== $this->logger) { @@ -150,7 +148,7 @@ private function handleAccessDeniedException(GetResponseForExceptionEvent $event $subRequest = $this->httpUtils->createRequest($event->getRequest(), $this->errorPage); $subRequest->attributes->set(Security::ACCESS_DENIED_ERROR, $exception); - $event->setResponse($event->getKernel()->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true)); + $event->setResponse($event->getKernel()->handle($subRequest, HttpKernelInterface::SUB_REQUEST)); $event->allowCustomResponseCode(); } } catch (\Exception $e) { diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php index 53fedebcad705..3220e43e70e95 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ExceptionListenerTest.php @@ -130,10 +130,8 @@ public function testAccessDeniedExceptionFullFledgedAndWithAccessDeniedHandlerAn { $event = $this->createEvent($exception); - $accessDeniedHandler = $this->getMockBuilder('Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface')->getMock(); - $accessDeniedHandler->expects($this->once())->method('handle')->will($this->returnValue(new Response('error'))); + $listener = $this->createExceptionListener(null, $this->createTrustResolver(true), null, null, null, $this->createCustomAccessDeniedHandler(new Response('error'))); - $listener = $this->createExceptionListener(null, $this->createTrustResolver(true), null, null, null, $accessDeniedHandler); $listener->onKernelException($event); $this->assertEquals('error', $event->getResponse()->getContent()); @@ -147,16 +145,51 @@ public function testAccessDeniedExceptionNotFullFledged(\Exception $exception, \ { $event = $this->createEvent($exception); - $tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); - $tokenStorage->expects($this->once())->method('getToken')->will($this->returnValue($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock())); - - $listener = $this->createExceptionListener($tokenStorage, $this->createTrustResolver(false), null, $this->createEntryPoint()); + $listener = $this->createExceptionListener($this->createTokenStorage(), $this->createTrustResolver(false), null, $this->createEntryPoint()); $listener->onKernelException($event); $this->assertEquals('OK', $event->getResponse()->getContent()); $this->assertSame(null === $eventException ? $exception : $eventException, $event->getException()->getPrevious()); } + /** + * @dataProvider getAccessDeniedExceptionProvider + */ + public function testAccessDeniedExceptionNotFullFledgedAndWithAccessDeniedHandlerAndWithoutErrorPage(\Exception $exception, \Exception $eventException = null) + { + $event = $this->createEvent($exception); + + $listener = $this->createExceptionListener($this->createTokenStorage(), $this->createTrustResolver(false), null, $this->createEntryPoint(), null, $this->createCustomAccessDeniedHandler(new Response('denied', 403))); + $listener->onKernelException($event); + + $this->assertEquals('denied', $event->getResponse()->getContent()); + $this->assertEquals(403, $event->getResponse()->getStatusCode()); + $this->assertSame(null === $eventException ? $exception : $eventException, $event->getException()->getPrevious()); + } + + /** + * @dataProvider getAccessDeniedExceptionProvider + */ + public function testAccessDeniedExceptionNotFullFledgedAndWithoutAccessDeniedHandlerAndWithErrorPage(\Exception $exception, \Exception $eventException = null) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel->expects($this->once())->method('handle')->will($this->returnValue(new Response('Unauthorized', 401))); + + $event = $this->createEvent($exception, $kernel); + + $httpUtils = $this->getMockBuilder('Symfony\Component\Security\Http\HttpUtils')->getMock(); + $httpUtils->expects($this->once())->method('createRequest')->will($this->returnValue(Request::create('/error'))); + + $listener = $this->createExceptionListener($this->createTokenStorage(), $this->createTrustResolver(true), $httpUtils, null, '/error'); + $listener->onKernelException($event); + + $this->assertTrue($event->isAllowingCustomResponseCode()); + + $this->assertEquals('Unauthorized', $event->getResponse()->getContent()); + $this->assertEquals(401, $event->getResponse()->getStatusCode()); + $this->assertSame(null === $eventException ? $exception : $eventException, $event->getException()->getPrevious()); + } + public function getAccessDeniedExceptionProvider() { return [ @@ -168,6 +201,22 @@ public function getAccessDeniedExceptionProvider() ]; } + private function createTokenStorage() + { + $tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); + $tokenStorage->expects($this->once())->method('getToken')->will($this->returnValue($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock())); + + return $tokenStorage; + } + + private function createCustomAccessDeniedHandler(Response $response) + { + $accessDeniedHandler = $this->getMockBuilder('Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface')->getMock(); + $accessDeniedHandler->expects($this->once())->method('handle')->will($this->returnValue($response)); + + return $accessDeniedHandler; + } + private function createEntryPoint(Response $response = null) { $entryPoint = $this->getMockBuilder('Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface')->getMock(); From 0cf3227011c092c530628ff5ee4ca4f4ec392a0c Mon Sep 17 00:00:00 2001 From: Martijn Cuppens Date: Wed, 10 Apr 2019 11:35:05 +0200 Subject: [PATCH 37/53] Remove redundant `box-sizing` prefixes --- .../Resources/views/Profiler/toolbar.css.twig | 4 ---- 1 file changed, 4 deletions(-) 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 9c76e0ad8f33d..29702bb67a8a4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -5,8 +5,6 @@ background-color: #222; border-top-left-radius: 4px; bottom: 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; box-sizing: border-box; display: none; height: 36px; @@ -36,8 +34,6 @@ } .sf-toolbarreset * { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; box-sizing: content-box; vertical-align: baseline; letter-spacing: normal; From 3655bcfaf7a0019025b70ae00ed7c1ec038ef94f Mon Sep 17 00:00:00 2001 From: Martijn Cuppens Date: Wed, 10 Apr 2019 12:58:43 +0200 Subject: [PATCH 38/53] Remove redundant animation prefixes CSS animations can be used safely without any prefixes --- .../Resources/views/Profiler/toolbar.css.twig | 13 ------------- 1 file changed, 13 deletions(-) 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 29702bb67a8a4..8953316de06f2 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -363,21 +363,8 @@ div.sf-toolbar .sf-toolbar-block a:hover { text-align: right; } .sf-ajax-request-loading { - -webkit-animation: sf-blink .5s ease-in-out infinite; - -o-animation: sf-blink .5s ease-in-out infinite; - -moz-animation: sf-blink .5s ease-in-out infinite; animation: sf-blink .5s ease-in-out infinite; } -@-webkit-keyframes sf-blink { - 0% { background: #222; } - 50% { background: #444; } - 100% { background: #222; } -} -@-moz-keyframes sf-blink { - 0% { background: #222; } - 50% { background: #444; } - 100% { background: #222; } -} @keyframes sf-blink { 0% { background: #222; } 50% { background: #444; } From a56bf552ad5b844298fc27242ead879d3264f962 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Wed, 10 Apr 2019 18:00:48 +0200 Subject: [PATCH 39/53] CS Fixes: Not double split with one array argument Keep to use the same CS in all the Symfony code base. Use: ```php $resolver->setDefaults([ 'compound' => false ]); ``` Instead of: ```php $resolver->setDefaults( [ 'compound' => false, ] ); ``` Keep the double split when the method has two or more arguments. I miss a PSR with this rule. --- .../UserPasswordEncoderCommandTest.php | 14 ++--- .../Extension/Core/Type/DateIntervalType.php | 62 +++++++++---------- .../Factory/DefaultChoiceListFactoryTest.php | 10 ++- .../Tests/OptionsResolverTest.php | 38 +++++------- .../Tests/Dumper/YamlFileDumperTest.php | 18 +++--- .../Tests/Constraints/ChoiceValidatorTest.php | 24 +++---- 6 files changed, 74 insertions(+), 92 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php index d53bb45461f78..c8f8013f3f8a0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php @@ -126,14 +126,12 @@ public function testEncodePasswordOutput() public function testEncodePasswordEmptySaltOutput() { - $this->passwordEncoderCommandTester->execute( - [ - 'command' => 'security:encode-password', - 'password' => 'p@ssw0rd', - 'user-class' => 'Symfony\Component\Security\Core\User\User', - '--empty-salt' => true, - ] - ); + $this->passwordEncoderCommandTester->execute([ + 'command' => 'security:encode-password', + 'password' => 'p@ssw0rd', + 'user-class' => 'Symfony\Component\Security\Core\User\User', + '--empty-salt' => true, + ]); $this->assertContains('Password encoding succeeded', $this->passwordEncoderCommandTester->getDisplay()); $this->assertContains(' Encoded password p@ssw0rd', $this->passwordEncoderCommandTester->getDisplay()); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php index 181ce74993d64..2c41b1c6a35e6 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php @@ -205,38 +205,36 @@ public function configureOptions(OptionsResolver $resolver) })); }; - $resolver->setDefaults( - [ - 'with_years' => true, - 'with_months' => true, - 'with_days' => true, - 'with_weeks' => false, - 'with_hours' => false, - 'with_minutes' => false, - 'with_seconds' => false, - 'with_invert' => false, - 'years' => range(0, 100), - 'months' => range(0, 12), - 'weeks' => range(0, 52), - 'days' => range(0, 31), - 'hours' => range(0, 24), - 'minutes' => range(0, 60), - 'seconds' => range(0, 60), - 'widget' => 'choice', - 'input' => 'dateinterval', - 'placeholder' => $placeholderDefault, - 'by_reference' => true, - 'error_bubbling' => false, - // If initialized with a \DateInterval object, FormType initializes - // this option to "\DateInterval". Since the internal, normalized - // representation is not \DateInterval, but an array, we need to unset - // this option. - 'data_class' => null, - 'compound' => $compound, - 'empty_data' => $emptyData, - 'labels' => [], - ] - ); + $resolver->setDefaults([ + 'with_years' => true, + 'with_months' => true, + 'with_days' => true, + 'with_weeks' => false, + 'with_hours' => false, + 'with_minutes' => false, + 'with_seconds' => false, + 'with_invert' => false, + 'years' => range(0, 100), + 'months' => range(0, 12), + 'weeks' => range(0, 52), + 'days' => range(0, 31), + 'hours' => range(0, 24), + 'minutes' => range(0, 60), + 'seconds' => range(0, 60), + 'widget' => 'choice', + 'input' => 'dateinterval', + 'placeholder' => $placeholderDefault, + 'by_reference' => true, + 'error_bubbling' => false, + // If initialized with a \DateInterval object, FormType initializes + // this option to "\DateInterval". Since the internal, normalized + // representation is not \DateInterval, but an array, we need to unset + // this option. + 'data_class' => null, + 'compound' => $compound, + 'empty_data' => $emptyData, + 'labels' => [], + ]); $resolver->setNormalizer('placeholder', $placeholderNormalizer); $resolver->setNormalizer('labels', $labelsNormalizer); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index c520ab1a0de74..5a9884e2951b0 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -144,12 +144,10 @@ function ($object) { return $object->value; } public function testCreateFromChoicesGrouped() { - $list = $this->factory->createListFromChoices( - [ - 'Group 1' => ['A' => $this->obj1, 'B' => $this->obj2], - 'Group 2' => ['C' => $this->obj3, 'D' => $this->obj4], - ] - ); + $list = $this->factory->createListFromChoices([ + 'Group 1' => ['A' => $this->obj1, 'B' => $this->obj2], + 'Group 2' => ['C' => $this->obj3, 'D' => $this->obj4], + ]); $this->assertObjectListWithGeneratedValues($list); } diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php index abf0aa0155d23..d85bbe8fd8485 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php @@ -531,13 +531,11 @@ public function testResolveFailsWithCorrectLevelsButWrongScalar() $this->resolver->setDefined('foo'); $this->resolver->setAllowedTypes('foo', 'int[][]'); - $this->resolver->resolve( - [ - 'foo' => [ - [1.2], - ], - ] - ); + $this->resolver->resolve([ + 'foo' => [ + [1.2], + ], + ]); } /** @@ -1598,13 +1596,11 @@ public function testNestedArrays() 1, 2, ], ], - ], $this->resolver->resolve( - [ - 'foo' => [ - [1, 2], - ], - ] - )); + ], $this->resolver->resolve([ + 'foo' => [ + [1, 2], + ], + ])); } public function testNested2Arrays() @@ -1644,17 +1640,15 @@ public function testNestedArraysException() $this->resolver->setDefined('foo'); $this->resolver->setAllowedTypes('foo', 'float[][][][]'); - $this->resolver->resolve( - [ - 'foo' => [ + $this->resolver->resolve([ + 'foo' => [ + [ [ - [ - [1, 2], - ], + [1, 2], ], ], - ] - ); + ], + ]); } /** diff --git a/src/Symfony/Component/Translation/Tests/Dumper/YamlFileDumperTest.php b/src/Symfony/Component/Translation/Tests/Dumper/YamlFileDumperTest.php index 24bc65ba248fb..e46da5a7e8089 100644 --- a/src/Symfony/Component/Translation/Tests/Dumper/YamlFileDumperTest.php +++ b/src/Symfony/Component/Translation/Tests/Dumper/YamlFileDumperTest.php @@ -20,11 +20,10 @@ class YamlFileDumperTest extends TestCase public function testTreeFormatCatalogue() { $catalogue = new MessageCatalogue('en'); - $catalogue->add( - [ - 'foo.bar1' => 'value1', - 'foo.bar2' => 'value2', - ]); + $catalogue->add([ + 'foo.bar1' => 'value1', + 'foo.bar2' => 'value2', + ]); $dumper = new YamlFileDumper(); @@ -34,11 +33,10 @@ public function testTreeFormatCatalogue() public function testLinearFormatCatalogue() { $catalogue = new MessageCatalogue('en'); - $catalogue->add( - [ - 'foo.bar1' => 'value1', - 'foo.bar2' => 'value2', - ]); + $catalogue->add([ + 'foo.bar1' => 'value1', + 'foo.bar2' => 'value2', + ]); $dumper = new YamlFileDumper(); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php index 1f312cda17042..e83cb8997745a 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php @@ -55,12 +55,10 @@ public function testNullIsValid() { $this->validator->validate( null, - new Choice( - [ - 'choices' => ['foo', 'bar'], - 'strict' => true, - ] - ) + new Choice([ + 'choices' => ['foo', 'bar'], + 'strict' => true, + ]) ); $this->assertNoViolation(); @@ -102,14 +100,12 @@ public function testValidChoiceCallbackFunction() public function testValidChoiceCallbackClosure() { - $constraint = new Choice( - [ - 'strict' => true, - 'callback' => function () { - return ['foo', 'bar']; - }, - ] - ); + $constraint = new Choice([ + 'strict' => true, + 'callback' => function () { + return ['foo', 'bar']; + }, + ]); $this->validator->validate('bar', $constraint); From 326aa86d6a1975543d844295d8b3376bed6e92d4 Mon Sep 17 00:00:00 2001 From: Lynn Date: Wed, 10 Apr 2019 13:41:44 +0200 Subject: [PATCH 40/53] Show more accurate message in profiler when missing stopwatch --- .../Resources/views/Collector/time.html.twig | 6 +++++- src/Symfony/Bundle/WebProfilerBundle/composer.json | 2 +- .../HttpKernel/DataCollector/TimeDataCollector.php | 9 +++++++++ .../Tests/DataCollector/TimeDataCollectorTest.php | 2 ++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig index e9a4a51b403f6..dd95b511b40ea 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig @@ -95,7 +95,11 @@

Execution timeline

- {% if collector.events is empty %} + {% if not collector.isStopwatchInstalled() %} +
+

The Stopwatch component is not installed. If you want to see timing events, run: composer require symfony/stopwatch.

+
+ {% elseif collector.events is empty %}

No timing events have been recorded. Are you sure that debugging is enabled in the kernel?

diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index c1efe11a26bb1..241a5e350b414 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^5.5.9|>=7.0.8", - "symfony/http-kernel": "~3.3|~4.0", + "symfony/http-kernel": "~3.4.25|^4.2.6", "symfony/polyfill-php70": "~1.0", "symfony/routing": "~2.8|~3.0|~4.0", "symfony/twig-bridge": "~2.8|~3.0|~4.0", diff --git a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php index 99149ab0be569..f48db705686b6 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php @@ -47,6 +47,7 @@ public function collect(Request $request, Response $response, \Exception $except 'token' => $response->headers->get('X-Debug-Token'), 'start_time' => $startTime * 1000, 'events' => [], + 'stopwatch_installed' => \class_exists(Stopwatch::class, false), ]; } @@ -139,6 +140,14 @@ public function getStartTime() return $this->data['start_time']; } + /** + * @return bool whether or not the stopwatch component is installed + */ + public function isStopwatchInstalled() + { + return $this->data['stopwatch_installed']; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/TimeDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/TimeDataCollectorTest.php index cf6a86695d776..793fbd319f94d 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/TimeDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/TimeDataCollectorTest.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector; +use Symfony\Component\Stopwatch\Stopwatch; /** * @group time-sensitive @@ -51,5 +52,6 @@ public function testCollect() $c->collect($request, new Response()); $this->assertEquals(123456000, $c->getStartTime()); + $this->assertSame(\class_exists(Stopwatch::class, false), $c->isStopwatchInstalled()); } } From 13e2fb735d92742ab91aa04d7a262f28b3099ca4 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 11 Apr 2019 07:44:34 +0200 Subject: [PATCH 41/53] property normalizer should also pass format and context to isAllowedAttribute --- .../Component/Serializer/Normalizer/PropertyNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php index ece267873f25f..84047e82c6483 100644 --- a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php @@ -102,7 +102,7 @@ protected function extractAttributes($object, $format = null, array $context = [ do { foreach ($reflectionObject->getProperties() as $property) { - if (!$this->isAllowedAttribute($reflectionObject->getName(), $property->name)) { + if (!$this->isAllowedAttribute($reflectionObject->getName(), $property->name, $format, $context)) { continue; } From 4614cea9d275f03cfbefbd538106e67d93e79738 Mon Sep 17 00:00:00 2001 From: Martijn Cuppens Date: Thu, 11 Apr 2019 11:29:09 +0200 Subject: [PATCH 42/53] Optimize SVGs --- .../Resources/views/images/chevron-right.svg | 2 +- .../Resources/views/images/icon-plus-square.svg | 2 +- .../Resources/views/images/symfony-ghost.svg | 2 +- .../Resources/views/images/symfony-logo.svg | 2 +- .../Resources/views/Icon/ajax.svg | 7 +------ .../Resources/views/Icon/cache.svg | 4 +--- .../Resources/views/Icon/close.svg | 6 +----- .../Resources/views/Icon/config.svg | 4 +--- .../Resources/views/Icon/event.svg | 12 +----------- .../Resources/views/Icon/exception.svg | 16 +--------------- .../Resources/views/Icon/form.svg | 7 +------ .../Resources/views/Icon/forward.svg | 5 +---- .../Resources/views/Icon/logger.svg | 8 +------- .../Resources/views/Icon/memory.svg | 6 +----- .../Resources/views/Icon/menu.svg | 4 +--- .../Resources/views/Icon/no.svg | 6 +----- .../Resources/views/Icon/redirect.svg | 11 +---------- .../Resources/views/Icon/request.svg | 17 +---------------- .../Resources/views/Icon/router.svg | 7 +------ .../Resources/views/Icon/search.svg | 8 +------- .../Resources/views/Icon/symfony.svg | 13 +------------ .../Resources/views/Icon/time.svg | 6 +----- .../Resources/views/Icon/translation.svg | 14 +------------- .../Resources/views/Icon/twig.svg | 6 +----- .../Resources/views/Icon/validator.svg | 2 +- .../Resources/views/Icon/yes.svg | 6 +----- .../views/Profiler/toolbar_js.html.twig | 2 +- .../Tests/Resources/IconTest.php | 2 +- .../Component/Debug/ExceptionHandler.php | 2 +- .../HttpKernel/Resources/welcome.html.php | 16 +--------------- .../Component/VarDumper/Dumper/HtmlDumper.php | 8 ++------ 31 files changed, 32 insertions(+), 181 deletions(-) diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/images/chevron-right.svg b/src/Symfony/Bundle/TwigBundle/Resources/views/images/chevron-right.svg index 6f07d1b5b0bbc..6837aff18bd20 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/images/chevron-right.svg +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/images/chevron-right.svg @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/images/icon-plus-square.svg b/src/Symfony/Bundle/TwigBundle/Resources/views/images/icon-plus-square.svg index 8fbf7c4608d90..2f5c3b3583076 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/images/icon-plus-square.svg +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/images/icon-plus-square.svg @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-ghost.svg b/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-ghost.svg index 08234346851b0..58266bcbfaf38 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-ghost.svg +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-ghost.svg @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-logo.svg b/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-logo.svg index 785341864d00b..f10824ae96f6a 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-logo.svg +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-logo.svg @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/ajax.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/ajax.svg index bd878c3c6c21f..4019e3249bb22 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/ajax.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/ajax.svg @@ -1,6 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg index 5b36ae37e0158..798198928a6fb 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg @@ -1,3 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/close.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/close.svg index 4eef3ee624ed2..6038d73f9ab5b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/close.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/close.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg index a407719e8c761..ba51407d12d32 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg @@ -1,3 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/event.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/event.svg index 898117ef94558..76eaa32453453 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/event.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/event.svg @@ -1,11 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg index aafe2e874b30c..0e4df2b23a23c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg @@ -1,15 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg index 84fa75b6c3bf5..e1307960bd568 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg @@ -1,6 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/forward.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/forward.svg index 8b92d448fefd6..28a960a5bf835 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/forward.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/forward.svg @@ -1,4 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg index 5c46bbb2e4957..ae8c5aae4447c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg @@ -1,7 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg index 104ba57aeac79..deb047fc4a0ba 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg index 3c863393bc1ab..afccc7f629bb5 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg @@ -1,3 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/no.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/no.svg index c0bb768d40433..5ffc020f4efe8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/no.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/no.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg index dcdd15c288bd8..8c329d052f2c1 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg @@ -1,10 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/request.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/request.svg index 68b092c6b8809..67d6c643fced5 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/request.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/request.svg @@ -1,16 +1 @@ - - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/router.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/router.svg index 3b7f94355b37d..e16c617ebe18c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/router.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/router.svg @@ -1,6 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/search.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/search.svg index 7c8724aab54c1..cae0a67f9120b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/search.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/search.svg @@ -1,7 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg index 30b1c2e9aad51..c3beff6c8ddfd 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg @@ -1,12 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg index 8ae615d38bc84..d49851d440be4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/translation.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/translation.svg index b7aa1249304b2..735bb92c78b70 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/translation.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/translation.svg @@ -1,13 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg index bfb4e6dea1e09..8c6ad014e4783 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/validator.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/validator.svg index 0b60184f9def5..6a81d92daa786 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/validator.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/validator.svg @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/yes.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/yes.svg index da650231d520d..dbbff93d007d7 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/yes.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/yes.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig index 2dc3eaff9635d..f13edfcfebf52 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig @@ -114,7 +114,7 @@ var sfwdt = document.getElementById('sfwdt{{ token }}'); sfwdt.innerHTML = '\
\ -
\ +
\ An error occurred while loading the web debug toolbar. Open the web profiler.\
\ '; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php index 040d4003f5c54..c3d691d4d3dbf 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php @@ -20,7 +20,7 @@ class IconTest extends TestCase */ public function testIconFileContents($iconFilePath) { - $this->assertRegExp('~.*~s', file_get_contents($iconFilePath), sprintf('The SVG metadata of the %s icon is different than expected (use the same as the other icons).', $iconFilePath)); + $this->assertRegExp('~.*~s', file_get_contents($iconFilePath), sprintf('The SVG metadata of the %s icon is different than expected (use the same as the other icons).', $iconFilePath)); } public function provideIconFilePaths() diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index 6abe6bb7eb685..b79e83ea2c9bf 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -439,6 +439,6 @@ private function escapeHtml($str) private function getSymfonyGhostAsSvg() { - return ''; + return ''; } } diff --git a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php index caac7fd6ebb99..8fdc00506c860 100644 --- a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php +++ b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php @@ -51,21 +51,7 @@