From b4616c810c4d837a707cca9a05b1c2dc1861696c Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Sun, 19 Apr 2020 13:05:28 +0200 Subject: [PATCH 1/7] silently ignore uninitialized properties when mapping data to forms --- .../Core/DataMapper/PropertyPathMapper.php | 24 ++++++++++-- .../DataMapper/PropertyPathMapperTest.php | 37 ++++++++++++++++++- .../Fixtures/TypehintedPropertiesCar.php | 18 +++++++++ 3 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/Form/Tests/Fixtures/TypehintedPropertiesCar.php diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php index 657d9d63be..bc31505157 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Form\Extension\Core\DataMapper; use Symfony\Component\Form\DataMapperInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\PropertyAccess\Exception\AccessException; +use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -46,7 +48,7 @@ class PropertyPathMapper implements DataMapperInterface $config = $form->getConfig(); if (!$empty && null !== $propertyPath && $config->getMapped()) { - $form->setData($this->propertyAccessor->getValue($data, $propertyPath)); + $form->setData($this->getPropertyValue($data, $propertyPath)); } else { $form->setData($config->getData()); } @@ -76,16 +78,32 @@ class PropertyPathMapper implements DataMapperInterface $propertyValue = $form->getData(); // If the field is of type DateTimeInterface and the data is the same skip the update to // keep the original object hash - if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->propertyAccessor->getValue($data, $propertyPath)) { + if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->getPropertyValue($data, $propertyPath)) { continue; } // If the data is identical to the value in $data, we are // dealing with a reference - if (!\is_object($data) || !$config->getByReference() || $propertyValue !== $this->propertyAccessor->getValue($data, $propertyPath)) { + if (!\is_object($data) || !$config->getByReference() || $propertyValue !== $this->getPropertyValue($data, $propertyPath)) { $this->propertyAccessor->setValue($data, $propertyPath, $propertyValue); } } } } + + private function getPropertyValue($data, $propertyPath) + { + try { + return $this->propertyAccessor->getValue($data, $propertyPath); + } catch (AccessException $e) { + if (!$e instanceof UninitializedPropertyException + // For versions without UninitializedPropertyException check the exception message + && (class_exists(UninitializedPropertyException::class) || false === strpos($e->getMessage(), 'You should initialize it')) + ) { + throw $e; + } + + return null; + } + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php index da351295c3..8f274ad929 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php @@ -17,6 +17,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; use Symfony\Component\Form\Form; use Symfony\Component\Form\FormConfigBuilder; +use Symfony\Component\Form\Tests\Fixtures\TypehintedPropertiesCar; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyAccess\PropertyPath; @@ -113,6 +114,23 @@ class PropertyPathMapperTest extends TestCase $this->assertNull($form->getData()); } + /** + * @requires PHP 7.4 + */ + public function testMapDataToFormsIgnoresUninitializedProperties() + { + $engineForm = new Form(new FormConfigBuilder('engine', null, $this->dispatcher)); + $colorForm = new Form(new FormConfigBuilder('color', null, $this->dispatcher)); + + $car = new TypehintedPropertiesCar(); + $car->engine = 'BMW'; + + $this->mapper->mapDataToForms($car, [$engineForm, $colorForm]); + + $this->assertSame($car->engine, $engineForm->getData()); + $this->assertNull($colorForm->getData()); + } + public function testMapDataToFormsSetsDefaultDataIfPassedDataIsNull() { $default = new \stdClass(); @@ -293,13 +311,28 @@ class PropertyPathMapperTest extends TestCase $config->setPropertyPath($propertyPath); $config->setData($engine); $config->setDisabled(true); - $form = new Form($config); + $form = new SubmittedForm($config); $this->mapper->mapFormsToData([$form], $car); $this->assertSame($initialEngine, $car->engine); } + /** + * @requires PHP 7.4 + */ + public function testMapFormsToUninitializedProperties() + { + $car = new TypehintedPropertiesCar(); + $config = new FormConfigBuilder('engine', null, $this->dispatcher); + $config->setData('BMW'); + $form = new SubmittedForm($config); + + $this->mapper->mapFormsToData([$form], $car); + + $this->assertSame('BMW', $car->engine); + } + /** * @dataProvider provideDate */ @@ -339,7 +372,7 @@ class SubmittedForm extends Form } } -class NotSynchronizedForm extends Form +class NotSynchronizedForm extends SubmittedForm { public function isSynchronized() { diff --git a/src/Symfony/Component/Form/Tests/Fixtures/TypehintedPropertiesCar.php b/src/Symfony/Component/Form/Tests/Fixtures/TypehintedPropertiesCar.php new file mode 100644 index 0000000000..6d88c4841a --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Fixtures/TypehintedPropertiesCar.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Fixtures; + +class TypehintedPropertiesCar +{ + public ?string $engine; + public ?string $color; +} From d962202c5f6af1ef2d06a742fc0275094e70dd4c Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 8 Jul 2020 15:48:29 +0200 Subject: [PATCH 2/7] sync translations from master branch --- .../Component/Form/Resources/translations/validators.en.xlf | 4 ++++ .../Component/Form/Resources/translations/validators.fr.xlf | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Symfony/Component/Form/Resources/translations/validators.en.xlf b/src/Symfony/Component/Form/Resources/translations/validators.en.xlf index b8542d319d..89814258d1 100644 --- a/src/Symfony/Component/Form/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Form/Resources/translations/validators.en.xlf @@ -14,6 +14,10 @@ The CSRF token is invalid. Please try to resubmit the form. The CSRF token is invalid. Please try to resubmit the form. + + This value is not a valid HTML5 color. + This value is not a valid HTML5 color. + diff --git a/src/Symfony/Component/Form/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Form/Resources/translations/validators.fr.xlf index 21f9010143..a32c83fc93 100644 --- a/src/Symfony/Component/Form/Resources/translations/validators.fr.xlf +++ b/src/Symfony/Component/Form/Resources/translations/validators.fr.xlf @@ -14,6 +14,10 @@ The CSRF token is invalid. Please try to resubmit the form. Le jeton CSRF est invalide. Veuillez renvoyer le formulaire. + + This value is not a valid HTML5 color. + Cette valeur n'est pas une couleur HTML5 valide. + From 81a4baf4baf606e313fa20c182a4f684159586e5 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 8 Jul 2020 16:52:52 +0200 Subject: [PATCH 3/7] clean up HHVM instructions --- .travis.yml | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3d5253f077..0382b1646f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,7 +79,7 @@ before_install: export COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n' | sort) find ~/.phpenv -name xdebug.ini -delete - if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then + if [[ $TRAVIS_PHP_VERSION = 5.* ]]; then composer () { $HOME/.phpenv/versions/7.1/bin/php $HOME/.phpenv/versions/7.1/bin/composer config platform.php $(echo ' /dev/null || (cd / && wget https://s3.amazonaws.com/travis-php-archives/binaries/ubuntu/14.04/x86_64/php-$PHP.tar.bz2 -O - | tar -xj) - INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini - fi + phpenv global $PHP 2>/dev/null || (cd / && wget https://s3.amazonaws.com/travis-php-archives/binaries/ubuntu/14.04/x86_64/php-$PHP.tar.bz2 -O - | tar -xj) + INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini echo date.timezone = Europe/Paris >> $INI echo memory_limit = -1 >> $INI echo session.gc_probability = 0 >> $INI echo opcache.enable_cli = 1 >> $INI - echo hhvm.jit = 0 >> $INI echo apc.enable_cli = 1 >> $INI if [[ $PHP = 5.* ]]; then echo extension = redis.so >> $INI @@ -195,9 +190,6 @@ before_install: - | # Install extra PHP extensions for PHP in $TRAVIS_PHP_VERSION $php_extra; do - if [[ $PHP = hhvm* ]]; then - continue - fi export PHP=$PHP phpenv global $PHP INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini @@ -304,7 +296,7 @@ install: - | # phpinfo - if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi + php -i - | run_tests () { @@ -319,7 +311,7 @@ install: ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.6.0; composer require --dev --no-update mongodb/mongodb ~1.5.0) fi tfold 'composer update' $COMPOSER_UP - if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then + if [[ $TRAVIS_PHP_VERSION = 5.* ]]; then tfold 'phpunit install' 'composer global remove symfony/flex && ./phpunit install && composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-master' else tfold 'phpunit install' ./phpunit install @@ -332,9 +324,6 @@ install: php .github/rm-invalid-lowest-lock-files.php $COMPONENTS echo "$COMPONENTS" | parallel --gnu "tfold {} 'cd {} && ([ -e composer.lock ] && ${COMPOSER_UP/update/install} || $COMPOSER_UP --prefer-lowest --prefer-stable) && $PHPUNIT_X'" echo "$COMPONENTS" | xargs -n1 -I{} tar --append -f ~/php-ext/composer-lowest.lock.tar {}/composer.lock - elif [[ $PHP = hhvm* ]]; then - rm src/Symfony/Bridge/PhpUnit -Rf - $PHPUNIT --exclude-group no-hhvm,benchmark,intl-data else echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}" tfold src/Symfony/Component/Console.tty $PHPUNIT src/Symfony/Component/Console --group tty From 8ba34dafd335bfc91570274bc4517f4bdb7e46d4 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 8 Jul 2020 17:20:29 +0200 Subject: [PATCH 4/7] ensure compatibility with PHP 8 stack traces --- .../Component/Cache/Adapter/PhpArrayAdapter.php | 13 ++++++++++--- .../Config/Resource/ClassExistenceResource.php | 15 ++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index 6df00d1d33..b4f6a13342 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -275,10 +275,17 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl 'function' => 'spl_autoload_call', 'args' => [$class], ]; - $i = 1 + array_search($autoloadFrame, $trace, true); - if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) { - switch ($trace[$i]['function']) { + if (\PHP_VERSION_ID >= 80000 && isset($trace[1])) { + $callerFrame = $trace[1]; + } elseif (false !== $i = array_search($autoloadFrame, $trace, true)) { + $callerFrame = $trace[++$i]; + } else { + throw $e; + } + + if (isset($callerFrame['function']) && !isset($callerFrame['class'])) { + switch ($callerFrame['function']) { case 'get_class_methods': case 'get_class_vars': case 'get_parent_class': diff --git a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php index fc0259f418..f58d2a6df5 100644 --- a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php +++ b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php @@ -190,12 +190,17 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ 'args' => [$class], ]; - if (false === $i = array_search($autoloadFrame, $trace, true)) { + if (\PHP_VERSION_ID >= 80000 && isset($trace[1])) { + $callerFrame = $trace[1]; + $i = 2; + } elseif (false !== $i = array_search($autoloadFrame, $trace, true)) { + $callerFrame = $trace[++$i]; + } else { throw $e; } - if (isset($trace[++$i]['function']) && !isset($trace[$i]['class'])) { - switch ($trace[$i]['function']) { + if (isset($callerFrame['function']) && !isset($callerFrame['class'])) { + switch ($callerFrame['function']) { case 'get_class_methods': case 'get_class_vars': case 'get_parent_class': @@ -214,8 +219,8 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ } $props = [ - 'file' => isset($trace[$i]['file']) ? $trace[$i]['file'] : null, - 'line' => isset($trace[$i]['line']) ? $trace[$i]['line'] : null, + 'file' => isset($callerFrame['file']) ? $callerFrame['file'] : null, + 'line' => isset($callerFrame['line']) ? $callerFrame['line'] : null, 'trace' => \array_slice($trace, 1 + $i), ]; From 3db0684037693e07cec361dd773efc8b43dbcae3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 8 Jul 2020 19:07:26 +0200 Subject: [PATCH 5/7] Fix PHP 8 deprecations --- .../SecurityBundle/DependencyInjection/SecurityExtension.php | 2 +- .../Component/DependencyInjection/Compiler/AutowirePass.php | 2 +- .../Component/HttpKernel/Controller/ControllerResolver.php | 2 +- src/Symfony/Component/OptionsResolver/OptionsResolver.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index fce4bd9538..ca858f1204 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -520,7 +520,7 @@ class SecurityExtension extends Extension return $this->contextListeners[$contextKey] = $listenerId; } - private function createAuthenticationListeners($container, $id, $firewall, &$authenticationProviders, $defaultProvider = null, array $providerIds, $defaultEntryPoint, $contextListenerId = null) + private function createAuthenticationListeners($container, $id, $firewall, &$authenticationProviders, $defaultProvider, array $providerIds, $defaultEntryPoint, $contextListenerId = null) { $listeners = []; $hasListeners = false; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index d97f121ab9..0b59ecdce9 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -528,7 +528,7 @@ class AutowirePass extends AbstractRecursivePass if (method_exists($parameter, 'getType')) { $type = $parameter->getType(); if ($type && !$type->isBuiltin()) { - $class = new \ReflectionClass(method_exists($type, 'getName') ? $type->getName() : (string) $type); + $class = new \ReflectionClass($type instanceof \ReflectionNamedType ? $type->getName() : (string) $type); } else { $class = null; } diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php index 015448b510..8ed79ff7b2 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php @@ -274,7 +274,7 @@ class ControllerResolver implements ArgumentResolverInterface, ControllerResolve return false; } - $class = new \ReflectionClass(method_exists($type, 'getName') ? $type->getName() : (string) $type); + $class = new \ReflectionClass($type instanceof \ReflectionNamedType ? $type->getName() : (string) $type); return $class && $class->isInstance($request); } diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index 7354caf8a6..cf8c366450 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -1080,6 +1080,6 @@ class OptionsResolver implements Options return null; } - return method_exists($type, 'getName') ? $type->getName() : (string) $type; + return $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type; } } From f41c03b8aacce0e43356cca8f6c2783d32f73710 Mon Sep 17 00:00:00 2001 From: Ahmad El-Bardan Date: Thu, 9 Jul 2020 10:22:35 +0200 Subject: [PATCH 6/7] add german translation of the html5 color validation --- .../Component/Form/Resources/translations/validators.de.xlf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Form/Resources/translations/validators.de.xlf b/src/Symfony/Component/Form/Resources/translations/validators.de.xlf index a9a183197e..fe4353120d 100644 --- a/src/Symfony/Component/Form/Resources/translations/validators.de.xlf +++ b/src/Symfony/Component/Form/Resources/translations/validators.de.xlf @@ -14,6 +14,10 @@ The CSRF token is invalid. Please try to resubmit the form. Der CSRF-Token ist ungültig. Versuchen Sie bitte das Formular erneut zu senden. + + This value is not a valid HTML5 color. + Dieser Wert ist keine gültige HTML5 Farbe. + From 3a4303d30392444bec0487d24eb41fc22a932775 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 9 Jul 2020 10:07:44 +0200 Subject: [PATCH 7/7] cs fix --- src/Symfony/Bridge/PhpUnit/bin/simple-phpunit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index 90efc97658..27ea603711 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -117,7 +117,7 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ 'requires' => array('php' => '*'), ); - if (1 === \count($info['versions'])) { + if (1 === count($info['versions'])) { $passthruOrFail("$COMPOSER create-project --ignore-platform-reqs --no-install --prefer-dist --no-scripts --no-plugins --no-progress -s dev phpunit/phpunit phpunit-$PHPUNIT_VERSION \"$PHPUNIT_VERSION.*\""); } else { $passthruOrFail("$COMPOSER create-project --ignore-platform-reqs --no-install --prefer-dist --no-scripts --no-plugins --no-progress phpunit/phpunit phpunit-$PHPUNIT_VERSION \"$PHPUNIT_VERSION.*\"");