Merge branch '4.4' into 5.0

* 4.4:
  [Dotenv] Documentation improvement
  [DI] Clarified deprecation for TypedReference in 4.4
  [Validator] Add missing vietnamese translations
  add German translation
  add missing Messenger options to XML schema definition
  [Validator][ConstraintValidator] Update wrong PRETTY_DATE doc
  [DomCrawler][Form] Fix PHPDoc on get & offsetGet
  [ErrorHandler] fix parsing static return type on interface method annotation (fix #35836)
  prevent method calls on null values
  Return int if scale = 0
This commit is contained in:
Fabien Potencier 2020-02-29 11:07:09 +01:00
commit 65d06cbd41
19 changed files with 115 additions and 18 deletions

View File

@ -414,6 +414,7 @@
</xsd:sequence> </xsd:sequence>
<xsd:attribute name="default-bus" type="xsd:string" /> <xsd:attribute name="default-bus" type="xsd:string" />
<xsd:attribute name="enabled" type="xsd:boolean" /> <xsd:attribute name="enabled" type="xsd:boolean" />
<xsd:attribute name="failure-transport" type="xsd:string" />
</xsd:complexType> </xsd:complexType>
<xsd:complexType name="messenger_serializer"> <xsd:complexType name="messenger_serializer">
@ -444,12 +445,21 @@
<xsd:complexType name="messenger_transport"> <xsd:complexType name="messenger_transport">
<xsd:sequence> <xsd:sequence>
<xsd:element name="options" type="metadata" minOccurs="0" maxOccurs="unbounded" /> <xsd:element name="options" type="metadata" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="retry-strategy" type="messenger_retry_strategy" minOccurs="0" maxOccurs="1" />
</xsd:sequence> </xsd:sequence>
<xsd:attribute name="name" type="xsd:string" /> <xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="serializer" type="xsd:string" /> <xsd:attribute name="serializer" type="xsd:string" />
<xsd:attribute name="dsn" type="xsd:string" /> <xsd:attribute name="dsn" type="xsd:string" />
</xsd:complexType> </xsd:complexType>
<xsd:complexType name="messenger_retry_strategy">
<xsd:attribute name="service" type="xsd:string" />
<xsd:attribute name="max-retries" type="xsd:integer" />
<xsd:attribute name="delay" type="xsd:integer" />
<xsd:attribute name="multiplier" type="xsd:float" />
<xsd:attribute name="max-delay" type="xsd:float" />
</xsd:complexType>
<xsd:complexType name="messenger_bus"> <xsd:complexType name="messenger_bus">
<xsd:sequence> <xsd:sequence>
<xsd:element name="middleware" type="messenger_middleware" minOccurs="0" maxOccurs="unbounded" /> <xsd:element name="middleware" type="messenger_middleware" minOccurs="0" maxOccurs="unbounded" />

View File

@ -3,6 +3,7 @@
$container->loadFromExtension('framework', [ $container->loadFromExtension('framework', [
'serializer' => true, 'serializer' => true,
'messenger' => [ 'messenger' => [
'failure_transport' => 'failed',
'serializer' => [ 'serializer' => [
'default_serializer' => 'messenger.transport.symfony_serializer', 'default_serializer' => 'messenger.transport.symfony_serializer',
], ],
@ -12,7 +13,14 @@ $container->loadFromExtension('framework', [
'dsn' => 'amqp://localhost/%2f/messages?exchange_name=exchange_name', 'dsn' => 'amqp://localhost/%2f/messages?exchange_name=exchange_name',
'options' => ['queue' => ['name' => 'Queue']], 'options' => ['queue' => ['name' => 'Queue']],
'serializer' => 'messenger.transport.native_php_serializer', 'serializer' => 'messenger.transport.native_php_serializer',
'retry_strategy' => [
'max_retries' => 10,
'delay' => 7,
'multiplier' => 3,
'max_delay' => 100,
],
], ],
'failed' => 'in-memory:///',
'redis' => 'redis://127.0.0.1:6379/messages', 'redis' => 'redis://127.0.0.1:6379/messages',
], ],
], ],

View File

@ -7,7 +7,7 @@
<framework:config> <framework:config>
<framework:serializer enabled="true" /> <framework:serializer enabled="true" />
<framework:messenger> <framework:messenger failure-transport="failed">
<framework:serializer default-serializer="messenger.transport.symfony_serializer" /> <framework:serializer default-serializer="messenger.transport.symfony_serializer" />
<framework:transport name="default" dsn="amqp://localhost/%2f/messages" /> <framework:transport name="default" dsn="amqp://localhost/%2f/messages" />
<framework:transport name="customised" dsn="amqp://localhost/%2f/messages?exchange_name=exchange_name" serializer="messenger.transport.native_php_serializer"> <framework:transport name="customised" dsn="amqp://localhost/%2f/messages?exchange_name=exchange_name" serializer="messenger.transport.native_php_serializer">
@ -16,7 +16,9 @@
<framework:name>Queue</framework:name> <framework:name>Queue</framework:name>
</framework:queue> </framework:queue>
</framework:options> </framework:options>
<framework:retry-strategy max-retries="10" delay="7" multiplier="3" max-delay="100"/>
</framework:transport> </framework:transport>
<framework:transport name="failed" dsn="in-memory:///" />
<framework:transport name="redis" dsn="redis://127.0.0.1:6379/messages" /> <framework:transport name="redis" dsn="redis://127.0.0.1:6379/messages" />
</framework:messenger> </framework:messenger>
</framework:config> </framework:config>

View File

@ -1,6 +1,7 @@
framework: framework:
serializer: true serializer: true
messenger: messenger:
failure_transport: failed
serializer: serializer:
default_serializer: messenger.transport.symfony_serializer default_serializer: messenger.transport.symfony_serializer
transports: transports:
@ -11,4 +12,10 @@ framework:
queue: queue:
name: Queue name: Queue
serializer: 'messenger.transport.native_php_serializer' serializer: 'messenger.transport.native_php_serializer'
retry_strategy:
max_retries: 10
delay: 7
multiplier: 3
max_delay: 100
failed: 'in-memory:///'
redis: 'redis://127.0.0.1:6379/messages' redis: 'redis://127.0.0.1:6379/messages'

View File

@ -627,6 +627,13 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertSame('redis://127.0.0.1:6379/messages', $transportArguments[0]); $this->assertSame('redis://127.0.0.1:6379/messages', $transportArguments[0]);
$this->assertTrue($container->hasDefinition('messenger.transport.redis.factory')); $this->assertTrue($container->hasDefinition('messenger.transport.redis.factory'));
$this->assertSame(10, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(0));
$this->assertSame(7, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(1));
$this->assertSame(3, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(2));
$this->assertSame(100, $container->getDefinition('messenger.retry.multiplier_retry_strategy.customised')->getArgument(3));
$this->assertEquals(new Reference('messenger.transport.failed'), $container->getDefinition('messenger.failure.send_failed_message_to_failure_transport_listener')->getArgument(0));
} }
public function testMessengerRouting() public function testMessengerRouting()

View File

@ -273,7 +273,7 @@ class Form extends Link implements \ArrayAccess
/** /**
* Gets a named field. * Gets a named field.
* *
* @return FormField The field instance * @return FormField|FormField[]|FormField[][] The value of the field
* *
* @throws \InvalidArgumentException When field is not present in this form * @throws \InvalidArgumentException When field is not present in this form
*/ */
@ -317,7 +317,7 @@ class Form extends Link implements \ArrayAccess
* *
* @param string $name The field name * @param string $name The field name
* *
* @return FormField The associated Field instance * @return FormField|FormField[]|FormField[][] The value of the field
* *
* @throws \InvalidArgumentException if the field does not exist * @throws \InvalidArgumentException if the field does not exist
*/ */

View File

@ -66,7 +66,7 @@ class FormFieldRegistry
/** /**
* Returns the value of the field based on the fully qualifed name and its children. * Returns the value of the field based on the fully qualifed name and its children.
* *
* @return mixed The value of the field * @return FormField|FormField[]|FormField[][] The value of the field
* *
* @throws \InvalidArgumentException if the field does not exist * @throws \InvalidArgumentException if the field does not exist
*/ */

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\DomCrawler\Tests; namespace Symfony\Component\DomCrawler\Tests;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\DomCrawler\Field\TextareaFormField;
use Symfony\Component\DomCrawler\Form; use Symfony\Component\DomCrawler\Form;
use Symfony\Component\DomCrawler\FormFieldRegistry; use Symfony\Component\DomCrawler\FormFieldRegistry;
@ -965,7 +966,7 @@ class FormTest extends TestCase
return $dom; return $dom;
} }
public function testgetPhpValuesWithEmptyTextarea() public function testGetPhpValuesWithEmptyTextarea()
{ {
$dom = new \DOMDocument(); $dom = new \DOMDocument();
$dom->loadHTML(' $dom->loadHTML('
@ -980,4 +981,34 @@ class FormTest extends TestCase
$form = new Form($nodes->item(0), 'http://example.com'); $form = new Form($nodes->item(0), 'http://example.com');
$this->assertEquals($form->getPhpValues(), ['example' => '']); $this->assertEquals($form->getPhpValues(), ['example' => '']);
} }
public function testGetReturnTypes()
{
$dom = new \DOMDocument();
$dom->loadHTML('
<html>
<form>
<textarea name="foo[collection][0][bar]">item 0</textarea>
</form>
</html>'
);
$nodes = $dom->getElementsByTagName('form');
$form = new Form($nodes->item(0), 'http://example.com');
// FormField
$this->assertInstanceOf(TextareaFormField::class, $textareaFormField = $form->get('foo[collection][0][bar]'));
// Array of FormField
$this->assertSame([
'bar' => $textareaFormField,
], $form->get('foo[collection][0]'));
// Array of array of FormField
$this->assertSame([
[
'bar' => $textareaFormField,
],
], $form->get('foo[collection]'));
}
} }

View File

@ -2,7 +2,7 @@ Dotenv Component
================ ================
Symfony Dotenv parses `.env` files to make environment variables stored in them Symfony Dotenv parses `.env` files to make environment variables stored in them
accessible via `$_SERVER`, `$_ENV` and optionally `getenv()`. accessible via `$_SERVER` or `$_ENV`.
Resources Resources
--------- ---------

View File

@ -427,17 +427,17 @@ class DebugClassLoader
} }
} }
if ($refl->isInterface() && false !== strpos($doc, 'method') && preg_match_all('#\n \* @method\s+(static\s+)?+(?:[\w\|&\[\]\\\]+\s+)?(\w+(?:\s*\([^\)]*\))?)+(.+?([[:punct:]]\s*)?)?(?=\r?\n \*(?: @|/$|\r?\n))#', $doc, $notice, PREG_SET_ORDER)) { if ($refl->isInterface() && false !== strpos($doc, 'method') && preg_match_all('#\n \* @method\s+(static\s+)?+([\w\|&\[\]\\\]+\s+)?(\w+(?:\s*\([^\)]*\))?)+(.+?([[:punct:]]\s*)?)?(?=\r?\n \*(?: @|/$|\r?\n))#', $doc, $notice, PREG_SET_ORDER)) {
foreach ($notice as $method) { foreach ($notice as $method) {
$static = '' !== $method[1]; $static = '' !== $method[1] && !empty($method[2]);
$name = $method[2]; $name = $method[3];
$description = $method[3] ?? null; $description = $method[4] ?? null;
if (false === strpos($name, '(')) { if (false === strpos($name, '(')) {
$name .= '()'; $name .= '()';
} }
if (null !== $description) { if (null !== $description) {
$description = trim($description); $description = trim($description);
if (!isset($method[4])) { if (!isset($method[5])) {
$description .= '.'; $description .= '.';
} }
} }

View File

@ -325,6 +325,7 @@ class DebugClassLoaderTest extends TestCase
restore_error_handler(); restore_error_handler();
$this->assertSame([ $this->assertSame([
'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::staticReturningMethod()".',
'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::sameLineInterfaceMethodNoBraces()".', 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::sameLineInterfaceMethodNoBraces()".',
'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::newLineInterfaceMethod()": Some description!', 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::newLineInterfaceMethod()": Some description!',
'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::newLineInterfaceMethodNoBraces()": Description.', 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::newLineInterfaceMethodNoBraces()": Description.',

View File

@ -4,6 +4,7 @@ namespace Symfony\Component\ErrorHandler\Tests\Fixtures;
/** /**
* @method string interfaceMethod() * @method string interfaceMethod()
* @method static staticReturningMethod()
* @method sameLineInterfaceMethod($arg) * @method sameLineInterfaceMethod($arg)
* @method sameLineInterfaceMethodNoBraces * @method sameLineInterfaceMethodNoBraces
* *
@ -25,7 +26,7 @@ namespace Symfony\Component\ErrorHandler\Tests\Fixtures;
* *
* Static * Static
* @method static Foo&Bar staticMethod() * @method static Foo&Bar staticMethod()
* @method static staticMethodNoBraces * @method static mixed staticMethodNoBraces
* @method static \stdClass staticMethodTyped(int $arg) Description * @method static \stdClass staticMethodTyped(int $arg) Description
* @method static \stdClass[] staticMethodTypedNoBraces * @method static \stdClass[] staticMethodTypedNoBraces
*/ */

View File

@ -279,7 +279,7 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface
break; break;
} }
$number /= $roundingCoef; $number = 1 === $roundingCoef ? (int) $number : $number / $roundingCoef;
} }
return $number; return $number;

View File

@ -370,7 +370,7 @@ class NumberToLocalizedStringTransformerTest extends TestCase
{ {
$transformer = new NumberToLocalizedStringTransformer($scale, null, $roundingMode); $transformer = new NumberToLocalizedStringTransformer($scale, null, $roundingMode);
$this->assertEquals($output, $transformer->reverseTransform($input)); $this->assertSame($output, $transformer->reverseTransform($input));
} }
public function testReverseTransformDoesNotRoundIfNoScale() public function testReverseTransformDoesNotRoundIfNoScale()

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Serializer\Encoder; namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\BadMethodCallException;
use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerAwareTrait; use Symfony\Component\Serializer\SerializerAwareTrait;
@ -368,7 +369,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
$removeEmptyTags = $this->context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false; $removeEmptyTags = $this->context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false;
$encoderIgnoredNodeTypes = $this->context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES]; $encoderIgnoredNodeTypes = $this->context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES];
if (\is_array($data) || ($data instanceof \Traversable && !$this->serializer->supportsNormalization($data, $this->format))) { if (\is_array($data) || ($data instanceof \Traversable && (null === $this->serializer || !$this->serializer->supportsNormalization($data, $this->format)))) {
foreach ($data as $key => $data) { foreach ($data as $key => $data) {
//Ah this is the magic @ attribute types. //Ah this is the magic @ attribute types.
if (0 === strpos($key, '@') && $this->isElementNameValid($attributeName = substr($key, 1))) { if (0 === strpos($key, '@') && $this->isElementNameValid($attributeName = substr($key, 1))) {
@ -407,6 +408,10 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
} }
if (\is_object($data)) { if (\is_object($data)) {
if (null === $this->serializer) {
throw new BadMethodCallException(sprintf('The serializer needs to be set to allow %s() to be used with object data.', __METHOD__));
}
$data = $this->serializer->normalize($data, $this->format, $this->context); $data = $this->serializer->normalize($data, $this->format, $this->context);
if (null !== $data && !is_scalar($data)) { if (null !== $data && !is_scalar($data)) {
return $this->buildXml($parentNode, $data, $xmlRootNodeName); return $this->buildXml($parentNode, $data, $xmlRootNodeName);
@ -469,6 +474,10 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
} elseif ($val instanceof \Traversable) { } elseif ($val instanceof \Traversable) {
$this->buildXml($node, $val); $this->buildXml($node, $val);
} elseif (\is_object($val)) { } elseif (\is_object($val)) {
if (null === $this->serializer) {
throw new BadMethodCallException(sprintf('The serializer needs to be set to allow %s() to be used with object data.', __METHOD__));
}
return $this->selectNodeType($node, $this->serializer->normalize($val, $this->format, $this->context)); return $this->selectNodeType($node, $this->serializer->normalize($val, $this->format, $this->context));
} elseif (is_numeric($val)) { } elseif (is_numeric($val)) {
return $this->appendText($node, (string) $val); return $this->appendText($node, (string) $val);

View File

@ -68,6 +68,10 @@ class ArrayDenormalizer implements ContextAwareDenormalizerInterface, Serializer
*/ */
public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool
{ {
if (null === $this->serializer) {
throw new BadMethodCallException(sprintf('The serializer needs to be set to allow %s() to be used.', __METHOD__));
}
return '[]' === substr($type, -2) return '[]' === substr($type, -2)
&& $this->serializer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); && $this->serializer->supportsDenormalization($data, substr($type, 0, -2), $format, $context);
} }

View File

@ -21,8 +21,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
abstract class ConstraintValidator implements ConstraintValidatorInterface abstract class ConstraintValidator implements ConstraintValidatorInterface
{ {
/** /**
* Whether to format {@link \DateTime} objects as RFC-3339 dates * Whether to format {@link \DateTime} objects, either with the {@link \IntlDateFormatter}
* ("Y-m-d H:i:s"). * (if it is available) or as RFC-3339 dates ("Y-m-d H:i:s").
*/ */
const PRETTY_DATE = 1; const PRETTY_DATE = 1;
@ -69,7 +69,8 @@ abstract class ConstraintValidator implements ConstraintValidatorInterface
* in double quotes ("). Objects, arrays and resources are formatted as * in double quotes ("). Objects, arrays and resources are formatted as
* "object", "array" and "resource". If the $format bitmask contains * "object", "array" and "resource". If the $format bitmask contains
* the PRETTY_DATE bit, then {@link \DateTime} objects will be formatted * the PRETTY_DATE bit, then {@link \DateTime} objects will be formatted
* as RFC-3339 dates ("Y-m-d H:i:s"). * with the {@link \IntlDateFormatter}. If it is not available, they will be
* formatted as RFC-3339 dates ("Y-m-d H:i:s").
* *
* Be careful when passing message parameters to a constraint violation * Be careful when passing message parameters to a constraint violation
* that (may) contain objects, arrays or resources. These parameters * that (may) contain objects, arrays or resources. These parameters

View File

@ -370,6 +370,10 @@
<source>This value is not a valid hostname.</source> <source>This value is not a valid hostname.</source>
<target>Dieser Wert ist kein gültiger Hostname.</target> <target>Dieser Wert ist kein gültiger Hostname.</target>
</trans-unit> </trans-unit>
<trans-unit id="96">
<source>The number of elements in this collection should be a multiple of {{ compared_value }}.</source>
<target>Die Anzahl an Elementen in dieser Sammlung sollte ein Vielfaches von {{ compared_value }} sein.</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -362,6 +362,18 @@
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source> <source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
<target>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.</target> <target>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.</target>
</trans-unit> </trans-unit>
<trans-unit id="94">
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>Giá trị này nên thuộc giữa {{ min }} và {{ max }}.</target>
</trans-unit>
<trans-unit id="95">
<source>This value is not a valid hostname.</source>
<target>Giá trị này không phải là tên máy chủ hợp lệ.</target>
</trans-unit>
<trans-unit id="96">
<source>The number of elements in this collection should be a multiple of {{ compared_value }}.</source>
<target>Số lượng các phần tử trong bộ sưu tập này nên là bội số của {{compared_value}}.</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>