Merge branch '5.2' into 5.x

* 5.2:
  [HttpClient][PHPDoc] Fix 2 remaining return mixed
  [Cache] [FrameworkBundle] Fix logging for TagAwareAdapter
  [Route] Better inline requirements and defaults parsing
  Simplified condition and removed unused code from AbstractSessionListener::onKernelRequest
  [PhpUnitBridge] Fix phpunit symlink on Windows
  [Yaml] Fixed infinite loop when parser goes through an additional and invalid closing tag
  [Form] Fix 'invalid_message' use in multiple ChoiceType
This commit is contained in:
Alexander M. Turek 2021-04-12 01:07:08 +02:00
commit 364742f18c
18 changed files with 139 additions and 27 deletions

View File

@ -148,8 +148,8 @@ if ('disabled' === $getEnvVar('SYMFONY_DEPRECATIONS_HELPER')) {
}
$COMPOSER = file_exists($COMPOSER = $oldPwd.'/composer.phar')
|| ($COMPOSER = rtrim('\\' === \DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar 2> /dev/null`))
|| ($COMPOSER = rtrim('\\' === \DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer`) : `which composer 2> /dev/null`))
|| ($COMPOSER = rtrim('\\' === \DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar 2> NUL`) : `which composer.phar 2> /dev/null`))
|| ($COMPOSER = rtrim('\\' === \DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer 2> NUL`) : `which composer 2> /dev/null`))
|| file_exists($COMPOSER = rtrim('\\' === \DIRECTORY_SEPARATOR ? `git rev-parse --show-toplevel 2> NUL` : `git rev-parse --show-toplevel 2> /dev/null`).\DIRECTORY_SEPARATOR.'composer.phar')
? ('#!/usr/bin/env php' === file_get_contents($COMPOSER, false, null, 0, 18) ? $PHP : '').' '.escapeshellarg($COMPOSER) // detect shell wrappers by looking at the shebang
: 'composer';
@ -176,9 +176,9 @@ if (!file_exists("$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit") || $configurationH
@mkdir($PHPUNIT_DIR, 0777, true);
chdir($PHPUNIT_DIR);
if (file_exists("$PHPUNIT_VERSION_DIR")) {
passthru(sprintf('\\' === \DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s > NUL' : 'rm -rf %s', "$PHPUNIT_VERSION_DIR.old"));
passthru(sprintf('\\' === \DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s 2> NUL' : 'rm -rf %s', escapeshellarg("$PHPUNIT_VERSION_DIR.old")));
rename("$PHPUNIT_VERSION_DIR", "$PHPUNIT_VERSION_DIR.old");
passthru(sprintf('\\' === \DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s' : 'rm -rf %s', "$PHPUNIT_VERSION_DIR.old"));
passthru(sprintf('\\' === \DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s' : 'rm -rf %s', escapeshellarg("$PHPUNIT_VERSION_DIR.old")));
}
$info = [];
@ -307,10 +307,15 @@ EOPHP
// This is useful for static analytics tools such as PHPStan having to load PHPUnit's classes
// and for other testing libraries such as Behat using PHPUnit's assertions.
chdir($PHPUNIT_DIR);
if (file_exists('phpunit')) {
@unlink('phpunit');
if ('\\' === \DIRECTORY_SEPARATOR) {
passthru('rmdir /S /Q phpunit 2> NUL');
passthru(sprintf('mklink /j phpunit %s > NUL 2>&1', escapeshellarg($PHPUNIT_VERSION_DIR)));
} else {
if (file_exists('phpunit')) {
@unlink('phpunit');
}
@symlink($PHPUNIT_VERSION_DIR, 'phpunit');
}
@symlink($PHPUNIT_VERSION_DIR, 'phpunit');
chdir($oldPwd);
if ($PHPUNIT_VERSION < 8.0) {

View File

@ -2109,6 +2109,12 @@ class FrameworkExtension extends Extension
->setPublic($pool['public'])
;
if (method_exists(TagAwareAdapter::class, 'setLogger')) {
$container
->getDefinition($name)
->addMethodCall('setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]);
}
$pool['name'] = $tagAwareId = $name;
$pool['public'] = false;
$name = '.'.$name.'.inner';

View File

@ -35,6 +35,11 @@ $container->loadFromExtension('framework', [
'redis://foo' => 'cache.adapter.redis',
],
],
'cache.ccc' => [
'adapter' => 'cache.adapter.array',
'default_lifetime' => 410,
'tags' => true,
],
'cache.redis_tag_aware.foo' => [
'adapter' => 'cache.adapter.redis_tag_aware',
],

View File

@ -18,6 +18,7 @@
<framework:adapter name="cache.adapter.filesystem" />
<framework:adapter name="cache.adapter.redis" provider="redis://foo" />
</framework:pool>
<framework:pool name="cache.ccc" adapter="cache.adapter.array" default-lifetime="410" tags="true" />
<framework:pool name="cache.redis_tag_aware.foo" adapter="cache.adapter.redis_tag_aware" />
<framework:pool name="cache.redis_tag_aware.foo2" tags="true" adapter="cache.adapter.redis_tag_aware" />
<framework:pool name="cache.redis_tag_aware.bar" adapter="cache.redis_tag_aware.foo" />

View File

@ -25,6 +25,10 @@ framework:
- cache.adapter.array
- cache.adapter.filesystem
- {name: cache.adapter.redis, provider: 'redis://foo'}
cache.ccc:
adapter: cache.adapter.array
default_lifetime: 410
tags: true
cache.redis_tag_aware.foo:
adapter: cache.adapter.redis_tag_aware
cache.redis_tag_aware.foo2:

View File

@ -30,6 +30,7 @@ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
@ -1537,6 +1538,17 @@ abstract class FrameworkExtensionTest extends TestCase
12,
];
$this->assertEquals($expected, $chain->getArguments());
// Test "tags: true" wrapping logic
$tagAwareDefinition = $container->getDefinition('cache.ccc');
$this->assertSame(TagAwareAdapter::class, $tagAwareDefinition->getClass());
$this->assertCachePoolServiceDefinitionIsCreated($container, (string) $tagAwareDefinition->getArgument(0), 'cache.adapter.array', 410);
if (method_exists(TagAwareAdapter::class, 'setLogger')) {
$this->assertEquals([
['setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]],
], $tagAwareDefinition->getMethodCalls());
}
}
public function testRedisTagAwareAdapter()
@ -1948,6 +1960,9 @@ abstract class FrameworkExtensionTest extends TestCase
case 'cache.adapter.redis':
$this->assertSame(RedisAdapter::class, $parentDefinition->getClass());
break;
case 'cache.adapter.array':
$this->assertSame(ArrayAdapter::class, $parentDefinition->getClass());
break;
default:
$this->fail('Unresolved adapter: '.$adapter);
}

View File

@ -13,6 +13,8 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
@ -23,11 +25,12 @@ use Symfony\Contracts\Cache\TagAwareCacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface
class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface
{
public const TAGS_PREFIX = "\0tags\0";
use ContractsTrait;
use LoggerAwareTrait;
use ProxyTrait;
private $deferred = [];

View File

@ -82,7 +82,7 @@ final class LockRegistry
public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null)
{
$key = self::$files ? crc32($item->getKey()) % \count(self::$files) : -1;
$key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1;
if ($key < 0 || (self::$lockedFiles[$key] ?? false) || !$lock = self::open($key)) {
return $callback($item, $save);

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Cache\Tests\Adapter;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
@ -197,6 +198,20 @@ class TagAwareAdapterTest extends AdapterTestCase
$this->assertFalse($item->isHit());
}
public function testLog()
{
$logger = $this->createMock(LoggerInterface::class);
$logger
->expects($this->atLeastOnce())
->method($this->anything());
$cache = new TagAwareAdapter(new ArrayAdapter());
$cache->setLogger($logger);
// Computing will produce at least one log
$cache->get('foo', static function (): string { return 'ccc'; });
}
/**
* @return MockObject|PruneableCacheInterface
*/

View File

@ -187,13 +187,14 @@ class ChoiceType extends AbstractType
}
if ($options['multiple']) {
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$unknownValues) {
$messageTemplate = $options['invalid_message'] ?? 'The value {{ value }} is not valid.';
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$unknownValues, $messageTemplate) {
// Throw exception if unknown values were submitted
if (\count($unknownValues) > 0) {
$form = $event->getForm();
$clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : \gettype($form->getViewData());
$messageTemplate = 'The value {{ value }} is not valid.';
$clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : (\is_array($form->getViewData()) ? implode('", "', array_keys($unknownValues)) : \gettype($form->getViewData()));
if (null !== $this->translator) {
$message = $this->translator->trans($messageTemplate, ['{{ value }}' => $clientDataAsString], 'validators');
@ -201,7 +202,7 @@ class ChoiceType extends AbstractType
$message = strtr($messageTemplate, ['{{ value }}' => $clientDataAsString]);
}
$form->addError(new FormError($message, $messageTemplate, ['{{ value }}' => $clientDataAsString], null, new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', implode('", "', array_keys($unknownValues))))));
$form->addError(new FormError($message, $messageTemplate, ['{{ value }}' => $clientDataAsString], null, new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', $clientDataAsString))));
}
});

View File

@ -1861,6 +1861,32 @@ class ChoiceTypeTest extends BaseTypeTest
$this->assertSame('name[]', $view->vars['full_name']);
}
public function testInvalidMessageAwarenessForMultiple()
{
$form = $this->factory->create(static::TESTED_TYPE, null, [
'multiple' => true,
'expanded' => false,
'choices' => $this->choices,
'invalid_message' => 'You are not able to use value "{{ value }}"',
]);
$form->submit(['My invalid choice']);
$this->assertEquals("ERROR: You are not able to use value \"My invalid choice\"\n", (string) $form->getErrors(true));
}
public function testInvalidMessageAwarenessForMultipleWithoutScalarOrArrayViewData()
{
$form = $this->factory->create(static::TESTED_TYPE, null, [
'multiple' => true,
'expanded' => false,
'choices' => $this->choices,
'invalid_message' => 'You are not able to use value "{{ value }}"',
]);
$form->submit(new \stdClass());
$this->assertEquals("ERROR: You are not able to use value \"stdClass\"\n", (string) $form->getErrors(true));
}
// https://github.com/symfony/symfony/issues/3298
public function testInitializeWithEmptyChoices()
{

View File

@ -53,10 +53,8 @@ final class HttplugPromise implements HttplugPromiseInterface
/**
* {@inheritdoc}
*
* @return Psr7ResponseInterface|mixed
*/
public function wait($unwrap = true)
public function wait($unwrap = true): ?Psr7ResponseInterface
{
$result = $this->promise->wait($unwrap);

View File

@ -56,14 +56,13 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
return;
}
$session = null;
$request = $event->getRequest();
if (!$request->hasSession()) {
$sess = null;
$request->setSessionFactory(function () use (&$sess) { return $sess ?? $sess = $this->getSession(); });
}
$session = $session ?? ($this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : null);
$session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : null;
$this->sessionUsageStack[] = $session instanceof Session ? $session->getUsageIndex() : 0;
}

View File

@ -539,15 +539,15 @@ class Route implements \Serializable
return $pattern;
}
return preg_replace_callback('#\{(!?\w++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
if (isset($m[3][0])) {
$this->setDefault($m[1], '?' !== $m[3] ? substr($m[3], 1) : null);
return preg_replace_callback('#\{(!?)(\w++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
if (isset($m[4][0])) {
$this->setDefault($m[2], '?' !== $m[4] ? substr($m[4], 1) : null);
}
if (isset($m[2][0])) {
$this->setRequirement($m[1], substr($m[2], 1, -1));
if (isset($m[3][0])) {
$this->setRequirement($m[2], substr($m[3], 1, -1));
}
return '{'.$m[1].'}';
return '{'.$m[1].$m[2].'}';
}, $pattern);
}

View File

@ -50,6 +50,14 @@ class RouteTest extends TestCase
$this->assertEquals($route, $route->setPath(''), '->setPath() implements a fluent interface');
$route->setPath('//path');
$this->assertEquals('/path', $route->getPath(), '->setPath() does not allow two slashes "//" at the beginning of the path as it would be confused with a network path when generating the path from the route');
$route->setPath('/path/{!foo}');
$this->assertEquals('/path/{!foo}', $route->getPath(), '->setPath() keeps ! to pass important params');
$route->setPath('/path/{bar<\w++>}');
$this->assertEquals('/path/{bar}', $route->getPath(), '->setPath() removes inline requirements');
$route->setPath('/path/{foo?value}');
$this->assertEquals('/path/{foo}', $route->getPath(), '->setPath() removes inline defaults');
$route->setPath('/path/{!bar<\d+>?value}');
$this->assertEquals('/path/{!bar}', $route->getPath(), '->setPath() removes all inline settings');
}
public function testOptions()
@ -221,17 +229,20 @@ class RouteTest extends TestCase
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', null), new Route('/foo/{bar?}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', 'baz'), new Route('/foo/{bar?baz}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', 'baz<buz>'), new Route('/foo/{bar?baz<buz>}'));
$this->assertEquals((new Route('/foo/{!bar}'))->setDefault('!bar', 'baz<buz>'), new Route('/foo/{!bar?baz<buz>}'));
$this->assertEquals((new Route('/foo/{!bar}'))->setDefault('bar', 'baz<buz>'), new Route('/foo/{!bar?baz<buz>}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', 'baz'), new Route('/foo/{bar?}', ['bar' => 'baz']));
$this->assertEquals((new Route('/foo/{bar}'))->setRequirement('bar', '.*'), new Route('/foo/{bar<.*>}'));
$this->assertEquals((new Route('/foo/{bar}'))->setRequirement('bar', '>'), new Route('/foo/{bar<>>}'));
$this->assertEquals((new Route('/foo/{bar}'))->setRequirement('bar', '\d+'), new Route('/foo/{bar<.*>}', [], ['bar' => '\d+']));
$this->assertEquals((new Route('/foo/{bar}'))->setRequirement('bar', '[a-z]{2}'), new Route('/foo/{bar<[a-z]{2}>}'));
$this->assertEquals((new Route('/foo/{!bar}'))->setRequirement('bar', '\d+'), new Route('/foo/{!bar<\d+>}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', null)->setRequirement('bar', '.*'), new Route('/foo/{bar<.*>?}'));
$this->assertEquals((new Route('/foo/{bar}'))->setDefault('bar', '<>')->setRequirement('bar', '>'), new Route('/foo/{bar<>>?<>}'));
$this->assertEquals((new Route('/{foo}/{!bar}'))->setDefaults(['bar' => '<>', 'foo' => '\\'])->setRequirements(['bar' => '\\', 'foo' => '.']), new Route('/{foo<.>?\}/{!bar<\>?<>}'));
$this->assertEquals((new Route('/'))->setHost('{bar}')->setDefault('bar', null), (new Route('/'))->setHost('{bar?}'));
$this->assertEquals((new Route('/'))->setHost('{bar}')->setDefault('bar', 'baz'), (new Route('/'))->setHost('{bar?baz}'));
$this->assertEquals((new Route('/'))->setHost('{bar}')->setDefault('bar', 'baz<buz>'), (new Route('/'))->setHost('{bar?baz<buz>}'));

View File

@ -1233,6 +1233,10 @@ class Parser
$offset = $cursor;
$cursor += strcspn($this->currentLine, '[]{},: ', $cursor);
if ($cursor === $offset) {
throw new ParseException('Malformed unquoted YAML string.');
}
return substr($this->currentLine, $offset, $cursor - $offset);
}

View File

@ -2675,6 +2675,25 @@ YAML;
);
}
public function testThrowExceptionIfInvalidAdditionalClosingTagOccurs()
{
$yaml = '{
"object": {
"array": [
"a",
"b",
"c"
]
],
}
}';
$this->expectException(ParseException::class);
$this->expectExceptionMessage('Malformed unquoted YAML string at line 8 (near " ],").');
$this->parser->parse($yaml);
}
public function testWhitespaceAtEndOfLine()
{
$yaml = "\nfoo:\n arguments: [ '@bar' ] \n";

View File

@ -102,8 +102,8 @@ interface ResponseInterface
*
* Other info SHOULD be named after curl_getinfo()'s associative return value.
*
* @return array|mixed An array of all available info, or one of them when $type is
* provided, or null when an unsupported type is requested
* @return mixed An array of all available info, or one of them when $type is
* provided, or null when an unsupported type is requested
*/
public function getInfo(string $type = null);
}