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:
commit
cb16fe7432
@ -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
|
||||
---------
|
||||
|
||||
|
@ -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'];
|
||||
}
|
||||
|
||||
|
@ -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'])) {
|
||||
|
@ -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);
|
||||
|
@ -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],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user