Merge branch '5.0' into 5.1

* 5.0:
  fix merge
  Require PHPUnit 9.3 on PHP 8
  [Cache] fix catching auth errors
  Fix CS
  [FrameworkBundle] set default session.handler alias if handler_id is not provided
  Fix CS
  Readability update
  Fix checks for phpunit releases on Composer 2 (resolves #37601)
  [Serializer] Support multiple levels of discriminator mapping
  Use hexadecimal numerals instead of hexadecimals in strings to represent error codes.
  [SCA] Minor fixes on tests
  [WebProfilerBundle] modified url generation to use absolute urls
  [Mailer] Fix reply-to functionality in the SendgridApiTransport
  [Mime] Fix compat with HTTP requests
  ticket_36879 - Fix mandrill raw http request setting from email/name
This commit is contained in:
Nicolas Grekas 2020-07-23 10:36:24 +02:00
commit 1b7714a05c
28 changed files with 170 additions and 78 deletions

View File

@ -93,7 +93,10 @@ $passthruOrFail = function ($command) {
}
};
if (PHP_VERSION_ID >= 70200) {
if (PHP_VERSION_ID >= 80000) {
// PHP 8 requires PHPUnit 9.3+
$PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '9.3');
} elseif (PHP_VERSION_ID >= 70200) {
// PHPUnit 8 requires PHP 7.2+
$PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '8.3');
} elseif (PHP_VERSION_ID >= 70100) {
@ -194,7 +197,11 @@ if (!file_exists("$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit") || $configurationH
'requires' => ['php' => '*'],
];
if (1 === count($info['versions'])) {
$stableVersions = array_filter($info['versions'], function($v) {
return !preg_match('/-dev$|^dev-/', $v);
});
if (!$stableVersions) {
$passthruOrFail("$COMPOSER create-project --ignore-platform-reqs --no-install --prefer-dist --no-scripts --no-plugins --no-progress -s dev phpunit/phpunit $PHPUNIT_VERSION_DIR \"$PHPUNIT_VERSION.*\"");
} else {
$passthruOrFail("$COMPOSER create-project --ignore-platform-reqs --no-install --prefer-dist --no-scripts --no-plugins --no-progress phpunit/phpunit $PHPUNIT_VERSION_DIR \"$PHPUNIT_VERSION.*\"");

View File

@ -956,6 +956,7 @@ class FrameworkExtension extends Extension
// Set the handler class to be null
$container->getDefinition('session.storage.native')->replaceArgument(1, null);
$container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null);
$container->setAlias('session.handler', 'session.handler.native_file')->setPrivate(true);
} else {
$container->resolveEnvPlaceholders($config['handler_id'], null, $usedEnvs);

View File

@ -503,6 +503,7 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml');
$this->assertNull($container->getDefinition('session.storage.native')->getArgument(1));
$this->assertNull($container->getDefinition('session.storage.php_bridge')->getArgument(0));
$this->assertSame('session.handler.native_file', (string) $container->getAlias('session.handler'));
$expected = ['session', 'initialized_session', 'logger'];
$this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues()));

View File

@ -50,7 +50,7 @@ class UserPasswordEncoderCommandTest extends AbstractWebTestCase
], ['interactive' => false]);
$this->assertStringContainsString('[ERROR] The password must not be empty.', $this->passwordEncoderCommandTester->getDisplay());
$this->assertEquals($statusCode, 1);
$this->assertEquals(1, $statusCode);
}
public function testEncodePasswordBcrypt()

View File

@ -123,8 +123,15 @@ class ContentSecurityPolicyHandler
$headers = $this->getCspHeaders($response);
$types = [
'script-src' => 'csp_script_nonce',
'script-src-elem' => 'csp_script_nonce',
'style-src' => 'csp_style_nonce',
'style-src-elem' => 'csp_style_nonce',
];
foreach ($headers as $header => $directives) {
foreach (['script-src' => 'csp_script_nonce', 'script-src-elem' => 'csp_script_nonce', 'style-src' => 'csp_style_nonce', 'style-src-elem' => 'csp_style_nonce'] as $type => $tokenName) {
foreach ($types as $type => $tokenName) {
if ($this->authorizesInline($directives, $type)) {
continue;
}

View File

@ -422,7 +422,7 @@
newToken = (newToken || token);
this.load(
'sfwdt' + token,
'{{ path("_wdt", { "token": "xxxxxx" })|escape('js') }}'.replace(/xxxxxx/, newToken),
'{{ url("_wdt", { "token": "xxxxxx" })|escape('js') }}'.replace(/xxxxxx/, newToken),
function(xhr, el) {
/* Evaluate in global scope scripts embedded inside the toolbar */
@ -531,7 +531,7 @@
sfwdt.innerHTML = '\
<div class="sf-toolbarreset">\
<div class="sf-toolbar-icon"><svg width="26" height="28" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 0 26 28" enable-background="new 0 0 26 28" xml:space="preserve"><path fill="#FFFFFF" d="M13 0C5.8 0 0 5.8 0 13c0 7.2 5.8 13 13 13c7.2 0 13-5.8 13-13C26 5.8 20.2 0 13 0z M20 7.5 c-0.6 0-1-0.3-1-0.9c0-0.2 0-0.4 0.2-0.6c0.1-0.3 0.2-0.3 0.2-0.4c0-0.3-0.5-0.4-0.7-0.4c-2 0.1-2.5 2.7-2.9 4.8l-0.2 1.1 c1.1 0.2 1.9 0 2.4-0.3c0.6-0.4-0.2-0.8-0.1-1.3C18 9.2 18.4 9 18.7 8.9c0.5 0 0.8 0.5 0.8 1c0 0.8-1.1 2-3.3 1.9 c-0.3 0-0.5 0-0.7-0.1L15 14.1c-0.4 1.7-0.9 4.1-2.6 6.2c-1.5 1.8-3.1 2.1-3.8 2.1c-1.3 0-2.1-0.6-2.2-1.6c0-0.9 0.8-1.4 1.3-1.4 c0.7 0 1.2 0.5 1.2 1.1c0 0.5-0.2 0.6-0.4 0.7c-0.1 0.1-0.3 0.2-0.3 0.4c0 0.1 0.1 0.3 0.4 0.3c0.5 0 0.9-0.3 1.2-0.5 c1.3-1 1.7-2.9 2.4-6.2l0.1-0.8c0.2-1.1 0.5-2.3 0.8-3.5c-0.9-0.7-1.4-1.5-2.6-1.8c-0.8-0.2-1.3 0-1.7 0.4C8.4 10 8.6 10.7 9 11.1 l0.7 0.7c0.8 0.9 1.3 1.7 1.1 2.7c-0.3 1.6-2.1 2.8-4.3 2.1c-1.9-0.6-2.2-1.9-2-2.7c0.2-0.6 0.7-0.8 1.2-0.6 c0.5 0.2 0.7 0.8 0.6 1.3c0 0.1 0 0.1-0.1 0.3C6 15 5.9 15.2 5.9 15.3c-0.1 0.4 0.4 0.7 0.8 0.8c0.8 0.3 1.7-0.2 1.9-0.9 c0.2-0.6-0.2-1.1-0.4-1.2l-0.8-0.9c-0.4-0.4-1.2-1.5-0.8-2.8c0.2-0.5 0.5-1 0.9-1.4c1-0.7 2-0.8 3-0.6c1.3 0.4 1.9 1.2 2.8 1.9 c0.5-1.3 1.1-2.6 2-3.8c0.9-1 2-1.7 3.3-1.8C20 4.8 21 5.4 21 6.3C21 6.7 20.8 7.5 20 7.5z"/></svg></div>\
An error occurred while loading the web debug toolbar. <a href="{{ path("_profiler_home")|escape('js') }}' + newToken + '>Open the web profiler.</a>\
An error occurred while loading the web debug toolbar. <a href="{{ url("_profiler_home")|escape('js') }}' + newToken + '>Open the web profiler.</a>\
</div>\
';
sfwdt.setAttribute('class', 'sf-toolbar sf-error-toolbar');

View File

@ -173,30 +173,30 @@ trait RedisTrait
$initializer = function ($redis) use ($connect, $params, $dsn, $auth, $hosts) {
try {
@$redis->{$connect}($hosts[0]['host'] ?? $hosts[0]['path'], $hosts[0]['port'] ?? null, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval']);
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$isConnected = $redis->isConnected();
restore_error_handler();
if (!$isConnected) {
$error = preg_match('/^Redis::p?connect\(\): (.*)/', $error, $error) ? sprintf(' (%s)', $error[1]) : '';
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$error.'.');
}
if ((null !== $auth && !$redis->auth($auth))
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
|| ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
) {
$e = preg_replace('/^ERR /', '', $redis->getLastError());
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e.'.');
}
if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
$redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
}
} catch (\RedisException $e) {
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
}
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$isConnected = $redis->isConnected();
restore_error_handler();
if (!$isConnected) {
$error = preg_match('/^Redis::p?connect\(\): (.*)/', $error, $error) ? sprintf(' (%s)', $error[1]) : '';
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$error.'.');
}
if ((null !== $auth && !$redis->auth($auth))
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
|| ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
) {
$e = preg_replace('/^ERR /', '', $redis->getLastError());
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e.'.');
}
if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
$redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
}
return true;
};

View File

@ -902,12 +902,12 @@ class ContainerBuilderTest extends TestCase
->addTag('bar', ['bar' => 'bar'])
->addTag('foo', ['foofoo' => 'foofoo'])
;
$this->assertEquals($builder->findTaggedServiceIds('foo'), [
$this->assertEquals([
'foo' => [
['foo' => 'foo'],
['foofoo' => 'foofoo'],
],
], '->findTaggedServiceIds() returns an array of service ids and its tag attributes');
], $builder->findTaggedServiceIds('foo'), '->findTaggedServiceIds() returns an array of service ids and its tag attributes');
$this->assertEquals([], $builder->findTaggedServiceIds('foobar'), '->findTaggedServiceIds() returns an empty array if there is annotated services');
}

View File

@ -266,10 +266,10 @@ class DefinitionTest extends TestCase
$def->addTag('foo', ['foo' => 'bar']);
$this->assertEquals([[], ['foo' => 'bar']], $def->getTag('foo'), '->addTag() can adds the same tag several times');
$def->addTag('bar', ['bar' => 'bar']);
$this->assertEquals($def->getTags(), [
$this->assertEquals([
'foo' => [[], ['foo' => 'bar']],
'bar' => [['bar' => 'bar']],
], '->getTags() returns all tags');
], $def->getTags(), '->getTags() returns all tags');
}
public function testSetArgument()

View File

@ -170,25 +170,28 @@ class FormTest extends TestCase
');
$this->assertEquals(
array_keys($form->all()),
['foo[2]', 'foo[3]', 'bar[foo][0]', 'bar[foo][foobar]']
['foo[2]', 'foo[3]', 'bar[foo][0]', 'bar[foo][foobar]'],
array_keys($form->all())
);
$this->assertEquals($form->get('foo[2]')->getValue(), 'foo');
$this->assertEquals($form->get('foo[3]')->getValue(), 'foo');
$this->assertEquals($form->get('bar[foo][0]')->getValue(), 'foo');
$this->assertEquals($form->get('bar[foo][foobar]')->getValue(), 'foo');
$this->assertEquals('foo', $form->get('foo[2]')->getValue());
$this->assertEquals('foo', $form->get('foo[3]')->getValue());
$this->assertEquals('foo', $form->get('bar[foo][0]')->getValue());
$this->assertEquals('foo', $form->get('bar[foo][foobar]')->getValue());
$form['foo[2]'] = 'bar';
$form['foo[3]'] = 'bar';
$this->assertEquals($form->get('foo[2]')->getValue(), 'bar');
$this->assertEquals($form->get('foo[3]')->getValue(), 'bar');
$this->assertEquals('bar', $form->get('foo[2]')->getValue());
$this->assertEquals('bar', $form->get('foo[3]')->getValue());
$form['bar'] = ['foo' => ['0' => 'bar', 'foobar' => 'foobar']];
$this->assertEquals($form->get('bar[foo][0]')->getValue(), 'bar');
$this->assertEquals($form->get('bar[foo][foobar]')->getValue(), 'foobar');
$this->assertEquals('bar', $form->get('bar[foo][0]')->getValue());
$this->assertEquals(
'foobar',
$form->get('bar[foo][foobar]')->getValue()
);
}
/**
@ -979,7 +982,7 @@ class FormTest extends TestCase
$nodes = $dom->getElementsByTagName('form');
$form = new Form($nodes->item(0), 'http://example.com');
$this->assertEquals($form->getPhpValues(), ['example' => '']);
$this->assertEquals(['example' => ''], $form->getPhpValues());
}
public function testGetReturnTypes()

View File

@ -161,9 +161,9 @@ class FlattenExceptionTest extends TestCase
$flattened = FlattenException::createFromThrowable($exception)->getPrevious();
$this->assertEquals($flattened->getMessage(), 'Oh noes!', 'The message is copied from the original exception.');
$this->assertEquals($flattened->getCode(), 42, 'The code is copied from the original exception.');
$this->assertEquals($flattened->getClass(), 'ParseError', 'The class is set to the class of the original exception');
$this->assertEquals('Oh noes!', $flattened->getMessage(), 'The message is copied from the original exception.');
$this->assertEquals(42, $flattened->getCode(), 'The code is copied from the original exception.');
$this->assertEquals('ParseError', $flattened->getClass(), 'The class is set to the class of the original exception');
}
/**
@ -289,7 +289,7 @@ class FlattenExceptionTest extends TestCase
$this->assertSame(['resource', 'stream'], $array[$i++]);
$args = $array[$i++];
$this->assertSame($args[0], 'object');
$this->assertSame('object', $args[0]);
$this->assertTrue('Closure' === $args[1] || is_subclass_of($args[1], '\Closure'), 'Expect object class name to be Closure or a subclass of Closure.');
$this->assertSame(['array', [['integer', 1], ['integer', 2]]], $array[$i++]);
@ -304,7 +304,7 @@ class FlattenExceptionTest extends TestCase
$this->assertSame(['float', INF], $array[$i++]);
// assertEquals() does not like NAN values.
$this->assertEquals($array[$i][0], 'float');
$this->assertEquals('float', $array[$i][0]);
$this->assertNan($array[$i][1]);
}
@ -344,7 +344,7 @@ class FlattenExceptionTest extends TestCase
$flattened = FlattenException::createFromThrowable($exception);
$trace = $flattened->getTrace();
$this->assertSame($trace[1]['args'][0], ['array', ['array', '*SKIPPED over 10000 entries*']]);
$this->assertSame(['array', ['array', '*SKIPPED over 10000 entries*']], $trace[1]['args'][0]);
$serializeTrace = serialize($trace);

View File

@ -620,20 +620,20 @@ class ResponseTest extends ResponseTestCase
$options = ['etag' => '"whatever"'];
$response->setCache($options);
$this->assertEquals($response->getEtag(), '"whatever"');
$this->assertEquals('"whatever"', $response->getEtag());
$now = $this->createDateTimeNow();
$options = ['last_modified' => $now];
$response->setCache($options);
$this->assertEquals($response->getLastModified()->getTimestamp(), $now->getTimestamp());
$this->assertEquals($now->getTimestamp(), $response->getLastModified()->getTimestamp());
$options = ['max_age' => 100];
$response->setCache($options);
$this->assertEquals($response->getMaxAge(), 100);
$this->assertEquals(100, $response->getMaxAge());
$options = ['s_maxage' => 200];
$response->setCache($options);
$this->assertEquals($response->getMaxAge(), 200);
$this->assertEquals(200, $response->getMaxAge());
$this->assertTrue($response->headers->hasCacheControlDirective('public'));
$this->assertFalse($response->headers->hasCacheControlDirective('private'));

View File

@ -86,7 +86,7 @@ class MongoDbSessionHandlerTest extends TestCase
->method('findOne')
->willReturnCallback(function ($criteria) use ($testTimeout) {
$this->assertArrayHasKey($this->options['id_field'], $criteria);
$this->assertEquals($criteria[$this->options['id_field']], 'foo');
$this->assertEquals('foo', $criteria[$this->options['id_field']]);
$this->assertArrayHasKey($this->options['expiry_field'], $criteria);
$this->assertArrayHasKey('$gte', $criteria[$this->options['expiry_field']]);

View File

@ -83,10 +83,10 @@ class PhpBridgeSessionStorageTest extends TestCase
$_SESSION['drak'] = 'loves symfony';
$storage->getBag('attributes')->set('symfony', 'greatness');
$key = $storage->getBag('attributes')->getStorageKey();
$this->assertEquals($_SESSION[$key], ['symfony' => 'greatness']);
$this->assertEquals($_SESSION['drak'], 'loves symfony');
$this->assertEquals(['symfony' => 'greatness'], $_SESSION[$key]);
$this->assertEquals('loves symfony', $_SESSION['drak']);
$storage->clear();
$this->assertEquals($_SESSION[$key], []);
$this->assertEquals($_SESSION['drak'], 'loves symfony');
$this->assertEquals([], $_SESSION[$key]);
$this->assertEquals('loves symfony', $_SESSION['drak']);
}
}

View File

@ -228,9 +228,9 @@ class FileProfilerStorageTest extends TestCase
$records = $this->storage->find('', '', 3, 'GET', $start, time() + 3 * 60);
$this->assertCount(3, $records, '->find() returns all previously added records');
$this->assertEquals($records[0]['token'], 'time_2', '->find() returns records ordered by time in descendant order');
$this->assertEquals($records[1]['token'], 'time_1', '->find() returns records ordered by time in descendant order');
$this->assertEquals($records[2]['token'], 'time_0', '->find() returns records ordered by time in descendant order');
$this->assertEquals('time_2', $records[0]['token'], '->find() returns records ordered by time in descendant order');
$this->assertEquals('time_1', $records[1]['token'], '->find() returns records ordered by time in descendant order');
$this->assertEquals('time_0', $records[2]['token'], '->find() returns records ordered by time in descendant order');
$records = $this->storage->find('', '', 3, 'GET', $start, time() + 2 * 60);
$this->assertCount(2, $records, '->find() should return only first two of the previously added records');

View File

@ -25,9 +25,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
class Connection extends AbstractConnection
{
private const LDAP_INVALID_CREDENTIALS = '0x31';
private const LDAP_TIMEOUT = '0x55';
private const LDAP_ALREADY_EXISTS = '0x44';
private const LDAP_INVALID_CREDENTIALS = 0x31;
private const LDAP_TIMEOUT = 0x55;
private const LDAP_ALREADY_EXISTS = 0x44;
/** @var bool */
private $bound = false;

View File

@ -94,12 +94,12 @@ class AdapterTest extends LdapTestCase
$ldap->getConnection()->bind('cn=admin,dc=symfony,dc=com', 'symfony');
$query = $ldap->createQuery('cn=Fabien Potencier,dc=symfony,dc=com', '(objectclass=*)', [
'scope' => Query::SCOPE_BASE,
'scope' => Query::SCOPE_BASE,
]);
$result = $query->execute();
$entry = $result[0];
$this->assertEquals($result->count(), 1);
$this->assertEquals(1, $result->count());
$this->assertEquals(['Fabien Potencier'], $entry->getAttribute('cn'));
}
@ -116,8 +116,8 @@ class AdapterTest extends LdapTestCase
$subtree_count = $ldap->createQuery('ou=Components,dc=symfony,dc=com', '(objectclass=*)')->execute()->count();
$this->assertNotEquals($one_level_result->count(), $subtree_count);
$this->assertEquals($one_level_result->count(), 1);
$this->assertEquals($one_level_result[0]->getAttribute('ou'), ['Ldap']);
$this->assertEquals(1, $one_level_result->count());
$this->assertEquals(['Ldap'], $one_level_result[0]->getAttribute('ou'));
}
public function testLdapPagination()

View File

@ -186,7 +186,7 @@ class LdapManagerTest extends LdapTestCase
$result = $this->executeSearchQuery(1);
$renamedEntry = $result[0];
$this->assertEquals($renamedEntry->getAttribute('cn')[0], 'Kevin');
$this->assertEquals('Kevin', $renamedEntry->getAttribute('cn')[0]);
$oldRdn = $entry->getAttribute('cn')[0];
$entryManager->rename($renamedEntry, 'cn='.$oldRdn);

View File

@ -59,8 +59,9 @@ class MandrillHttpTransportTest extends TestCase
$body = json_decode($options['body'], true);
$message = $body['raw_message'];
$this->assertSame('KEY', $body['key']);
$this->assertSame('Fabien', $body['from_name']);
$this->assertSame('fabpot@symfony.com', $body['from_email']);
$this->assertSame('saif.gmati@symfony.com', $body['to'][0]);
$this->assertSame('Fabien <fabpot@symfony.com>', $body['from_email']);
$this->assertStringContainsString('Subject: Hello!', $message);
$this->assertStringContainsString('To: Saif Eddin <saif.gmati@symfony.com>', $message);

View File

@ -51,7 +51,8 @@ class MandrillHttpTransport extends AbstractHttpTransport
'to' => array_map(function (Address $recipient): string {
return $recipient->getAddress();
}, $envelope->getRecipients()),
'from_email' => $envelope->getSender()->toString(),
'from_email' => $envelope->getSender()->getAddress(),
'from_name' => $envelope->getSender()->getName(),
'raw_message' => $message->toString(),
],
]);

View File

@ -166,4 +166,30 @@ class SendgridApiTransportTest extends TestCase
$this->assertArrayHasKey('foo', $payload['headers']);
$this->assertEquals('bar', $payload['headers']['foo']);
}
public function testReplyTo()
{
$from = 'from@example.com';
$to = 'to@example.com';
$replyTo = 'replyto@example.com';
$email = new Email();
$email->from($from)
->to($to)
->replyTo($replyTo)
->text('content');
$envelope = new Envelope(new Address($from), [new Address($to)]);
$transport = new SendgridApiTransport('ACCESS_KEY');
$method = new \ReflectionMethod(SendgridApiTransport::class, 'getPayload');
$method->setAccessible(true);
$payload = $method->invoke($transport, $email, $envelope);
$this->assertArrayHasKey('from', $payload);
$this->assertArrayHasKey('email', $payload['from']);
$this->assertSame($from, $payload['from']['email']);
$this->assertArrayHasKey('reply_to', $payload);
$this->assertArrayHasKey('email', $payload['reply_to']);
$this->assertSame($replyTo, $payload['reply_to']['email']);
}
}

View File

@ -93,11 +93,16 @@ class SendgridApiTransport extends AbstractApiTransport
if ($emails = array_map($addressStringifier, $email->getBcc())) {
$personalization['bcc'] = $emails;
}
if ($emails = array_map($addressStringifier, $email->getReplyTo())) {
// Email class supports an array of reply-to addresses,
// but SendGrid only supports a single address
$payload['reply_to'] = $emails[0];
}
$payload['personalizations'][] = $personalization;
// these headers can't be overwritten according to Sendgrid docs
// see https://developers.pepipost.com/migration-api/new-subpage/email-send
// see https://sendgrid.api-docs.io/v3.0/mail-send/mail-send-errors#-Headers-Errors
$headersToBypass = ['x-sg-id', 'x-sg-eid', 'received', 'dkim-signature', 'content-transfer-encoding', 'from', 'to', 'cc', 'bcc', 'subject', 'content-type', 'reply-to'];
foreach ($email->getHeaders()->all() as $name => $header) {
if (\in_array($name, $headersToBypass, true)) {

View File

@ -158,7 +158,8 @@ final class ParameterizedHeader extends UnstructuredHeader
*/
private function getEndOfParameterValue(string $value, bool $encoded = false, bool $firstLine = false): string
{
if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) {
$forceHttpQuoting = 'content-disposition' === strtolower($this->getName()) && 'form-data' === $this->getValue();
if ($forceHttpQuoting || !preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) {
$value = '"'.$value.'"';
}
$prepend = '=';

View File

@ -131,7 +131,7 @@ class TextPart extends AbstractPart
if ($this->charset) {
$headers->setHeaderParameter('Content-Type', 'charset', $this->charset);
}
if ($this->name) {
if ($this->name && 'form-data' !== $this->disposition) {
$headers->setHeaderParameter('Content-Type', 'name', $this->name);
}
$headers->setHeaderBody('Text', 'Content-Transfer-Encoding', $this->encoding);

View File

@ -41,9 +41,9 @@ class PhpExecutableFinderTest extends TestCase
$f = new PhpExecutableFinder();
if ('phpdbg' === \PHP_SAPI) {
$this->assertEquals($f->findArguments(), ['-qrr'], '::findArguments() returns phpdbg arguments');
$this->assertEquals(['-qrr'], $f->findArguments(), '::findArguments() returns phpdbg arguments');
} else {
$this->assertEquals($f->findArguments(), [], '::findArguments() returns no arguments');
$this->assertEquals([], $f->findArguments(), '::findArguments() returns no arguments');
}
}

View File

@ -223,8 +223,9 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
throw new RuntimeException(sprintf('The type "%s" has no mapped class for the abstract object "%s".', $type, $class));
}
$class = $mappedClass;
$reflectionClass = new \ReflectionClass($class);
if ($mappedClass !== $class) {
return $this->instantiateObject($data, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format);
}
}
return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format);

View File

@ -19,6 +19,7 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
@ -235,6 +236,43 @@ class AbstractObjectNormalizerTest extends TestCase
$this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux);
}
public function testDenormalizeWithNestedDiscriminatorMap()
{
$classDiscriminatorResolver = new class() implements ClassDiscriminatorResolverInterface {
public function getMappingForClass(string $class): ?ClassDiscriminatorMapping
{
switch ($class) {
case AbstractDummy::class:
return new ClassDiscriminatorMapping('type', [
'foo' => AbstractDummyFirstChild::class,
]);
case AbstractDummyFirstChild::class:
return new ClassDiscriminatorMapping('nested_type', [
'bar' => AbstractDummySecondChild::class,
]);
default:
return null;
}
}
public function getMappingForMappedObject($object): ?ClassDiscriminatorMapping
{
return null;
}
public function getTypeForMappedObject($object): ?string
{
return null;
}
};
$normalizer = new AbstractObjectNormalizerDummy(null, null, null, $classDiscriminatorResolver);
$denormalizedData = $normalizer->denormalize(['type' => 'foo', 'nested_type' => 'bar'], AbstractDummy::class);
$this->assertInstanceOf(AbstractDummySecondChild::class, $denormalizedData);
}
/**
* Test that additional attributes throw an exception if no metadata factory is specified.
*/

View File

@ -180,9 +180,9 @@ class PhpEngineTest extends TestCase
$this->loader->setTemplate('global.php', '<?php echo $global; ?>');
$this->assertEquals($engine->render('global.php'), 'global variable');
$this->assertEquals('global variable', $engine->render('global.php'));
$this->assertEquals($engine->render('global.php', ['global' => 'overwritten']), 'overwritten');
$this->assertEquals('overwritten', $engine->render('global.php', ['global' => 'overwritten']));
}
public function testGetLoader()