Merge branch '4.3' into 4.4

* 4.3: (34 commits)
  [PhpunitBridge] Read environment variable from superglobals
  [Bridge/PhpUnit] Fix PHP5.5 compat
  [PhpUnitBridge] More accurate grouping
  fixed CS
  Extract unrecoverable exception to interface
  [FrameworkBundle] Fix calling Client::getProfile() before sending a request
  Fix type error
  [Security/Core] require libsodium >= 1.0.14
  [Workflow] re-add workflow.definition tag to workflow services
  [Security/Core] Don't use ParagonIE_Sodium_Compat
  revert #30525 due to performance penalty
  collect called listeners information only once
  [Lock] fix missing inherit docs in RedisStore
  [Messenger] fix retrying handlers using DoctrineTransactionMiddleware
  [Mailgun Mailer] fixed issue when using html body
  [HttpClient] fix timing measurements with NativeHttpClient
  [HttpClient] fix dealing with 1xx informational responses
  add test to avoid regressions
  fix mirroring directory into parent directory
  fix typos
  ...
This commit is contained in:
Nicolas Grekas 2019-06-26 11:30:56 +02:00
commit 55c0b02587
60 changed files with 612 additions and 227 deletions

View File

@ -138,6 +138,9 @@ class DoctrineDataCollector extends DataCollector
if (!\is_array($query['params'])) { if (!\is_array($query['params'])) {
$query['params'] = [$query['params']]; $query['params'] = [$query['params']];
} }
if (!\is_array($query['types'])) {
$query['types'] = [];
}
foreach ($query['params'] as $j => $param) { foreach ($query['params'] as $j => $param) {
if (isset($query['types'][$j])) { if (isset($query['types'][$j])) {
// Transform the param according to the type // Transform the param according to the type

View File

@ -13,7 +13,9 @@ namespace Symfony\Bridge\Doctrine\Messenger;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
use Symfony\Component\Messenger\Middleware\StackInterface; use Symfony\Component\Messenger\Middleware\StackInterface;
use Symfony\Component\Messenger\Stamp\HandledStamp;
/** /**
* Wraps all handlers in a single doctrine transaction. * Wraps all handlers in a single doctrine transaction.
@ -36,6 +38,12 @@ class DoctrineTransactionMiddleware extends AbstractDoctrineMiddleware
} catch (\Throwable $exception) { } catch (\Throwable $exception) {
$entityManager->getConnection()->rollBack(); $entityManager->getConnection()->rollBack();
if ($exception instanceof HandlerFailedException) {
// Remove all HandledStamp from the envelope so the retry will execute all handlers again.
// When a handler fails, the queries of allegedly successful previous handlers just got rolled back.
throw new HandlerFailedException($exception->getEnvelope()->withoutAll(HandledStamp::class), $exception->getNestedExceptions());
}
throw $exception; throw $exception;
} }
} }

View File

@ -102,6 +102,18 @@ class DoctrineDataCollectorTest extends TestCase
$this->assertTrue($collectedQueries['default'][1]['explainable']); $this->assertTrue($collectedQueries['default'][1]['explainable']);
} }
public function testCollectQueryWithNoTypes()
{
$queries = [
['sql' => 'SET sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))', 'params' => [], 'types' => null, 'executionMS' => 1],
];
$c = $this->createCollector($queries);
$c->collect(new Request(), new Response());
$collectedQueries = $c->getQueries();
$this->assertSame([], $collectedQueries['default'][0]['types']);
}
public function testReset() public function testReset()
{ {
$queries = [ $queries = [

View File

@ -139,10 +139,13 @@ class DeprecationErrorHandler
$group = 'unsilenced'; $group = 'unsilenced';
} elseif ($deprecation->isLegacy(self::$utilPrefix)) { } elseif ($deprecation->isLegacy(self::$utilPrefix)) {
$group = 'legacy'; $group = 'legacy';
} elseif (!$deprecation->isSelf()) {
$group = $deprecation->isIndirect() ? 'remaining indirect' : 'remaining direct';
} else { } else {
$group = 'remaining self'; $group = [
Deprecation::TYPE_SELF => 'remaining self',
Deprecation::TYPE_DIRECT => 'remaining direct',
Deprecation::TYPE_INDIRECT => 'remaining indirect',
Deprecation::TYPE_UNDETERMINED => 'other',
][$deprecation->getType()];
} }
if ($this->getConfiguration()->shouldDisplayStackTrace($msg)) { if ($this->getConfiguration()->shouldDisplayStackTrace($msg)) {
@ -216,7 +219,13 @@ class DeprecationErrorHandler
return $this->configuration; return $this->configuration;
} }
if (false === $mode = $this->mode) { if (false === $mode = $this->mode) {
$mode = getenv('SYMFONY_DEPRECATIONS_HELPER'); if (isset($_SERVER['SYMFONY_DEPRECATIONS_HELPER'])) {
$mode = $_SERVER['SYMFONY_DEPRECATIONS_HELPER'];
} elseif (isset($_ENV['SYMFONY_DEPRECATIONS_HELPER'])) {
$mode = $_ENV['SYMFONY_DEPRECATIONS_HELPER'];
} else {
$mode = getenv('SYMFONY_DEPRECATIONS_HELPER');
}
} }
if ('strict' === $mode) { if ('strict' === $mode) {
return $this->configuration = Configuration::inStrictMode(); return $this->configuration = Configuration::inStrictMode();

View File

@ -18,6 +18,15 @@ use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor;
*/ */
class Deprecation class Deprecation
{ {
const PATH_TYPE_VENDOR = 'path_type_vendor';
const PATH_TYPE_SELF = 'path_type_internal';
const PATH_TYPE_UNDETERMINED = 'path_type_undetermined';
const TYPE_SELF = 'type_self';
const TYPE_DIRECT = 'type_direct';
const TYPE_INDIRECT = 'type_indirect';
const TYPE_UNDETERMINED = 'type_undetermined';
/** /**
* @var array * @var array
*/ */
@ -39,13 +48,21 @@ class Deprecation
private $originMethod; private $originMethod;
/** /**
* @var bool * @var string one of the PATH_TYPE_* constants
*/ */
private $self; private $triggeringFilePathType;
/** @var string[] absolute paths to vendor directories */ /** @var string[] absolute paths to vendor directories */
private static $vendors; private static $vendors;
/**
* @var string[] absolute paths to source or tests of the project. This
* excludes cache directories, because it is based on
* autoloading rules and cache systems typically do not use
* those.
*/
private static $internalPaths;
/** /**
* @param string $message * @param string $message
* @param string $file * @param string $file
@ -59,7 +76,7 @@ class Deprecation
// No-op // No-op
} }
$line = $trace[$i]; $line = $trace[$i];
$this->self = !$this->pathOriginatesFromVendor($file); $this->trigerringFilePathType = $this->getPathType($file);
if (isset($line['object']) || isset($line['class'])) { if (isset($line['object']) || isset($line['class'])) {
if (isset($line['class']) && 0 === strpos($line['class'], SymfonyTestsListenerFor::class)) { if (isset($line['class']) && 0 === strpos($line['class'], SymfonyTestsListenerFor::class)) {
$parsedMsg = unserialize($this->message); $parsedMsg = unserialize($this->message);
@ -70,8 +87,9 @@ class Deprecation
// \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest() // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest()
// then we need to use the serialized information to determine // then we need to use the serialized information to determine
// if the error has been triggered from vendor code. // if the error has been triggered from vendor code.
$this->self = isset($parsedMsg['triggering_file']) if (isset($parsedMsg['triggering_file'])) {
&& $this->pathOriginatesFromVendor($parsedMsg['triggering_file']); $this->trigerringFilePathType = $this->getPathType($parsedMsg['triggering_file']);
}
return; return;
} }
@ -101,14 +119,6 @@ class Deprecation
return isset($this->originClass); return isset($this->originClass);
} }
/**
* @return bool
*/
public function isSelf()
{
return $this->self;
}
/** /**
* @return string * @return string
*/ */
@ -163,10 +173,16 @@ class Deprecation
* Tells whether both the calling package and the called package are vendor * Tells whether both the calling package and the called package are vendor
* packages. * packages.
* *
* @return bool * @return string
*/ */
public function isIndirect() public function getType()
{ {
if (self::PATH_TYPE_SELF === $this->trigerringFilePathType) {
return self::TYPE_SELF;
}
if (self::PATH_TYPE_UNDETERMINED === $this->trigerringFilePathType) {
return self::TYPE_UNDETERMINED;
}
$erroringFile = $erroringPackage = null; $erroringFile = $erroringPackage = null;
foreach ($this->trace as $line) { foreach ($this->trace as $line) {
if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) { if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) {
@ -179,13 +195,16 @@ class Deprecation
if ('-' === $file || 'Standard input code' === $file || !realpath($file)) { if ('-' === $file || 'Standard input code' === $file || !realpath($file)) {
continue; continue;
} }
if (!$this->pathOriginatesFromVendor($file)) { if (self::PATH_TYPE_SELF === $this->getPathType($file)) {
return false; return self::TYPE_DIRECT;
}
if (self::PATH_TYPE_UNDETERMINED === $this->getPathType($file)) {
return self::TYPE_UNDETERMINED;
} }
if (null !== $erroringFile && null !== $erroringPackage) { if (null !== $erroringFile && null !== $erroringPackage) {
$package = $this->getPackage($file); $package = $this->getPackage($file);
if ('composer' !== $package && $package !== $erroringPackage) { if ('composer' !== $package && $package !== $erroringPackage) {
return true; return self::TYPE_INDIRECT;
} }
continue; continue;
} }
@ -193,11 +212,11 @@ class Deprecation
$erroringPackage = $this->getPackage($file); $erroringPackage = $this->getPackage($file);
} }
return false; return self::TYPE_DIRECT;
} }
/** /**
* pathOriginatesFromVendor() should always be called prior to calling this method. * getPathType() should always be called prior to calling this method.
* *
* @param string $path * @param string $path
* *
@ -237,6 +256,15 @@ class Deprecation
$v = \dirname(\dirname($r->getFileName())); $v = \dirname(\dirname($r->getFileName()));
if (file_exists($v.'/composer/installed.json')) { if (file_exists($v.'/composer/installed.json')) {
self::$vendors[] = $v; self::$vendors[] = $v;
$loader = require $v.'/autoload.php';
$paths = self::getSourcePathsFromPrefixes(array_merge($loader->getPrefixes(), $loader->getPrefixesPsr4()));
}
}
}
foreach ($paths as $path) {
foreach (self::$vendors as $vendor) {
if (0 !== strpos($path, $vendor)) {
self::$internalPaths[] = $path;
} }
} }
} }
@ -245,24 +273,41 @@ class Deprecation
return self::$vendors; return self::$vendors;
} }
private static function getSourcePathsFromPrefixes(array $prefixesByNamespace)
{
foreach ($prefixesByNamespace as $prefixes) {
foreach ($prefixes as $prefix) {
if (false !== realpath($prefix)) {
yield realpath($prefix);
}
}
}
}
/** /**
* @param string $path * @param string $path
* *
* @return bool * @return string
*/ */
private function pathOriginatesFromVendor($path) private function getPathType($path)
{ {
$realPath = realpath($path); $realPath = realpath($path);
if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) { if (false === $realPath && '-' !== $path && 'Standard input code' !== $path) {
return true; return self::PATH_TYPE_UNDETERMINED;
} }
foreach (self::getVendors() as $vendor) { foreach (self::getVendors() as $vendor) {
if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { if (0 === strpos($realPath, $vendor) && false !== strpbrk(substr($realPath, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
return true; return self::PATH_TYPE_VENDOR;
} }
} }
return false; foreach (self::$internalPaths as $internalPath) {
if (0 === strpos($realPath, $internalPath)) {
return self::PATH_TYPE_SELF;
}
}
return self::PATH_TYPE_UNDETERMINED;
} }
/** /**
@ -281,19 +326,4 @@ class Deprecation
"\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString()). "\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString()).
"\n"; "\n";
} }
private function getPackageFromLine(array $line)
{
if (!isset($line['file'])) {
return 'internal function';
}
if (!$this->pathOriginatesFromVendor($line['file'])) {
return 'source code';
}
try {
return $this->getPackage($line['file']);
} catch (\RuntimeException $e) {
return 'unknown';
}
}
} }

View File

@ -27,7 +27,7 @@ class DeprecationTest extends TestCase
public function testItCanTellWhetherItIsInternal() public function testItCanTellWhetherItIsInternal()
{ {
$deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__); $deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__);
$this->assertTrue($deprecation->isSelf()); $this->assertSame(Deprecation::TYPE_SELF, $deprecation->getType());
} }
public function testLegacyTestMethodIsDetectedAsSuch() public function testLegacyTestMethodIsDetectedAsSuch()
@ -46,7 +46,7 @@ class DeprecationTest extends TestCase
public function testItRulesOutFilesOutsideVendorsAsIndirect() public function testItRulesOutFilesOutsideVendorsAsIndirect()
{ {
$deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__); $deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__);
$this->assertFalse($deprecation->isIndirect()); $this->assertNotSame(Deprecation::TYPE_INDIRECT, $deprecation->getType());
} }
/** /**

View File

@ -73,15 +73,13 @@ Unsilenced deprecation notices (3)
1x: unsilenced bar deprecation 1x: unsilenced bar deprecation
1x in FooTestCase::testNonLegacyBar 1x in FooTestCase::testNonLegacyBar
Remaining self deprecation notices (1) Legacy deprecation notices (1)
Other deprecation notices (2)
1x: root deprecation
1x: silenced bar deprecation 1x: silenced bar deprecation
1x in FooTestCase::testNonLegacyBar 1x in FooTestCase::testNonLegacyBar
Legacy deprecation notices (1)
Other deprecation notices (1)
1x: root deprecation
I get precedence over any exit statements inside the deprecation error handler. I get precedence over any exit statements inside the deprecation error handler.

View File

@ -1,9 +1,9 @@
--TEST-- --TEST--
Test DeprecationErrorHandler in weak mode Test DeprecationErrorHandler in disabled mode
--FILE-- --FILE--
<?php <?php
putenv('SYMFONY_DEPRECATIONS_HELPER=disabled'); $_SERVER['SYMFONY_DEPRECATIONS_HELPER'] = 'disabled';
putenv('ANSICON'); putenv('ANSICON');
putenv('ConEmuANSI'); putenv('ConEmuANSI');
putenv('TERM'); putenv('TERM');

View File

@ -0,0 +1,24 @@
--TEST--
Test DeprecationErrorHandler in disabled mode (via putenv)
--FILE--
<?php
$_ENV['SYMFONY_DEPRECATIONS_HELPER'] = 'disabled';
putenv('ANSICON');
putenv('ConEmuANSI');
putenv('TERM');
$vendor = __DIR__;
while (!file_exists($vendor.'/vendor')) {
$vendor = dirname($vendor);
}
define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
require PHPUNIT_COMPOSER_INSTALL;
require_once __DIR__.'/../../bootstrap.php';
echo (int) set_error_handler('var_dump');
echo (int) class_exists('Symfony\Bridge\PhpUnit\DeprecationErrorHandler', false);
?>
--EXPECTF--
00

View File

@ -0,0 +1,24 @@
--TEST--
Test DeprecationErrorHandler in disabled mode (via putenv)
--FILE--
<?php
putenv('SYMFONY_DEPRECATIONS_HELPER=disabled');
putenv('ANSICON');
putenv('ConEmuANSI');
putenv('TERM');
$vendor = __DIR__;
while (!file_exists($vendor.'/vendor')) {
$vendor = dirname($vendor);
}
define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
require PHPUNIT_COMPOSER_INSTALL;
require_once __DIR__.'/../../bootstrap.php';
echo (int) set_error_handler('var_dump');
echo (int) class_exists('Symfony\Bridge\PhpUnit\DeprecationErrorHandler', false);
?>
--EXPECTF--
00

View File

@ -1,3 +1,5 @@
<?php <?php
require_once __DIR__.'/composer/autoload_real.php'; require_once __DIR__.'/composer/autoload_real.php';
return ComposerAutoloaderInitFake::getLoader();

View File

@ -1,5 +1,22 @@
<?php <?php
class ComposerLoaderFake
{
public function getPrefixes()
{
return [];
}
public function getPrefixesPsr4()
{
return [];
}
}
class ComposerAutoloaderInitFake class ComposerAutoloaderInitFake
{ {
public static function getLoader()
{
return new ComposerLoaderFake();
}
} }

View File

@ -61,14 +61,12 @@ Unsilenced deprecation notices (3)
1x: unsilenced bar deprecation 1x: unsilenced bar deprecation
1x in FooTestCase::testNonLegacyBar 1x in FooTestCase::testNonLegacyBar
Remaining self deprecation notices (1) Legacy deprecation notices (1)
Other deprecation notices (2)
1x: root deprecation
1x: silenced bar deprecation 1x: silenced bar deprecation
1x in FooTestCase::testNonLegacyBar 1x in FooTestCase::testNonLegacyBar
Legacy deprecation notices (1)
Other deprecation notices (1)
1x: root deprecation

View File

@ -73,17 +73,15 @@ Unsilenced deprecation notices (3)
1x: unsilenced bar deprecation 1x: unsilenced bar deprecation
1x in FooTestCase::testNonLegacyBar 1x in FooTestCase::testNonLegacyBar
Remaining self deprecation notices (1) Legacy deprecation notices (1)
Other deprecation notices (2)
1x: root deprecation
1x: silenced bar deprecation 1x: silenced bar deprecation
1x in FooTestCase::testNonLegacyBar 1x in FooTestCase::testNonLegacyBar
Legacy deprecation notices (1)
Other deprecation notices (1)
1x: root deprecation
Shutdown-time deprecations: Shutdown-time deprecations:
Other deprecation notices (1) Other deprecation notices (1)

View File

@ -61,14 +61,13 @@ Unsilenced deprecation notices (3)
1x: unsilenced bar deprecation 1x: unsilenced bar deprecation
1x in FooTestCase::testNonLegacyBar 1x in FooTestCase::testNonLegacyBar
Remaining self deprecation notices (1) Legacy deprecation notices (1)
Other deprecation notices (2)
1x: root deprecation
1x: silenced bar deprecation 1x: silenced bar deprecation
1x in FooTestCase::testNonLegacyBar 1x in FooTestCase::testNonLegacyBar
Legacy deprecation notices (1)
Other deprecation notices (1)
1x: root deprecation

View File

@ -114,6 +114,7 @@
<div class="form-group{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}"> <div class="form-group{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
{{- form_label(form) }} {# -#} {{- form_label(form) }} {# -#}
{{ form_widget(form, widget_attr) }} {# -#} {{ form_widget(form, widget_attr) }} {# -#}
{{- form_help(form) -}}
{{ form_errors(form) }} {# -#} {{ form_errors(form) }} {# -#}
</div> {# -#} </div> {# -#}
{%- endblock form_row %} {%- endblock form_row %}

View File

@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Dumper\CliDumper; use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Symfony\Component\VarDumper\Dumper\HtmlDumper;
@ -43,9 +44,9 @@ class DebugExtension extends Extension
->addMethodCall('setMinDepth', [$config['min_depth']]) ->addMethodCall('setMinDepth', [$config['min_depth']])
->addMethodCall('setMaxString', [$config['max_string_length']]); ->addMethodCall('setMaxString', [$config['max_string_length']]);
if (method_exists(ReflectionClass::class, 'unsetClosureFileInfo')) { if (method_exists(ReflectionCaster::class, 'unsetClosureFileInfo')) {
$container->getDefinition('var_dumper.cloner') $container->getDefinition('var_dumper.cloner')
->addMethodCall('addCasters', ReflectionClass::UNSET_CLOSURE_FILE_INFO); ->addMethodCall('addCasters', [ReflectionCaster::UNSET_CLOSURE_FILE_INFO]);
} }
if (method_exists(HtmlDumper::class, 'setTheme') && 'dark' !== $config['theme']) { if (method_exists(HtmlDumper::class, 'setTheme') && 'dark' !== $config['theme']) {

View File

@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
use Symfony\Bundle\DebugBundle\DependencyInjection\DebugExtension; use Symfony\Bundle\DebugBundle\DependencyInjection\DebugExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
class DebugExtensionTest extends TestCase class DebugExtensionTest extends TestCase
{ {
@ -36,6 +37,39 @@ class DebugExtensionTest extends TestCase
$this->assertSame($expectedTags, $container->getDefinition('data_collector.dump')->getTag('data_collector')); $this->assertSame($expectedTags, $container->getDefinition('data_collector.dump')->getTag('data_collector'));
} }
public function testUnsetClosureFileInfoShouldBeRegisteredInVarCloner()
{
if (!method_exists(ReflectionCaster::class, 'unsetClosureFileInfo')) {
$this->markTestSkipped('Method not available');
}
$container = $this->createContainer();
$container->registerExtension(new DebugExtension());
$container->loadFromExtension('debug', []);
$this->compileContainer($container);
$definition = $container->getDefinition('var_dumper.cloner');
$called = false;
foreach ($definition->getMethodCalls() as $call) {
if ('addCasters' !== $call[0]) {
continue;
}
$argument = $call[1][0] ?? null;
if (null === $argument) {
continue;
}
if (['Closure' => ReflectionCaster::class.'::unsetClosureFileInfo'] === $argument) {
$called = true;
break;
}
}
$this->assertTrue($called);
}
private function createContainer() private function createContainer()
{ {
$container = new ContainerBuilder(new ParameterBag([ $container = new ContainerBuilder(new ParameterBag([

View File

@ -23,7 +23,7 @@ CHANGELOG
* [BC Break] When using Messenger, the default transport changed from * [BC Break] When using Messenger, the default transport changed from
using Symfony's serializer service to use `PhpSerializer`, which uses using Symfony's serializer service to use `PhpSerializer`, which uses
PHP's native `serialize()` and `unserialize()` functions. To use the PHP's native `serialize()` and `unserialize()` functions. To use the
original serialization method, set the `framework.messenger.defaut_serializer` original serialization method, set the `framework.messenger.default_serializer`
config option to `messenger.transport.symfony_serializer`. Or set the config option to `messenger.transport.symfony_serializer`. Or set the
`serializer` option under one specific `transport`. `serializer` option under one specific `transport`.
* [BC Break] The `framework.messenger.serializer` config key changed to * [BC Break] The `framework.messenger.serializer` config key changed to

View File

@ -66,7 +66,7 @@ class Client extends HttpKernelBrowser
*/ */
public function getProfile() public function getProfile()
{ {
if (!$this->kernel->getContainer()->has('profiler')) { if (null === $this->response || !$this->kernel->getContainer()->has('profiler')) {
return false; return false;
} }

View File

@ -652,6 +652,10 @@ class FrameworkExtension extends Extension
$definitionDefinition->addArgument($transitions); $definitionDefinition->addArgument($transitions);
$definitionDefinition->addArgument($initialMarking); $definitionDefinition->addArgument($initialMarking);
$definitionDefinition->addArgument($metadataStoreDefinition); $definitionDefinition->addArgument($metadataStoreDefinition);
$definitionDefinition->addTag('workflow.definition', [
'name' => $name,
'type' => $type,
]);
// Create MarkingStore // Create MarkingStore
if (isset($workflow['marking_store']['type'])) { if (isset($workflow['marking_store']['type'])) {

View File

@ -311,6 +311,8 @@ abstract class FrameworkExtensionTest extends TestCase
$workflowDefinition->getArgument(0), $workflowDefinition->getArgument(0),
'Places are passed to the workflow definition' 'Places are passed to the workflow definition'
); );
$this->assertSame(['workflow.definition' => [['name' => 'legacy', 'type' => 'state_machine']]], $workflowDefinition->getTags());
} }
/** /**

View File

@ -28,9 +28,9 @@ class ProfilerTest extends WebTestCase
// enable the profiler for the next request // enable the profiler for the next request
$client->enableProfiler(); $client->enableProfiler();
$crawler = $client->request('GET', '/profiler'); $this->assertFalse($client->getProfile());
$profile = $client->getProfile(); $client->request('GET', '/profiler');
$this->assertInternalType('object', $profile); $this->assertInternalType('object', $client->getProfile());
$client->request('GET', '/profiler'); $client->request('GET', '/profiler');
$this->assertFalse($client->getProfile()); $this->assertFalse($client->getProfile());

View File

@ -48,7 +48,7 @@
"symfony/security-http": "^3.4|^4.0|^5.0", "symfony/security-http": "^3.4|^4.0|^5.0",
"symfony/serializer": "^4.3|^5.0", "symfony/serializer": "^4.3|^5.0",
"symfony/stopwatch": "^3.4|^4.0|^5.0", "symfony/stopwatch": "^3.4|^4.0|^5.0",
"symfony/translation": "^4.2|^5.0", "symfony/translation": "^4.3|^5.0",
"symfony/templating": "^3.4|^4.0|^5.0", "symfony/templating": "^3.4|^4.0|^5.0",
"symfony/twig-bundle": "^3.4|^4.0|^5.0", "symfony/twig-bundle": "^3.4|^4.0|^5.0",
"symfony/validator": "^4.1|^5.0", "symfony/validator": "^4.1|^5.0",

View File

@ -12,8 +12,8 @@
namespace Symfony\Component\Cache\Tests\Adapter; namespace Symfony\Component\Cache\Tests\Adapter;
use Symfony\Component\Cache\Adapter\SimpleCacheAdapter; use Symfony\Component\Cache\Adapter\SimpleCacheAdapter;
use Symfony\Component\Cache\Simple\FilesystemCache;
use Symfony\Component\Cache\Simple\ArrayCache; use Symfony\Component\Cache\Simple\ArrayCache;
use Symfony\Component\Cache\Simple\FilesystemCache;
/** /**
* @group time-sensitive * @group time-sensitive

View File

@ -314,7 +314,7 @@ class DotenvTest extends TestCase
$dotenv->load(__DIR__); $dotenv->load(__DIR__);
} }
public function testServerSuperglobalIsNotOverriden() public function testServerSuperglobalIsNotOverridden()
{ {
$originalValue = $_SERVER['argc']; $originalValue = $_SERVER['argc'];
@ -324,7 +324,7 @@ class DotenvTest extends TestCase
$this->assertSame($originalValue, $_SERVER['argc']); $this->assertSame($originalValue, $_SERVER['argc']);
} }
public function testEnvVarIsNotOverriden() public function testEnvVarIsNotOverridden()
{ {
putenv('TEST_ENV_VAR=original_value'); putenv('TEST_ENV_VAR=original_value');
$_SERVER['TEST_ENV_VAR'] = 'original_value'; $_SERVER['TEST_ENV_VAR'] = 'original_value';
@ -335,7 +335,7 @@ class DotenvTest extends TestCase
$this->assertSame('original_value', getenv('TEST_ENV_VAR')); $this->assertSame('original_value', getenv('TEST_ENV_VAR'));
} }
public function testHttpVarIsPartiallyOverriden() public function testHttpVarIsPartiallyOverridden()
{ {
$_SERVER['HTTP_TEST_ENV_VAR'] = 'http_value'; $_SERVER['HTTP_TEST_ENV_VAR'] = 'http_value';

View File

@ -569,14 +569,15 @@ class Filesystem
} }
$this->mkdir($targetDir); $this->mkdir($targetDir);
$targetDirInfo = new \SplFileInfo($targetDir); $filesCreatedWhileMirroring = [];
foreach ($iterator as $file) { foreach ($iterator as $file) {
if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || 0 === strpos($file->getRealPath(), $targetDirInfo->getRealPath())) { if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) {
continue; continue;
} }
$target = $targetDir.substr($file->getPathname(), $originDirLen); $target = $targetDir.substr($file->getPathname(), $originDirLen);
$filesCreatedWhileMirroring[$target] = true;
if (!$copyOnWindows && is_link($file)) { if (!$copyOnWindows && is_link($file)) {
$this->symlink($file->getLinkTarget(), $target); $this->symlink($file->getLinkTarget(), $target);

View File

@ -1362,6 +1362,22 @@ class FilesystemTest extends FilesystemTestCase
$this->assertFalse($this->filesystem->exists($targetPath.'target')); $this->assertFalse($this->filesystem->exists($targetPath.'target'));
} }
public function testMirrorFromSubdirectoryInToParentDirectory()
{
$targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR;
$sourcePath = $targetPath.'bar'.\DIRECTORY_SEPARATOR;
$file1 = $sourcePath.'file1';
$file2 = $sourcePath.'file2';
$this->filesystem->mkdir($sourcePath);
file_put_contents($file1, 'FILE1');
file_put_contents($file2, 'FILE2');
$this->filesystem->mirror($sourcePath, $targetPath);
$this->assertFileEquals($file1, $targetPath.'file1');
}
/** /**
* @dataProvider providePathsForIsAbsolutePath * @dataProvider providePathsForIsAbsolutePath
*/ */

View File

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="28">
<source>This form should not contain extra fields.</source>
<target>Form ekstra alanlar içeremez.</target>
</trans-unit>
<trans-unit id="29">
<source>The uploaded file was too large. Please try to upload a smaller file.</source>
<target>Yüklenen dosya boyutu çok yüksek. Lütfen daha küçük bir dosya yüklemeyi deneyin.</target>
</trans-unit>
<trans-unit id="30">
<source>The CSRF token is invalid. Please try to resubmit the form.</source>
<target>CSRF fişi geçersiz. Formu tekrar göndermeyi deneyin.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -98,9 +98,9 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
'http_code' => 0, 'http_code' => 0,
'redirect_count' => 0, 'redirect_count' => 0,
'start_time' => 0.0, 'start_time' => 0.0,
'fopen_time' => 0.0,
'connect_time' => 0.0, 'connect_time' => 0.0,
'redirect_time' => 0.0, 'redirect_time' => 0.0,
'pretransfer_time' => 0.0,
'starttransfer_time' => 0.0, 'starttransfer_time' => 0.0,
'total_time' => 0.0, 'total_time' => 0.0,
'namelookup_time' => 0.0, 'namelookup_time' => 0.0,
@ -118,7 +118,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info) { $onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info) {
$progressInfo = $info; $progressInfo = $info;
$progressInfo['url'] = implode('', $info['url']); $progressInfo['url'] = implode('', $info['url']);
unset($progressInfo['fopen_time'], $progressInfo['size_body']); unset($progressInfo['size_body']);
if ($progress && -1 === $progress[0]) { if ($progress && -1 === $progress[0]) {
// Response completed // Response completed
@ -133,14 +133,14 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
// Always register a notification callback to compute live stats about the response // Always register a notification callback to compute live stats about the response
$notification = static function (int $code, int $severity, ?string $msg, int $msgCode, int $dlNow, int $dlSize) use ($onProgress, &$info) { $notification = static function (int $code, int $severity, ?string $msg, int $msgCode, int $dlNow, int $dlSize) use ($onProgress, &$info) {
$now = microtime(true); $info['total_time'] = microtime(true) - $info['start_time'];
$info['total_time'] = $now - $info['start_time'];
if (STREAM_NOTIFY_PROGRESS === $code) { if (STREAM_NOTIFY_PROGRESS === $code) {
$info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time'];
$info['size_upload'] += $dlNow ? 0 : $info['size_body']; $info['size_upload'] += $dlNow ? 0 : $info['size_body'];
$info['size_download'] = $dlNow; $info['size_download'] = $dlNow;
} elseif (STREAM_NOTIFY_CONNECT === $code) { } elseif (STREAM_NOTIFY_CONNECT === $code) {
$info['connect_time'] += $now - $info['fopen_time']; $info['connect_time'] = $info['total_time'];
$info['debug'] .= $info['request_header']; $info['debug'] .= $info['request_header'];
unset($info['request_header']); unset($info['request_header']);
} else { } else {
@ -310,7 +310,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
throw new TransportException(sprintf('Could not resolve host "%s".', $host)); throw new TransportException(sprintf('Could not resolve host "%s".', $host));
} }
$info['namelookup_time'] += microtime(true) - $now; $info['namelookup_time'] = microtime(true) - ($info['start_time'] ?: $now);
$multi->dnsCache[$host] = $ip = $ip[0]; $multi->dnsCache[$host] = $ip = $ip[0];
$info['debug'] .= "* Added {$host}:0:{$ip} to DNS cache\n"; $info['debug'] .= "* Added {$host}:0:{$ip} to DNS cache\n";
} else { } else {
@ -368,10 +368,9 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
return null; return null;
} }
$now = microtime(true);
$info['url'] = $url; $info['url'] = $url;
++$info['redirect_count']; ++$info['redirect_count'];
$info['redirect_time'] = $now - $info['start_time']; $info['redirect_time'] = microtime(true) - $info['start_time'];
// Do like curl and browsers: turn POST to GET on 301, 302 and 303 // Do like curl and browsers: turn POST to GET on 301, 302 and 303
if (\in_array($info['http_code'], [301, 302, 303], true)) { if (\in_array($info['http_code'], [301, 302, 303], true)) {

View File

@ -55,6 +55,7 @@ final class CurlResponse implements ResponseInterface
$this->info['start_time'] = $this->info['start_time'] ?? microtime(true); $this->info['start_time'] = $this->info['start_time'] ?? microtime(true);
$info = &$this->info; $info = &$this->info;
$headers = &$this->headers; $headers = &$this->headers;
$debugBuffer = $this->debugBuffer;
if (!$info['response_headers']) { if (!$info['response_headers']) {
// Used to keep track of what we're waiting for // Used to keep track of what we're waiting for
@ -88,9 +89,11 @@ final class CurlResponse implements ResponseInterface
if ($onProgress = $options['on_progress']) { if ($onProgress = $options['on_progress']) {
$url = isset($info['url']) ? ['url' => $info['url']] : []; $url = isset($info['url']) ? ['url' => $info['url']] : [];
curl_setopt($ch, CURLOPT_NOPROGRESS, false); curl_setopt($ch, CURLOPT_NOPROGRESS, false);
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi) { curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer) {
try { try {
$onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info); rewind($debugBuffer);
$debug = ['debug' => stream_get_contents($debugBuffer)];
$onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$multi->handlesActivity[(int) $ch][] = null; $multi->handlesActivity[(int) $ch][] = null;
$multi->handlesActivity[(int) $ch][] = $e; $multi->handlesActivity[(int) $ch][] = $e;
@ -148,12 +151,6 @@ final class CurlResponse implements ResponseInterface
if (!$info = $this->finalInfo) { if (!$info = $this->finalInfo) {
self::perform($this->multi); self::perform($this->multi);
if ('debug' === $type) {
rewind($this->debugBuffer);
return stream_get_contents($this->debugBuffer);
}
$info = array_merge($this->info, curl_getinfo($this->handle)); $info = array_merge($this->info, curl_getinfo($this->handle));
$info['url'] = $this->info['url'] ?? $info['url']; $info['url'] = $this->info['url'] ?? $info['url'];
$info['redirect_url'] = $this->info['redirect_url'] ?? null; $info['redirect_url'] = $this->info['redirect_url'] ?? null;
@ -164,9 +161,10 @@ final class CurlResponse implements ResponseInterface
$info['starttransfer_time'] = 0.0; $info['starttransfer_time'] = 0.0;
} }
rewind($this->debugBuffer);
$info['debug'] = stream_get_contents($this->debugBuffer);
if (!\in_array(curl_getinfo($this->handle, CURLINFO_PRIVATE), ['headers', 'content'], true)) { if (!\in_array(curl_getinfo($this->handle, CURLINFO_PRIVATE), ['headers', 'content'], true)) {
rewind($this->debugBuffer);
$info['debug'] = stream_get_contents($this->debugBuffer);
curl_setopt($this->handle, CURLOPT_VERBOSE, false); curl_setopt($this->handle, CURLOPT_VERBOSE, false);
rewind($this->debugBuffer); rewind($this->debugBuffer);
ftruncate($this->debugBuffer, 0); ftruncate($this->debugBuffer, 0);
@ -289,7 +287,19 @@ final class CurlResponse implements ResponseInterface
// Regular header line: add it to the list // Regular header line: add it to the list
self::addResponseHeaders([substr($data, 0, -2)], $info, $headers); self::addResponseHeaders([substr($data, 0, -2)], $info, $headers);
if (0 === strpos($data, 'HTTP') && 300 <= $info['http_code'] && $info['http_code'] < 400) { if (0 !== strpos($data, 'HTTP/')) {
if (0 === stripos($data, 'Location:')) {
$location = trim(substr($data, 9, -2));
}
return \strlen($data);
}
if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, CURLINFO_CERTINFO)) {
$info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert'));
}
if (300 <= $info['http_code'] && $info['http_code'] < 400) {
if (curl_getinfo($ch, CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) { if (curl_getinfo($ch, CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
} elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) { } elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) {
@ -298,15 +308,14 @@ final class CurlResponse implements ResponseInterface
} }
} }
if (0 === stripos($data, 'Location:')) {
$location = trim(substr($data, 9, -2));
}
return \strlen($data); return \strlen($data);
} }
// End of headers: handle redirects and add to the activity list // End of headers: handle redirects and add to the activity list
$statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); if (200 > $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE)) {
return \strlen($data);
}
$info['redirect_url'] = null; $info['redirect_url'] = null;
if (300 <= $statusCode && $statusCode < 400 && null !== $location) { if (300 <= $statusCode && $statusCode < 400 && null !== $location) {
@ -336,10 +345,6 @@ final class CurlResponse implements ResponseInterface
return 0; return 0;
} }
if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, CURLINFO_CERTINFO)) {
$info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert'));
}
curl_setopt($ch, CURLOPT_PRIVATE, 'content'); curl_setopt($ch, CURLOPT_PRIVATE, 'content');
} elseif (null !== $info['redirect_url'] && $logger) { } elseif (null !== $info['redirect_url'] && $logger) {
$logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url'])); $logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url']));

View File

@ -78,18 +78,12 @@ final class NativeResponse implements ResponseInterface
if (!$info = $this->finalInfo) { if (!$info = $this->finalInfo) {
self::perform($this->multi); self::perform($this->multi);
if ('debug' === $type) {
return $this->info['debug'];
}
$info = $this->info; $info = $this->info;
$info['url'] = implode('', $info['url']); $info['url'] = implode('', $info['url']);
unset($info['fopen_time'], $info['size_body'], $info['request_header']); unset($info['size_body'], $info['request_header']);
if (null === $this->buffer) { if (null === $this->buffer) {
$this->finalInfo = $info; $this->finalInfo = $info;
} else {
unset($info['debug']);
} }
} }
@ -134,7 +128,6 @@ final class NativeResponse implements ResponseInterface
$this->info['request_header'] .= implode("\r\n", $context['http']['header'])."\r\n\r\n"; $this->info['request_header'] .= implode("\r\n", $context['http']['header'])."\r\n\r\n";
// Send request and follow redirects when needed // Send request and follow redirects when needed
$this->info['fopen_time'] = microtime(true);
$this->handle = $h = fopen($url, 'r', false, $this->context); $this->handle = $h = fopen($url, 'r', false, $this->context);
self::addResponseHeaders($http_response_header, $this->info, $this->headers, $this->info['debug']); self::addResponseHeaders($http_response_header, $this->info, $this->headers, $this->info['debug']);
$url = ($this->resolveRedirect)($this->multi, $this->headers['location'][0] ?? null, $this->context); $url = ($this->resolveRedirect)($this->multi, $this->headers['location'][0] ?? null, $this->context);
@ -152,7 +145,7 @@ final class NativeResponse implements ResponseInterface
return; return;
} finally { } finally {
$this->info['starttransfer_time'] = $this->info['total_time'] = microtime(true) - $this->info['start_time']; $this->info['pretransfer_time'] = $this->info['total_time'] = microtime(true) - $this->info['start_time'];
restore_error_handler(); restore_error_handler();
} }
@ -273,6 +266,7 @@ final class NativeResponse implements ResponseInterface
if (null !== $e || !$remaining || feof($h)) { if (null !== $e || !$remaining || feof($h)) {
// Stream completed // Stream completed
$info['total_time'] = microtime(true) - $info['start_time']; $info['total_time'] = microtime(true) - $info['start_time'];
$info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time'];
if ($onProgress) { if ($onProgress) {
try { try {

View File

@ -253,7 +253,9 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
*/ */
public function getBag($name) public function getBag($name)
{ {
return $this->storage->getBag($name)->getBag(); $bag = $this->storage->getBag($name);
return method_exists($bag, 'getBag') ? $bag->getBag() : $bag;
} }
/** /**

View File

@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionBagProxy;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
/** /**
@ -260,4 +261,28 @@ class SessionTest extends TestCase
$flash->get('hello'); $flash->get('hello');
$this->assertTrue($this->session->isEmpty()); $this->assertTrue($this->session->isEmpty());
} }
public function testGetBagWithBagImplementingGetBag()
{
$bag = new AttributeBag();
$bag->setName('foo');
$storage = new MockArraySessionStorage();
$storage->registerBag($bag);
$this->assertSame($bag, (new Session($storage))->getBag('foo'));
}
public function testGetBagWithBagNotImplementingGetBag()
{
$data = [];
$bag = new AttributeBag();
$bag->setName('foo');
$storage = new MockArraySessionStorage();
$storage->registerBag(new SessionBagProxy($bag, $data, $usageIndex));
$this->assertSame($bag, (new Session($storage))->getBag('foo'));
}
} }

View File

@ -71,6 +71,9 @@ class RedisStore implements StoreInterface
$this->checkNotExpired($key); $this->checkNotExpired($key);
} }
/**
* {@inheritdoc}
*/
public function waitAndSave(Key $key) public function waitAndSave(Key $key)
{ {
throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this))); throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this)));

View File

@ -68,7 +68,7 @@ class MailgunTransport extends AbstractApiTransport
{ {
$headers = $email->getHeaders(); $headers = $email->getHeaders();
$html = $email->getHtmlBody(); $html = $email->getHtmlBody();
if (null !== $html) { if (null !== $html && \is_resource($html)) {
if (stream_get_meta_data($html)['seekable'] ?? false) { if (stream_get_meta_data($html)['seekable'] ?? false) {
rewind($html); rewind($html);
} }

View File

@ -71,11 +71,18 @@ class TransportTest extends TestCase
Transport::fromDsn('some://'); Transport::fromDsn('some://');
} }
public function testNoScheme()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The "//sendmail" mailer DSN must contain a transport scheme.');
Transport::fromDsn('//sendmail');
}
public function testFromInvalidDsnNoHost() public function testFromInvalidDsnNoHost()
{ {
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The "?!" mailer DSN must contain a mailer name.'); $this->expectExceptionMessage('The "file:///some/path" mailer DSN must contain a mailer name.');
Transport::fromDsn('?!'); Transport::fromDsn('file:///some/path');
} }
public function testFromInvalidTransportName() public function testFromInvalidTransportName()
@ -168,6 +175,19 @@ class TransportTest extends TestCase
$transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger); $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger);
$transport->send($message); $transport->send($message);
$message = (new Email())->from('me@me.com')->to('you@you.com')->subject('hello')->html('test');
$client = $this->createMock(HttpClientInterface::class);
$client->expects($this->once())->method('request')->with('POST', 'https://api.mailgun.net/v3/pa%24s/messages')->willReturn($response);
$transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger);
$transport->send($message);
$stream = fopen('data://text/plain,'.$message->getTextBody(), 'r');
$message = (new Email())->from('me@me.com')->to('you@you.com')->subject('hello')->html($stream);
$client = $this->createMock(HttpClientInterface::class);
$client->expects($this->once())->method('request')->with('POST', 'https://api.mailgun.net/v3/pa%24s/messages')->willReturn($response);
$transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger);
$transport->send($message);
$this->expectException(LogicException::class); $this->expectException(LogicException::class);
Transport::fromDsn('foo://mailgun'); Transport::fromDsn('foo://mailgun');
} }

View File

@ -64,6 +64,10 @@ class Transport
throw new InvalidArgumentException(sprintf('The "%s" mailer DSN is invalid.', $dsn)); throw new InvalidArgumentException(sprintf('The "%s" mailer DSN is invalid.', $dsn));
} }
if (!isset($parsedDsn['scheme'])) {
throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a transport scheme.', $dsn));
}
if (!isset($parsedDsn['host'])) { if (!isset($parsedDsn['host'])) {
throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a mailer name.', $dsn)); throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a mailer name.', $dsn));
} }

View File

@ -17,7 +17,7 @@ namespace Symfony\Component\Messenger\Exception;
* *
* @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/ */
class DelayedMessageHandlingException extends \RuntimeException implements ExceptionInterface class DelayedMessageHandlingException extends RuntimeException
{ {
private $exceptions; private $exceptions;

View File

@ -16,6 +16,6 @@ namespace Symfony\Component\Messenger\Exception;
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class MessageDecodingFailedException extends \InvalidArgumentException implements ExceptionInterface class MessageDecodingFailedException extends InvalidArgumentException
{ {
} }

View File

@ -16,6 +16,6 @@ namespace Symfony\Component\Messenger\Exception;
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class NoHandlerForMessageException extends \LogicException implements ExceptionInterface class NoHandlerForMessageException extends LogicException
{ {
} }

View File

@ -16,6 +16,6 @@ namespace Symfony\Component\Messenger\Exception;
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class UnknownSenderException extends \InvalidArgumentException implements ExceptionInterface class UnknownSenderException extends InvalidArgumentException
{ {
} }

View File

@ -0,0 +1,26 @@
<?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\Messenger\Exception;
/**
* Marker interface for exceptions to indicate that handling a message will continue to fail.
*
* If something goes wrong while handling a message that's received from a transport
* and the message should not be retried, a handler can throw such an exception.
*
* @author Tobias Schultze <http://tobion.de>
*
* @experimental in 4.3
*/
interface UnrecoverableExceptionInterface extends \Throwable
{
}

View File

@ -12,15 +12,12 @@
namespace Symfony\Component\Messenger\Exception; namespace Symfony\Component\Messenger\Exception;
/** /**
* Thrown while handling a message to indicate that handling will continue to fail. * A concrete implementation of UnrecoverableExceptionInterface that can be used directly.
*
* If something goes wrong while handling a message that's received from a transport
* and the message should not be retried, a handler can throw this exception.
* *
* @author Frederic Bouchery <frederic@bouchery.fr> * @author Frederic Bouchery <frederic@bouchery.fr>
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class UnrecoverableMessageHandlingException extends RuntimeException class UnrecoverableMessageHandlingException extends RuntimeException implements UnrecoverableExceptionInterface
{ {
} }

View File

@ -18,7 +18,7 @@ use Symfony\Component\Validator\ConstraintViolationListInterface;
* *
* @experimental in 4.3 * @experimental in 4.3
*/ */
class ValidationFailedException extends \RuntimeException implements ExceptionInterface class ValidationFailedException extends RuntimeException
{ {
private $violations; private $violations;
private $violatingMessage; private $violatingMessage;

View File

@ -112,7 +112,7 @@ final class QueuedEnvelope
public function __construct(Envelope $envelope, StackInterface $stack) public function __construct(Envelope $envelope, StackInterface $stack)
{ {
$this->envelope = $envelope; $this->envelope = $envelope->withoutAll(DispatchAfterCurrentBusStamp::class);
$this->stack = $stack; $this->stack = $stack;
} }

View File

@ -99,6 +99,53 @@ class DispatchAfterCurrentBusMiddlewareTest extends TestCase
$messageBus->dispatch($message); $messageBus->dispatch($message);
} }
public function testHandleDelayedEventFromQueue()
{
$message = new DummyMessage('Hello');
$event = new DummyEvent('Event on queue');
$middleware = new DispatchAfterCurrentBusMiddleware();
$commandHandlingMiddleware = $this->createMock(MiddlewareInterface::class);
$eventHandlingMiddleware = $this->createMock(MiddlewareInterface::class);
// This bus simulates the bus that are used when messages come back form the queue
$messageBusAfterQueue = new MessageBus([
// Create a new middleware
new DispatchAfterCurrentBusMiddleware(),
$eventHandlingMiddleware,
]);
$fakePutMessageOnQueue = $this->createMock(MiddlewareInterface::class);
$fakePutMessageOnQueue->expects($this->any())
->method('handle')
->with($this->callback(function ($envelope) use ($messageBusAfterQueue) {
// Fake putting the message on the queue
// Fake reading the queue
// Now, we add the message back to a new bus.
$messageBusAfterQueue->dispatch($envelope);
return true;
}))
->willReturnArgument(0);
$eventBus = new MessageBus([
$middleware,
$fakePutMessageOnQueue,
]);
$messageBus = new MessageBus([
$middleware,
new DispatchingMiddleware($eventBus, [
new Envelope($event, [new DispatchAfterCurrentBusStamp()]),
]),
$commandHandlingMiddleware,
]);
$this->expectHandledMessage($commandHandlingMiddleware, 0, $message);
$this->expectHandledMessage($eventHandlingMiddleware, 0, $event);
$messageBus->dispatch($message);
}
/** /**
* @param MiddlewareInterface|MockObject $handlingMiddleware * @param MiddlewareInterface|MockObject $handlingMiddleware
*/ */

View File

@ -34,9 +34,9 @@ class RetryIntegrationTest extends TestCase
$senderAndReceiver = new DummySenderAndReceiver(); $senderAndReceiver = new DummySenderAndReceiver();
$senderLocator = $this->createMock(ContainerInterface::class); $senderLocator = $this->createMock(ContainerInterface::class);
$senderLocator->method('has')->with('sender_alias')->willReturn(true); $senderLocator->method('has')->with('transportName')->willReturn(true);
$senderLocator->method('get')->with('sender_alias')->willReturn($senderAndReceiver); $senderLocator->method('get')->with('transportName')->willReturn($senderAndReceiver);
$senderLocator = new SendersLocator([DummyMessage::class => ['sender_alias']], $senderLocator); $senderLocator = new SendersLocator([DummyMessage::class => ['transportName']], $senderLocator);
$handler = new DummyMessageHandlerFailingFirstTimes(0); $handler = new DummyMessageHandlerFailingFirstTimes(0);
$throwingHandler = new DummyMessageHandlerFailingFirstTimes(1); $throwingHandler = new DummyMessageHandlerFailingFirstTimes(1);
@ -52,7 +52,7 @@ class RetryIntegrationTest extends TestCase
$envelope = new Envelope(new DummyMessage('API')); $envelope = new Envelope(new DummyMessage('API'));
$bus->dispatch($envelope); $bus->dispatch($envelope);
$worker = new Worker(['receiverName' => $senderAndReceiver], $bus, ['receiverName' => new MultiplierRetryStrategy()]); $worker = new Worker(['transportName' => $senderAndReceiver], $bus, ['transportName' => new MultiplierRetryStrategy()]);
$worker->run([], function (?Envelope $envelope) use ($worker) { $worker->run([], function (?Envelope $envelope) use ($worker) {
if (null === $envelope) { if (null === $envelope) {
$worker->stop(); $worker->stop();

View File

@ -230,4 +230,90 @@ class ConnectionTest extends TestCase
{ {
Connection::buildConfiguration('doctrine://default?new_option=woops'); Connection::buildConfiguration('doctrine://default?new_option=woops');
} }
public function testFind()
{
$queryBuilder = $this->getQueryBuilderMock();
$driverConnection = $this->getDBALConnectionMock();
$schemaSynchronizer = $this->getSchemaSynchronizerMock();
$id = 1;
$stmt = $this->getStatementMock([
'id' => $id,
'body' => '{"message":"Hi"}',
'headers' => json_encode(['type' => DummyMessage::class]),
]);
$driverConnection
->method('createQueryBuilder')
->willReturn($queryBuilder);
$queryBuilder
->method('where')
->willReturn($queryBuilder);
$queryBuilder
->method('getSQL')
->willReturn('');
$queryBuilder
->method('getParameters')
->willReturn([]);
$driverConnection
->method('prepare')
->willReturn($stmt);
$connection = new Connection([], $driverConnection, $schemaSynchronizer);
$doctrineEnvelope = $connection->find($id);
$this->assertEquals(1, $doctrineEnvelope['id']);
$this->assertEquals('{"message":"Hi"}', $doctrineEnvelope['body']);
$this->assertEquals(['type' => DummyMessage::class], $doctrineEnvelope['headers']);
}
public function testFindAll()
{
$queryBuilder = $this->getQueryBuilderMock();
$driverConnection = $this->getDBALConnectionMock();
$schemaSynchronizer = $this->getSchemaSynchronizerMock();
$message1 = [
'id' => 1,
'body' => '{"message":"Hi"}',
'headers' => json_encode(['type' => DummyMessage::class]),
];
$message2 = [
'id' => 2,
'body' => '{"message":"Hi again"}',
'headers' => json_encode(['type' => DummyMessage::class]),
];
$stmt = $this->getMockBuilder(Statement::class)
->disableOriginalConstructor()
->getMock();
$stmt->expects($this->once())
->method('fetchAll')
->willReturn([$message1, $message2]);
$driverConnection
->method('createQueryBuilder')
->willReturn($queryBuilder);
$queryBuilder
->method('where')
->willReturn($queryBuilder);
$queryBuilder
->method('getSQL')
->willReturn('');
$queryBuilder
->method('getParameters')
->willReturn([]);
$driverConnection
->method('prepare')
->willReturn($stmt);
$connection = new Connection([], $driverConnection, $schemaSynchronizer);
$doctrineEnvelopes = $connection->findAll();
$this->assertEquals(1, $doctrineEnvelopes[0]['id']);
$this->assertEquals('{"message":"Hi"}', $doctrineEnvelopes[0]['body']);
$this->assertEquals(['type' => DummyMessage::class], $doctrineEnvelopes[0]['headers']);
$this->assertEquals(2, $doctrineEnvelopes[1]['id']);
$this->assertEquals('{"message":"Hi again"}', $doctrineEnvelopes[1]['body']);
$this->assertEquals(['type' => DummyMessage::class], $doctrineEnvelopes[1]['headers']);
}
} }

View File

@ -84,7 +84,7 @@ class WorkerTest extends TestCase
public function testDispatchCausesRetry() public function testDispatchCausesRetry()
{ {
$receiver = new DummyReceiver([ $receiver = new DummyReceiver([
[new Envelope(new DummyMessage('Hello'), [new SentStamp('Some\Sender', 'sender_alias')])], [new Envelope(new DummyMessage('Hello'), [new SentStamp('Some\Sender', 'transport1')])],
]); ]);
$bus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); $bus = $this->getMockBuilder(MessageBusInterface::class)->getMock();
@ -97,7 +97,7 @@ class WorkerTest extends TestCase
$this->assertNotNull($redeliveryStamp); $this->assertNotNull($redeliveryStamp);
// retry count now at 1 // retry count now at 1
$this->assertSame(1, $redeliveryStamp->getRetryCount()); $this->assertSame(1, $redeliveryStamp->getRetryCount());
$this->assertSame('sender_alias', $redeliveryStamp->getSenderClassOrAlias()); $this->assertSame('transport1', $redeliveryStamp->getSenderClassOrAlias());
// received stamp is removed // received stamp is removed
$this->assertNull($envelope->last(ReceivedStamp::class)); $this->assertNull($envelope->last(ReceivedStamp::class));
@ -108,7 +108,7 @@ class WorkerTest extends TestCase
$retryStrategy = $this->getMockBuilder(RetryStrategyInterface::class)->getMock(); $retryStrategy = $this->getMockBuilder(RetryStrategyInterface::class)->getMock();
$retryStrategy->expects($this->once())->method('isRetryable')->willReturn(true); $retryStrategy->expects($this->once())->method('isRetryable')->willReturn(true);
$worker = new Worker(['receiver1' => $receiver], $bus, ['receiver1' => $retryStrategy]); $worker = new Worker(['transport1' => $receiver], $bus, ['transport1' => $retryStrategy]);
$worker->run([], function (?Envelope $envelope) use ($worker) { $worker->run([], function (?Envelope $envelope) use ($worker) {
// stop after the messages finish // stop after the messages finish
if (null === $envelope) { if (null === $envelope) {
@ -123,7 +123,7 @@ class WorkerTest extends TestCase
public function testDispatchCausesRejectWhenNoRetry() public function testDispatchCausesRejectWhenNoRetry()
{ {
$receiver = new DummyReceiver([ $receiver = new DummyReceiver([
[new Envelope(new DummyMessage('Hello'), [new SentStamp('Some\Sender', 'sender_alias')])], [new Envelope(new DummyMessage('Hello'), [new SentStamp('Some\Sender', 'transport1')])],
]); ]);
$bus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); $bus = $this->getMockBuilder(MessageBusInterface::class)->getMock();
@ -132,7 +132,7 @@ class WorkerTest extends TestCase
$retryStrategy = $this->getMockBuilder(RetryStrategyInterface::class)->getMock(); $retryStrategy = $this->getMockBuilder(RetryStrategyInterface::class)->getMock();
$retryStrategy->expects($this->once())->method('isRetryable')->willReturn(false); $retryStrategy->expects($this->once())->method('isRetryable')->willReturn(false);
$worker = new Worker(['receiver1' => $receiver], $bus, ['receiver1' => $retryStrategy]); $worker = new Worker(['transport1' => $receiver], $bus, ['transport1' => $retryStrategy]);
$worker->run([], function (?Envelope $envelope) use ($worker) { $worker->run([], function (?Envelope $envelope) use ($worker) {
// stop after the messages finish // stop after the messages finish
if (null === $envelope) { if (null === $envelope) {
@ -155,7 +155,7 @@ class WorkerTest extends TestCase
$retryStrategy = $this->getMockBuilder(RetryStrategyInterface::class)->getMock(); $retryStrategy = $this->getMockBuilder(RetryStrategyInterface::class)->getMock();
$retryStrategy->expects($this->never())->method('isRetryable'); $retryStrategy->expects($this->never())->method('isRetryable');
$worker = new Worker(['receiver1' => $receiver], $bus, ['receiver1' => $retryStrategy]); $worker = new Worker(['transport1' => $receiver], $bus, ['transport1' => $retryStrategy]);
$worker->run([], function (?Envelope $envelope) use ($worker) { $worker->run([], function (?Envelope $envelope) use ($worker) {
// stop after the messages finish // stop after the messages finish
if (null === $envelope) { if (null === $envelope) {

View File

@ -155,7 +155,7 @@ class Connection
return null; return null;
} }
$doctrineEnvelope['headers'] = json_decode($doctrineEnvelope['headers'], true); $doctrineEnvelope = $this->decodeEnvelopeHeaders($doctrineEnvelope);
$queryBuilder = $this->driverConnection->createQueryBuilder() $queryBuilder = $this->driverConnection->createQueryBuilder()
->update($this->configuration['table_name']) ->update($this->configuration['table_name'])
@ -238,7 +238,11 @@ class Connection
$queryBuilder->setMaxResults($limit); $queryBuilder->setMaxResults($limit);
} }
return $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters())->fetchAll(); $data = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters())->fetchAll();
return array_map(function ($doctrineEnvelope) {
return $this->decodeEnvelopeHeaders($doctrineEnvelope);
}, $data);
} }
public function find($id): ?array public function find($id): ?array
@ -254,7 +258,7 @@ class Connection
'id' => $id, 'id' => $id,
])->fetch(); ])->fetch();
return false === $data ? null : $data; return false === $data ? null : $this->decodeEnvelopeHeaders($data);
} }
private function createAvailableMessagesQueryBuilder(): QueryBuilder private function createAvailableMessagesQueryBuilder(): QueryBuilder
@ -332,4 +336,11 @@ class Connection
{ {
return $dateTime->format('Y-m-d\TH:i:s'); return $dateTime->format('Y-m-d\TH:i:s');
} }
private function decodeEnvelopeHeaders(array $doctrineEnvelope): array
{
$doctrineEnvelope['headers'] = json_decode($doctrineEnvelope['headers'], true);
return $doctrineEnvelope;
}
} }

View File

@ -17,13 +17,11 @@ use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent;
use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent; use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent;
use Symfony\Component\Messenger\Event\WorkerStoppedEvent; use Symfony\Component\Messenger\Event\WorkerStoppedEvent;
use Symfony\Component\Messenger\Exception\HandlerFailedException; use Symfony\Component\Messenger\Exception\HandlerFailedException;
use Symfony\Component\Messenger\Exception\LogicException; use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\Retry\RetryStrategyInterface; use Symfony\Component\Messenger\Retry\RetryStrategyInterface;
use Symfony\Component\Messenger\Stamp\DelayStamp; use Symfony\Component\Messenger\Stamp\DelayStamp;
use Symfony\Component\Messenger\Stamp\ReceivedStamp; use Symfony\Component\Messenger\Stamp\ReceivedStamp;
use Symfony\Component\Messenger\Stamp\RedeliveryStamp; use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
use Symfony\Component\Messenger\Stamp\SentStamp;
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface; use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@ -150,7 +148,7 @@ class Worker implements WorkerInterface
// add the delay and retry stamp info + remove ReceivedStamp // add the delay and retry stamp info + remove ReceivedStamp
$retryEnvelope = $envelope->with(new DelayStamp($delay)) $retryEnvelope = $envelope->with(new DelayStamp($delay))
->with(new RedeliveryStamp($retryCount, $this->getSenderClassOrAlias($envelope))) ->with(new RedeliveryStamp($retryCount, $transportName))
->withoutAll(ReceivedStamp::class); ->withoutAll(ReceivedStamp::class);
// re-send the message // re-send the message
@ -193,32 +191,10 @@ class Worker implements WorkerInterface
private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInterface $retryStrategy): bool private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInterface $retryStrategy): bool
{ {
if ($e instanceof UnrecoverableMessageHandlingException) { if ($e instanceof UnrecoverableExceptionInterface) {
return false;
}
$sentStamp = $envelope->last(SentStamp::class);
if (null === $sentStamp) {
if (null !== $this->logger) {
$this->logger->warning('Message will not be retried because the SentStamp is missing and so the target sender cannot be determined.');
}
return false; return false;
} }
return $retryStrategy->isRetryable($envelope); return $retryStrategy->isRetryable($envelope);
} }
private function getSenderClassOrAlias(Envelope $envelope): string
{
/** @var SentStamp|null $sentStamp */
$sentStamp = $envelope->last(SentStamp::class);
if (null === $sentStamp) {
// should not happen, because of the check in shouldRetry()
throw new LogicException('Could not find SentStamp.');
}
return $sentStamp->getSenderAlias() ?: $sentStamp->getSenderClass();
}
} }

View File

@ -24,13 +24,6 @@ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface, Prop
{ {
private $propertyInfoExtractor; private $propertyInfoExtractor;
private $cacheItemPool; private $cacheItemPool;
/**
* A cache of property information, first keyed by the method called and
* then by the serialized method arguments.
*
* @var array
*/
private $arrayCache = []; private $arrayCache = [];
public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, CacheItemPoolInterface $cacheItemPool) public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, CacheItemPoolInterface $cacheItemPool)
@ -110,34 +103,22 @@ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface, Prop
} }
// Calling rawurlencode escapes special characters not allowed in PSR-6's keys // Calling rawurlencode escapes special characters not allowed in PSR-6's keys
$encodedMethod = rawurlencode($method); $key = rawurlencode($method.'.'.$serializedArguments);
if (\array_key_exists($encodedMethod, $this->arrayCache) && \array_key_exists($serializedArguments, $this->arrayCache[$encodedMethod])) {
return $this->arrayCache[$encodedMethod][$serializedArguments]; if (\array_key_exists($key, $this->arrayCache)) {
return $this->arrayCache[$key];
} }
$item = $this->cacheItemPool->getItem($encodedMethod); $item = $this->cacheItemPool->getItem($key);
$data = $item->get();
if ($item->isHit()) { if ($item->isHit()) {
$this->arrayCache[$encodedMethod] = $data[$encodedMethod]; return $this->arrayCache[$key] = $item->get();
// Only match if the specific arguments have been cached.
if (\array_key_exists($serializedArguments, $data[$encodedMethod])) {
return $this->arrayCache[$encodedMethod][$serializedArguments];
}
}
// It's possible that the method has been called, but with different
// arguments, in which case $data will already be initialized.
if (!$data) {
$data = [];
} }
$value = $this->propertyInfoExtractor->{$method}(...$arguments); $value = $this->propertyInfoExtractor->{$method}(...$arguments);
$data[$encodedMethod][$serializedArguments] = $value; $item->set($value);
$this->arrayCache[$encodedMethod][$serializedArguments] = $value;
$item->set($data);
$this->cacheItemPool->save($item); $this->cacheItemPool->save($item);
return $this->arrayCache[$encodedMethod][$serializedArguments]; return $this->arrayCache[$key] = $value;
} }
} }

View File

@ -51,10 +51,6 @@ class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingE
return true; return true;
} }
if (class_exists('ParagonIE_Sodium_Compat') && method_exists('ParagonIE_Sodium_Compat', 'crypto_pwhash_is_available')) {
return \ParagonIE_Sodium_Compat::crypto_pwhash_is_available();
}
return \function_exists('sodium_crypto_pwhash_str') || \extension_loaded('libsodium'); return \function_exists('sodium_crypto_pwhash_str') || \extension_loaded('libsodium');
} }

View File

@ -33,8 +33,8 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti
$opsLimit = $opsLimit ?? max(6, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE : 6); $opsLimit = $opsLimit ?? max(6, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE : 6);
$memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); $memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024);
if (2 > $opsLimit) { if (3 > $opsLimit) {
throw new \InvalidArgumentException('$opsLimit must be 2 or greater.'); throw new \InvalidArgumentException('$opsLimit must be 3 or greater.');
} }
if (10 * 1024 > $memLimit) { if (10 * 1024 > $memLimit) {

View File

@ -37,8 +37,8 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti
$this->opsLimit = $opsLimit ?? max(6, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE : 6); $this->opsLimit = $opsLimit ?? max(6, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE : 6);
$this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 2014); $this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 2014);
if (2 > $this->opsLimit) { if (3 > $this->opsLimit) {
throw new \InvalidArgumentException('$opsLimit must be 2 or greater.'); throw new \InvalidArgumentException('$opsLimit must be 3 or greater.');
} }
if (10 * 1024 > $this->memLimit) { if (10 * 1024 > $this->memLimit) {
@ -48,15 +48,7 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti
public static function isSupported(): bool public static function isSupported(): bool
{ {
if (\extension_loaded('libsodium') || \function_exists('sodium_crypto_pwhash_str')) { return \function_exists('sodium_crypto_pwhash_str_needs_rehash') || \function_exists('Sodium\crypto_pwhash_str_needs_rehash');
return true;
}
if (class_exists('ParagonIE_Sodium_Compat') && method_exists('ParagonIE_Sodium_Compat', 'crypto_pwhash_is_available')) {
return \ParagonIE_Sodium_Compat::crypto_pwhash_is_available();
}
return false;
} }
/** /**

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Validator\Constraints;
use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\LogicException;
/** /**
* Used for the comparison of values. * Used for the comparison of values.
@ -46,7 +47,7 @@ abstract class AbstractComparison extends Constraint
} }
if (isset($options['propertyPath']) && !class_exists(PropertyAccess::class)) { if (isset($options['propertyPath']) && !class_exists(PropertyAccess::class)) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', \get_class($this))); throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', \get_class($this)));
} }
} }

View File

@ -31,7 +31,7 @@ It also provides a few improvements over `var_export()`/`serialize()`:
Resources Resources
--------- ---------
* [Documentation](https://symfony.com/doc/current/components/var_exporter/introduction.html) * [Documentation](https://symfony.com/doc/current/components/var_exporter.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [send Pull Requests](https://github.com/symfony/symfony/pulls)

View File

@ -41,6 +41,17 @@ switch ($vars['REQUEST_URI']) {
ob_start('ob_gzhandler'); ob_start('ob_gzhandler');
break; break;
case '/103':
header('HTTP/1.1 103 Early Hints');
header('Link: </style.css>; rel=preload; as=style', false);
header('Link: </script.js>; rel=preload; as=script', false);
echo "HTTP/1.1 200 OK\r\n";
echo "Date: Fri, 26 May 2017 10:02:11 GMT\r\n";
echo "Content-Length: 13\r\n";
echo "\r\n";
echo 'Here the body';
exit;
case '/404': case '/404':
header('Content-Type: application/json', true, 404); header('Content-Type: application/json', true, 404);
break; break;

View File

@ -721,6 +721,15 @@ abstract class HttpClientTestCase extends TestCase
$this->assertSame('/?a=a&b=b', $body['REQUEST_URI']); $this->assertSame('/?a=a&b=b', $body['REQUEST_URI']);
} }
public function testInformationalResponse()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/103');
$this->assertSame('Here the body', $response->getContent());
$this->assertSame(200, $response->getStatusCode());
}
/** /**
* @requires extension zlib * @requires extension zlib
*/ */