Merge branch '4.4' into 5.0
* 4.4: Add missing use statements [Translation] Add missing use statement [Translation] Add missing use statement [Config][XmlReferenceDumper] Prevent potential \TypeError [Mailer] Fix broken mandrill http send for recipients with names [Translation] prefer intl domain when adding messages to catalogue Fix CS Fix CS Fail on empty password verification (without warning on any implementation) [Translation][Debug] Add installation and minimal example to README [Validator] try to call __get method if property is uninitialized Show both missing packages in the same error message Fix handling of empty_data's \Closure value in Date/Time form types
This commit is contained in:
commit
85f793bec6
@ -41,12 +41,17 @@ class NotificationEmail extends TemplatedEmail
|
||||
|
||||
public function __construct(Headers $headers = null, AbstractPart $body = null)
|
||||
{
|
||||
$missingPackages = [];
|
||||
if (!class_exists(CssInlinerExtension::class)) {
|
||||
throw new \LogicException(sprintf('You cannot use "%s" if the CSS Inliner Twig extension is not available; try running "composer require twig/cssinliner-extra".', static::class));
|
||||
$missingPackages['twig/cssinliner-extra'] = ' CSS Inliner';
|
||||
}
|
||||
|
||||
if (!class_exists(InkyExtension::class)) {
|
||||
throw new \LogicException(sprintf('You cannot use "%s" if the Inky Twig extension is not available; try running "composer require twig/inky-extra".', static::class));
|
||||
$missingPackages['twig/inky-extra'] = 'Inky';
|
||||
}
|
||||
|
||||
if ($missingPackages) {
|
||||
throw new \LogicException(sprintf('You cannot use "%s" if the %s Twig extension%s not available; try running "composer require %s".', static::class, implode(' and ', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', implode(' ', array_keys($missingPackages))));
|
||||
}
|
||||
|
||||
parent::__construct($headers, $body);
|
||||
|
@ -12,7 +12,6 @@
|
||||
namespace Symfony\Bundle\SecurityBundle\Debug;
|
||||
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\VarDumper\Caster\ClassStub;
|
||||
|
||||
/**
|
||||
* Wraps a security listener for calls record.
|
||||
|
@ -91,7 +91,7 @@ class XmlReferenceDumper
|
||||
}
|
||||
|
||||
if ($prototype instanceof PrototypedArrayNode) {
|
||||
$prototype->setName($key);
|
||||
$prototype->setName($key ?? '');
|
||||
$children = [$key => $prototype];
|
||||
} elseif ($prototype instanceof ArrayNode) {
|
||||
$children = $prototype->getChildren();
|
||||
|
@ -109,7 +109,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'];
|
||||
}
|
||||
|
||||
@ -128,7 +138,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'];
|
||||
}
|
||||
|
||||
|
@ -83,14 +83,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'])) {
|
||||
|
@ -94,7 +94,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'];
|
||||
}
|
||||
|
||||
@ -161,14 +171,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
|
||||
{
|
||||
@ -659,6 +660,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());
|
||||
@ -667,11 +671,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
|
||||
{
|
||||
@ -928,6 +929,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());
|
||||
@ -936,11 +940,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],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ class SessionHandlerFactory
|
||||
case 0 === strpos($connection, 'rediss:'):
|
||||
case 0 === strpos($connection, 'memcached:'):
|
||||
if (!class_exists(AbstractAdapter::class)) {
|
||||
throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection));
|
||||
throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection));
|
||||
}
|
||||
$handlerClass = 0 === strpos($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class;
|
||||
$connection = AbstractAdapter::createConnection($connection, ['lazy' => true]);
|
||||
|
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Mailer\Bridge\Mailchimp\Http;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Mailer\Exception\TransportException;
|
||||
use Symfony\Component\Mailer\SentMessage;
|
||||
use Symfony\Component\Mailer\SmtpEnvelope;
|
||||
use Symfony\Component\Mailer\Transport\Http\AbstractHttpTransport;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* @author Kevin Verschaeve
|
||||
*
|
||||
* @experimental in 4.3
|
||||
*/
|
||||
class MandrillTransport extends AbstractHttpTransport
|
||||
{
|
||||
private const ENDPOINT = 'https://mandrillapp.com/api/1.0/messages/send-raw.json';
|
||||
private $key;
|
||||
|
||||
public function __construct(string $key, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->key = $key;
|
||||
|
||||
parent::__construct($client, $dispatcher, $logger);
|
||||
}
|
||||
|
||||
protected function doSend(SentMessage $message): void
|
||||
{
|
||||
$envelope = $message->getEnvelope();
|
||||
$response = $this->client->request('POST', self::ENDPOINT, [
|
||||
'json' => [
|
||||
'key' => $this->key,
|
||||
'to' => $this->getRecipients($envelope),
|
||||
'from_email' => $envelope->getSender()->getAddress(),
|
||||
'raw_message' => $message->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
$result = $response->toArray(false);
|
||||
if ('error' === ($result['status'] ?? false)) {
|
||||
throw new TransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $result['code']));
|
||||
}
|
||||
|
||||
throw new TransportException(sprintf('Unable to send an email (code %s).', $result['code']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getRecipients(SmtpEnvelope $envelope): array
|
||||
{
|
||||
return array_map(function (Address $recipient): string {
|
||||
return $recipient->getAddress();
|
||||
}, $envelope->getRecipients());
|
||||
}
|
||||
}
|
@ -12,9 +12,11 @@
|
||||
namespace Symfony\Component\Mailer\Bridge\Mailchimp\Transport;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Mailer\Envelope;
|
||||
use Symfony\Component\Mailer\Exception\HttpTransportException;
|
||||
use Symfony\Component\Mailer\SentMessage;
|
||||
use Symfony\Component\Mailer\Transport\AbstractHttpTransport;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
@ -45,7 +47,7 @@ class MandrillHttpTransport extends AbstractHttpTransport
|
||||
$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/1.0/messages/send-raw.json', [
|
||||
'json' => [
|
||||
'key' => $this->key,
|
||||
'to' => $this->stringifyAddresses($envelope->getRecipients()),
|
||||
'to' => $this->getRecipients($envelope->getRecipients()),
|
||||
'from_email' => $envelope->getSender()->toString(),
|
||||
'raw_message' => $message->toString(),
|
||||
],
|
||||
@ -69,4 +71,14 @@ class MandrillHttpTransport extends AbstractHttpTransport
|
||||
{
|
||||
return ($this->host ?: self::HOST).($this->port ? ':'.$this->port : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getRecipients(Envelope $envelope): array
|
||||
{
|
||||
return array_map(function (Address $recipient): string {
|
||||
return $recipient->getAddress();
|
||||
}, $envelope->getRecipients());
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Mime\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\Mime\Message;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
final class EmailAttachmentCount extends Constraint
|
||||
|
@ -12,6 +12,8 @@
|
||||
namespace Symfony\Component\Mime\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\Mime\Message;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
final class EmailHtmlBodyContains extends Constraint
|
||||
{
|
||||
|
@ -12,6 +12,8 @@
|
||||
namespace Symfony\Component\Mime\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\Mime\Message;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
final class EmailTextBodyContains extends Constraint
|
||||
{
|
||||
|
@ -3,6 +3,19 @@ Nexmo Notifier
|
||||
|
||||
Provides Nexmo integration for Symfony Notifier.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
```
|
||||
$ composer install symfony/debug
|
||||
```
|
||||
|
||||
```php
|
||||
use Symfony\Component\Debug\Debug;
|
||||
|
||||
Debug::enable();
|
||||
```
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
|
@ -76,6 +76,10 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
|
||||
*/
|
||||
public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
|
||||
{
|
||||
if ('' === $raw) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
@ -76,6 +76,10 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti
|
||||
*/
|
||||
public function isPasswordValid(string $encoded, string $raw, ?string $salt): bool
|
||||
{
|
||||
if ('' === $raw) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ class NativePasswordEncoderTest extends TestCase
|
||||
$result = $encoder->encodePassword('password', null);
|
||||
$this->assertTrue($encoder->isPasswordValid($result, 'password', null));
|
||||
$this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null));
|
||||
$this->assertFalse($encoder->isPasswordValid($result, '', null));
|
||||
}
|
||||
|
||||
public function testNonArgonValidation()
|
||||
|
@ -29,6 +29,7 @@ class SodiumPasswordEncoderTest extends TestCase
|
||||
$result = $encoder->encodePassword('password', null);
|
||||
$this->assertTrue($encoder->isPasswordValid($result, 'password', null));
|
||||
$this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null));
|
||||
$this->assertFalse($encoder->isPasswordValid($result, '', null));
|
||||
}
|
||||
|
||||
public function testBCryptValidation()
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Security\Http\Firewall\AbstractListener;
|
||||
use Symfony\Component\Security\Http\Firewall\AccessListener;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
|
@ -18,6 +18,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Translation\Util\XliffUtils;
|
||||
|
||||
/**
|
||||
|
@ -154,9 +154,17 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
public function add(array $messages, string $domain = 'messages')
|
||||
{
|
||||
if (!isset($this->messages[$domain])) {
|
||||
$this->messages[$domain] = $messages;
|
||||
} else {
|
||||
foreach ($messages as $id => $message) {
|
||||
$this->messages[$domain] = [];
|
||||
}
|
||||
$intlDomain = $domain;
|
||||
$suffixLength = \strlen(self::INTL_DOMAIN_SUFFIX);
|
||||
if (\strlen($domain) > $suffixLength && false !== strpos($domain, self::INTL_DOMAIN_SUFFIX, -$suffixLength)) {
|
||||
$intlDomain .= self::INTL_DOMAIN_SUFFIX;
|
||||
}
|
||||
foreach ($messages as $id => $message) {
|
||||
if (isset($this->messages[$intlDomain]) && \array_key_exists($id, $this->messages[$intlDomain])) {
|
||||
$this->messages[$intlDomain][$id] = $message;
|
||||
} else {
|
||||
$this->messages[$domain][$id] = $message;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -67,6 +67,19 @@ class TargetOperationTest extends AbstractOperationTest
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetResultWithMixedDomains()
|
||||
{
|
||||
$this->assertEquals(
|
||||
new MessageCatalogue('en', [
|
||||
'messages+intl-icu' => ['a' => 'old_a'],
|
||||
]),
|
||||
$this->createOperation(
|
||||
new MessageCatalogue('en', ['messages' => ['a' => 'old_a']]),
|
||||
new MessageCatalogue('en', ['messages+intl-icu' => ['a' => 'new_a']])
|
||||
)->getResult()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetResultWithMetadata()
|
||||
{
|
||||
$leftCatalogue = new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b']]);
|
||||
|
@ -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