Merge branch '3.4' into 4.4

* 3.4:
  [Translation] Add missing use statement
  [Translation][Debug] Add installation and minimal example to README
  [Validator] try to call __get method if property is uninitialized
  Fix handling of empty_data's \Closure value in Date/Time form types
This commit is contained in:
Fabien Potencier 2020-02-04 08:40:16 +01:00
commit cb16fe7432
11 changed files with 163 additions and 16 deletions

View File

@ -8,6 +8,19 @@ Debug Component
The Debug component provides tools to ease debugging PHP code.
Getting Started
---------------
```
$ composer install symfony/debug
```
```php
use Symfony\Component\Debug\Debug;
Debug::enable();
```
Resources
---------

View File

@ -108,7 +108,17 @@ class DateTimeType extends AbstractType
'invalid_message_parameters',
]));
if (isset($emptyData['date'])) {
if ($emptyData instanceof \Closure) {
$lazyEmptyData = static function ($option) use ($emptyData) {
return static function (FormInterface $form) use ($emptyData, $option) {
$emptyData = $emptyData($form->getParent());
return isset($emptyData[$option]) ? $emptyData[$option] : '';
};
};
$dateOptions['empty_data'] = $lazyEmptyData('date');
} elseif (isset($emptyData['date'])) {
$dateOptions['empty_data'] = $emptyData['date'];
}
@ -127,7 +137,9 @@ class DateTimeType extends AbstractType
'invalid_message_parameters',
]));
if (isset($emptyData['time'])) {
if ($emptyData instanceof \Closure) {
$timeOptions['empty_data'] = $lazyEmptyData('time');
} elseif (isset($emptyData['time'])) {
$timeOptions['empty_data'] = $emptyData['time'];
}

View File

@ -82,14 +82,28 @@ class DateType extends AbstractType
// so we need to handle the cascade setting here
$emptyData = $builder->getEmptyData() ?: [];
if (isset($emptyData['year'])) {
$yearOptions['empty_data'] = $emptyData['year'];
}
if (isset($emptyData['month'])) {
$monthOptions['empty_data'] = $emptyData['month'];
}
if (isset($emptyData['day'])) {
$dayOptions['empty_data'] = $emptyData['day'];
if ($emptyData instanceof \Closure) {
$lazyEmptyData = static function ($option) use ($emptyData) {
return static function (FormInterface $form) use ($emptyData, $option) {
$emptyData = $emptyData($form->getParent());
return isset($emptyData[$option]) ? $emptyData[$option] : '';
};
};
$yearOptions['empty_data'] = $lazyEmptyData('year');
$monthOptions['empty_data'] = $lazyEmptyData('month');
$dayOptions['empty_data'] = $lazyEmptyData('day');
} else {
if (isset($emptyData['year'])) {
$yearOptions['empty_data'] = $emptyData['year'];
}
if (isset($emptyData['month'])) {
$monthOptions['empty_data'] = $emptyData['month'];
}
if (isset($emptyData['day'])) {
$dayOptions['empty_data'] = $emptyData['day'];
}
}
if (isset($options['invalid_message'])) {

View File

@ -93,7 +93,17 @@ class TimeType extends AbstractType
// so we need to handle the cascade setting here
$emptyData = $builder->getEmptyData() ?: [];
if (isset($emptyData['hour'])) {
if ($emptyData instanceof \Closure) {
$lazyEmptyData = static function ($option) use ($emptyData) {
return static function (FormInterface $form) use ($emptyData, $option) {
$emptyData = $emptyData($form->getParent());
return isset($emptyData[$option]) ? $emptyData[$option] : '';
};
};
$hourOptions['empty_data'] = $lazyEmptyData('hour');
} elseif (isset($emptyData['hour'])) {
$hourOptions['empty_data'] = $emptyData['hour'];
}
@ -160,14 +170,18 @@ class TimeType extends AbstractType
$builder->add('hour', self::$widgets[$options['widget']], $hourOptions);
if ($options['with_minutes']) {
if (isset($emptyData['minute'])) {
if ($emptyData instanceof \Closure) {
$minuteOptions['empty_data'] = $lazyEmptyData('minute');
} elseif (isset($emptyData['minute'])) {
$minuteOptions['empty_data'] = $emptyData['minute'];
}
$builder->add('minute', self::$widgets[$options['widget']], $minuteOptions);
}
if ($options['with_seconds']) {
if (isset($emptyData['second'])) {
if ($emptyData instanceof \Closure) {
$secondOptions['empty_data'] = $lazyEmptyData('second');
} elseif (isset($emptyData['second'])) {
$secondOptions['empty_data'] = $emptyData['second'];
}
$builder->add('second', self::$widgets[$options['widget']], $secondOptions);

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
class DateTimeTypeTest extends BaseTypeTest
{
@ -671,6 +672,9 @@ class DateTimeTypeTest extends BaseTypeTest
]);
$form->submit(null);
if ($emptyData instanceof \Closure) {
$emptyData = $emptyData($form);
}
$this->assertSame($emptyData, $form->getViewData());
$this->assertEquals($expectedData, $form->getNormData());
$this->assertEquals($expectedData, $form->getData());
@ -679,11 +683,17 @@ class DateTimeTypeTest extends BaseTypeTest
public function provideEmptyData()
{
$expectedData = \DateTime::createFromFormat('Y-m-d H:i', '2018-11-11 21:23');
$lazyEmptyData = static function (FormInterface $form) {
return $form->getConfig()->getCompound() ? ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']] : '2018-11-11T21:23:00';
};
return [
'Simple field' => ['single_text', '2018-11-11T21:23:00', $expectedData],
'Compound text field' => ['text', ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']], $expectedData],
'Compound choice field' => ['choice', ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']], $expectedData],
'Simple field lazy' => ['single_text', $lazyEmptyData, $expectedData],
'Compound text field lazy' => ['text', $lazyEmptyData, $expectedData],
'Compound choice field lazy' => ['choice', $lazyEmptyData, $expectedData],
];
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Intl\Util\IntlTestHelper;
class DateTypeTest extends BaseTypeTest
@ -1022,6 +1023,9 @@ class DateTypeTest extends BaseTypeTest
]);
$form->submit(null);
if ($emptyData instanceof \Closure) {
$emptyData = $emptyData($form);
}
$this->assertSame($emptyData, $form->getViewData());
$this->assertEquals($expectedData, $form->getNormData());
$this->assertEquals($expectedData, $form->getData());
@ -1030,11 +1034,17 @@ class DateTypeTest extends BaseTypeTest
public function provideEmptyData()
{
$expectedData = \DateTime::createFromFormat('Y-m-d H:i:s', '2018-11-11 00:00:00');
$lazyEmptyData = static function (FormInterface $form) {
return $form->getConfig()->getCompound() ? ['year' => '2018', 'month' => '11', 'day' => '11'] : '2018-11-11';
};
return [
'Simple field' => ['single_text', '2018-11-11', $expectedData],
'Compound text fields' => ['text', ['year' => '2018', 'month' => '11', 'day' => '11'], $expectedData],
'Compound choice fields' => ['choice', ['year' => '2018', 'month' => '11', 'day' => '11'], $expectedData],
'Simple field lazy' => ['single_text', $lazyEmptyData, $expectedData],
'Compound text fields lazy' => ['text', $lazyEmptyData, $expectedData],
'Compound choice fields lazy' => ['choice', $lazyEmptyData, $expectedData],
];
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
class TimeTypeTest extends BaseTypeTest
{
@ -929,6 +930,9 @@ class TimeTypeTest extends BaseTypeTest
]);
$form->submit(null);
if ($emptyData instanceof \Closure) {
$emptyData = $emptyData($form);
}
$this->assertSame($emptyData, $form->getViewData());
$this->assertEquals($expectedData, $form->getNormData());
$this->assertEquals($expectedData, $form->getData());
@ -937,11 +941,17 @@ class TimeTypeTest extends BaseTypeTest
public function provideEmptyData()
{
$expectedData = \DateTime::createFromFormat('Y-m-d H:i', '1970-01-01 21:23');
$lazyEmptyData = static function (FormInterface $form) {
return $form->getConfig()->getCompound() ? ['hour' => '21', 'minute' => '23'] : '21:23';
};
return [
'Simple field' => ['single_text', '21:23', $expectedData],
'Compound text field' => ['text', ['hour' => '21', 'minute' => '23'], $expectedData],
'Compound choice field' => ['choice', ['hour' => '21', 'minute' => '23'], $expectedData],
'Simple field lazy' => ['single_text', $lazyEmptyData, $expectedData],
'Compound text field lazy' => ['text', $lazyEmptyData, $expectedData],
'Compound choice field lazy' => ['choice', $lazyEmptyData, $expectedData],
];
}
}

View File

@ -3,10 +3,28 @@ Translation Component
The Translation component provides tools to internationalize your application.
Getting Started
---------------
```
$ composer require symfony/translation
```
```php
use Symfony\Component\Translation\Translator;
$translator = new Translator('fr_FR');
$translator->addResource('array', [
'Hello World!' => 'Bonjour !',
], 'fr_FR');
echo $translator->trans('Hello World!'); // outputs « Bonjour ! »
```
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/translation.html)
* [Documentation](https://symfony.com/doc/current/translation.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)

View File

@ -50,8 +50,21 @@ class PropertyMetadata extends MemberMetadata
{
$reflProperty = $this->getReflectionMember($object);
if (\PHP_VERSION_ID >= 70400 && !$reflProperty->isInitialized($object)) {
return null;
if (\PHP_VERSION_ID >= 70400 && $reflProperty->hasType() && !$reflProperty->isInitialized($object)) {
// There is no way to check if a property has been unset or if it is uninitialized.
// When trying to access an uninitialized property, __get method is triggered.
// If __get method is not present, no fallback is possible
// Otherwise we need to catch an Error in case we are trying to access an uninitialized but set property.
if (!method_exists($object, '__get')) {
return null;
}
try {
return $reflProperty->getValue($object);
} catch (\Error $e) {
return null;
}
}
return $reflProperty->getValue($object);

View File

@ -0,0 +1,18 @@
<?php
namespace Symfony\Component\Validator\Tests\Fixtures;
class Entity_74_Proxy extends Entity_74
{
public string $notUnset;
public function __construct()
{
unset($this->uninitialized);
}
public function __get($name)
{
return 42;
}
}

View File

@ -15,11 +15,13 @@ use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Mapping\PropertyMetadata;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Entity_74;
use Symfony\Component\Validator\Tests\Fixtures\Entity_74_Proxy;
class PropertyMetadataTest extends TestCase
{
const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
const CLASSNAME_74 = 'Symfony\Component\Validator\Tests\Fixtures\Entity_74';
const CLASSNAME_74_PROXY = 'Symfony\Component\Validator\Tests\Fixtures\Entity_74_Proxy';
const PARENTCLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent';
public function testInvalidPropertyName()
@ -66,4 +68,17 @@ class PropertyMetadataTest extends TestCase
$this->assertNull($metadata->getPropertyValue($entity));
}
/**
* @requires PHP 7.4
*/
public function testGetPropertyValueFromUninitializedPropertyShouldNotReturnNullIfMagicGetIsPresent()
{
$entity = new Entity_74_Proxy();
$metadata = new PropertyMetadata(self::CLASSNAME_74_PROXY, 'uninitialized');
$notUnsetMetadata = new PropertyMetadata(self::CLASSNAME_74_PROXY, 'notUnset');
$this->assertNull($notUnsetMetadata->getPropertyValue($entity));
$this->assertEquals(42, $metadata->getPropertyValue($entity));
}
}