Merge branch '4.4'

* 4.4: (24 commits)
  [Console] Command::execute() should always return int - deprecate returning null
  [FrameworkBundle] Fix wrong returned status code in ConfigDebugCommand
  [AnnotationCacheWarmer] add RedirectController to annotation cache
  [WebProfilerBundle] Try to display the most useful panel by default
  Add note about deprecating the XmlEncoder::TYPE_CASE_ATTRIBUTES constant in the upgrade guide
  fix merge
  [DI] add tests loading calls with returns-clone
  [DI] dont mandate a class on inline services with a factory
  Fixed Redis Sentinel usage when only one Sentinel specified
  [EventDispatcher] Added tests for aliased events.
  Sync Twig templateExists behaviors
  Fix the :only-of-type pseudo class selector
  Deprecate the XmlEncoder::TYPE_CASE_ATTRIBUTES constant
  [Mailer] Tweak some code
  [Serializer] Add CsvEncoder tests for PHP 7.4
  Copy phpunit.xsd to a predictable path
  [WebserverBundle] Remove duplicated deprecation message
  remove duplicated test
  [Security/Http] fix parsing X509 emailAddress
  [FrameworkBundle] conflict with VarDumper < 4.4
  ...
This commit is contained in:
Nicolas Grekas 2019-10-02 17:00:37 +02:00
commit 3ee9dbd17b
80 changed files with 698 additions and 152 deletions

View File

@ -10,6 +10,7 @@ Console
-------
* Deprecated finding hidden commands using an abbreviation, use the full name instead
* Deprecated returning `null` from `Command::execute()`, return `0` instead
Debug
-----
@ -219,6 +220,11 @@ Security
) {}
```
Serializer
----------
* Deprecated the `XmlEncoder::TYPE_CASE_ATTRIBUTES` constant. Use `XmlEncoder::TYPE_CAST_ATTRIBUTES` instead.
Stopwatch
---------

View File

@ -37,6 +37,7 @@ Console
* Removed the `getHorizontalBorderChar()` method in favor of the `getBorderChars()` method in `TableStyle`.
* Removed the `setVerticalBorderChar()` method in favor of the `setVerticalBorderChars()` method in `TableStyle`.
* Removed the `getVerticalBorderChar()` method in favor of the `getBorderChars()` method in `TableStyle`.
* Removed support for returning `null` from `Command::execute()`, return `0` instead
* The `ProcessHelper::run()` method takes the command as an array of arguments.
Before:
@ -536,6 +537,11 @@ Serializer
* The `AbstractNormalizer::handleCircularReference()` method has two new `$format` and `$context` arguments.
* Removed support for instantiating a `DataUriNormalizer` with a default MIME type guesser when the `symfony/mime` component isn't installed.
Serializer
----------
* Removed the `XmlEncoder::TYPE_CASE_ATTRIBUTES` constant. Use `XmlEncoder::TYPE_CAST_ATTRIBUTES` instead.
Stopwatch
---------

View File

@ -116,6 +116,7 @@ if (!file_exists("$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit") || $configurationH
passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s': 'rm -rf %s', "$PHPUNIT_VERSION_DIR.old"));
}
passthru("$COMPOSER create-project --no-install --prefer-dist --no-scripts --no-plugins --no-progress --ansi phpunit/phpunit $PHPUNIT_VERSION_DIR \"$PHPUNIT_VERSION.*\"");
@copy("$PHPUNIT_VERSION_DIR/phpunit.xsd", 'phpunit.xsd');
chdir("$PHPUNIT_VERSION_DIR");
if ($SYMFONY_PHPUNIT_REMOVE) {
passthru("$COMPOSER remove --no-update ".$SYMFONY_PHPUNIT_REMOVE);

View File

@ -97,12 +97,16 @@ EOF
switch ($input->getOption('format')) {
case 'text':
return $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter);
$name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter);
break;
case 'json':
return $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter);
$name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter);
break;
default:
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
}
return 0;
}
private function displayPathsText(SymfonyStyle $io, string $name)

View File

@ -54,7 +54,7 @@ EOT
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@ -100,6 +100,8 @@ EOT
}
$io->table([], $rows);
return 0;
}
private static function formatPath(string $path, string $baseDir): string

View File

@ -72,7 +72,7 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$fs = $this->filesystem;
$io = new SymfonyStyle($input, $output);
@ -175,6 +175,8 @@ EOF
}
$io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
return 0;
}
private function warmup(string $warmupDir, string $realCacheDir, bool $enableOptionalWarmers = true)

View File

@ -60,7 +60,7 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$kernel = $this->getApplication()->getKernel();
@ -99,5 +99,7 @@ EOF
}
$io->success('Cache was successfully cleared.');
return 0;
}
}

View File

@ -59,7 +59,7 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$pool = $input->getArgument('pool');
@ -69,7 +69,7 @@ EOF
if (!$cachePool->hasItem($key)) {
$io->note(sprintf('Cache item "%s" does not exist in cache pool "%s".', $key, $pool));
return;
return 0;
}
if (!$cachePool->deleteItem($key)) {
@ -77,5 +77,7 @@ EOF
}
$io->success(sprintf('Cache item "%s" was successfully deleted.', $key));
return 0;
}
}

View File

@ -51,12 +51,14 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->table(['Pool name'], array_map(function ($pool) {
return [$pool];
}, $this->poolNames));
return 0;
}
}

View File

@ -57,7 +57,7 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@ -67,5 +67,7 @@ EOF
}
$io->success('Successfully pruned cache pool(s).');
return 0;
}
}

View File

@ -66,7 +66,7 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@ -80,5 +80,7 @@ EOF
$this->cacheWarmer->warmUp($kernel->getContainer()->getParameter('kernel.cache_dir'));
$io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully warmed.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
return 0;
}
}

View File

@ -63,7 +63,7 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $io->getErrorStyle();
@ -73,7 +73,7 @@ EOF
$errorIo->comment('Provide the name of a bundle as the first argument of this command to dump its configuration. (e.g. <comment>debug:config FrameworkBundle</comment>)');
$errorIo->comment('For dumping a specific option, add its path as the second argument of this command. (e.g. <comment>debug:config FrameworkBundle serializer</comment> to dump the <comment>framework.serializer</comment> configuration)');
return;
return 0;
}
$extension = $this->findExtension($name);
@ -101,7 +101,7 @@ EOF
$io->writeln(Yaml::dump([$extensionAlias => $config], 10));
return;
return 0;
}
try {
@ -109,12 +109,14 @@ EOF
} catch (LogicException $e) {
$errorIo->error($e->getMessage());
return;
return 1;
}
$io->title(sprintf('Current configuration for "%s.%s"', $extensionAlias, $path));
$io->writeln(Yaml::dump($config, 10));
return 0;
}
private function compileContainer(): ContainerBuilder

View File

@ -113,7 +113,7 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $io->getErrorStyle();
@ -179,6 +179,8 @@ EOF
$errorIo->comment('To search for a specific service, re-run this command with a search term. (e.g. <comment>debug:container log</comment>)');
}
}
return 0;
}
/**

View File

@ -69,7 +69,7 @@ EOF
*
* @throws \LogicException
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@ -78,7 +78,7 @@ EOF
if (!$this->dispatcher->hasListeners($event)) {
$io->getErrorStyle()->warning(sprintf('The event "%s" does not have any registered listeners.', $event));
return;
return 0;
}
$options = ['event' => $event];
@ -89,5 +89,7 @@ EOF
$options['raw_text'] = $input->getOption('raw');
$options['output'] = $io;
$helper->describe($io, $this->dispatcher, $options);
return 0;
}
}

View File

@ -73,7 +73,7 @@ EOF
*
* @throws InvalidArgumentException When route does not exist
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
@ -105,6 +105,8 @@ EOF
'output' => $io,
]);
}
return 0;
}
private function findRouteNameContaining(string $name, RouteCollection $routes): array

View File

@ -117,7 +117,7 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@ -191,7 +191,7 @@ EOF
$io->getErrorStyle()->warning($outputMessage);
return;
return 0;
}
// Load the fallback catalogues
@ -240,6 +240,8 @@ EOF
}
$io->table($headers, $rows);
return 0;
}
private function formatState(int $state): string

View File

@ -59,7 +59,7 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$container = $this->getApplication()->getKernel()->getContainer();
$serviceId = $input->getArgument('name');
@ -97,5 +97,7 @@ EOF
],
];
$output->writeln($dumper->dump($workflow->getDefinition(), $marking, $options));
return 0;
}
}

View File

@ -36,7 +36,7 @@
<service id="annotations.cache_warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\AnnotationsCacheWarmer">
<argument type="service" id="annotations.reader" />
<argument>%kernel.cache_dir%/annotations.php</argument>
<argument>#^Symfony\\(?:Component\\HttpKernel\\|Bundle\\FrameworkBundle\\Controller\\(?!AbstractController$|Controller$))#</argument>
<argument>#^Symfony\\(?:Component\\HttpKernel\\|Bundle\\FrameworkBundle\\Controller\\(?!.*Controller$))#</argument>
<argument>%kernel.debug%</argument>
</service>

View File

@ -83,6 +83,7 @@
"symfony/twig-bridge": "<4.4",
"symfony/twig-bundle": "<4.4",
"symfony/validator": "<4.4",
"symfony/var-dumper": "<4.4",
"symfony/workflow": "<4.4"
},
"suggest": {

View File

@ -17,6 +17,8 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag;
use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector;
use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@ -76,7 +78,7 @@ class ProfilerController
$this->cspHandler->disableCsp();
}
$panel = $request->query->get('panel', 'request');
$panel = $request->query->get('panel');
$page = $request->query->get('page', 'home');
if ('latest' === $token && $latest = current($this->profiler->find(null, null, 1, null, null, null))) {
@ -87,6 +89,22 @@ class ProfilerController
return new Response($this->twig->render('@WebProfiler/Profiler/info.html.twig', ['about' => 'no_token', 'token' => $token, 'request' => $request]), 200, ['Content-Type' => 'text/html']);
}
if (null === $panel) {
$panel = 'request';
foreach ($profile->getCollectors() as $collector) {
if ($collector instanceof ExceptionDataCollector && $collector->hasException()) {
$panel = $collector->getName();
break;
}
if ($collector instanceof DumpDataCollector && $collector->getDumpsCount() > 0) {
$panel = $collector->getName();
}
}
}
if (!$profile->hasCollector($panel)) {
throw new NotFoundHttpException(sprintf('Panel "%s" is not available for token "%s".', $panel, $token));
}

View File

@ -267,7 +267,7 @@
if (request.profilerUrl) {
profilerCell.textContent = '';
var profilerLink = document.createElement('a');
profilerLink.setAttribute('href', request.statusCode < 400 ? request.profilerUrl : request.profilerUrl + '?panel=exception');
profilerLink.setAttribute('href', request.profilerUrl);
profilerLink.textContent = request.profile;
profilerCell.appendChild(profilerLink);
}

View File

@ -15,8 +15,16 @@ use PHPUnit\Framework\TestCase;
use Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController;
use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector;
use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector;
use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Twig\Environment;
use Twig\Loader\LoaderInterface;
use Twig\Loader\SourceContextLoaderInterface;
class ProfilerControllerTest extends TestCase
{
@ -185,7 +193,97 @@ class ProfilerControllerTest extends TestCase
];
}
private function createController($profiler, $twig, $withCSP): ProfilerController
/**
* @dataProvider defaultPanelProvider
*/
public function testDefaultPanel(string $expectedPanel, Profile $profile)
{
$profiler = $this->createMock(Profiler::class);
$profiler
->expects($this->atLeastOnce())
->method('loadProfile')
->with($profile->getToken())
->willReturn($profile);
$profiler
->expects($this->atLeastOnce())
->method('has')
->with($this->logicalXor($collectorsNames = array_keys($profile->getCollectors())))
->willReturn(true);
$expectedTemplate = 'expected_template.html.twig';
if (Environment::MAJOR_VERSION > 1) {
$loader = $this->createMock(LoaderInterface::class);
$loader
->expects($this->atLeastOnce())
->method('exists')
->with($this->logicalXor($expectedTemplate, 'other_template.html.twig'))
->willReturn(true);
} else {
$loader = $this->createMock(SourceContextLoaderInterface::class);
}
$twig = $this->createMock(Environment::class);
$twig
->expects($this->atLeastOnce())
->method('getLoader')
->willReturn($loader);
$twig
->expects($this->once())
->method('render')
->with($expectedTemplate);
$this
->createController($profiler, $twig, false, array_map(function (string $collectorName) use ($expectedPanel, $expectedTemplate): array {
if ($collectorName === $expectedPanel) {
return [$expectedPanel, $expectedTemplate];
}
return [$collectorName, 'other_template.html.twig'];
}, $collectorsNames))
->panelAction(new Request(), $profile->getToken());
}
public function defaultPanelProvider(): \Generator
{
// Test default behavior
$profile = new Profile('xxxxxx');
$profile->addCollector($requestDataCollector = new RequestDataCollector());
yield [$requestDataCollector->getName(), $profile];
// Test exception
$profile = new Profile('xxxxxx');
$profile->addCollector($exceptionDataCollector = new ExceptionDataCollector());
$exceptionDataCollector->collect(new Request(), new Response(), new \DomainException());
yield [$exceptionDataCollector->getName(), $profile];
// Test exception priority
$dumpDataCollector = $this->createMock(DumpDataCollector::class);
$dumpDataCollector
->expects($this->atLeastOnce())
->method('getName')
->willReturn('dump');
$dumpDataCollector
->expects($this->atLeastOnce())
->method('getDumpsCount')
->willReturn(1);
$profile = new Profile('xxxxxx');
$profile->setCollectors([$exceptionDataCollector, $dumpDataCollector]);
yield [$exceptionDataCollector->getName(), $profile];
// Test exception priority when defined afterwards
$profile = new Profile('xxxxxx');
$profile->setCollectors([$dumpDataCollector, $exceptionDataCollector]);
yield [$exceptionDataCollector->getName(), $profile];
// Test dump
$profile = new Profile('xxxxxx');
$profile->addCollector($dumpDataCollector);
yield [$dumpDataCollector->getName(), $profile];
}
private function createController($profiler, $twig, $withCSP, array $templates = []): ProfilerController
{
$urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock();
@ -193,9 +291,9 @@ class ProfilerControllerTest extends TestCase
$nonceGenerator = $this->getMockBuilder('Symfony\Bundle\WebProfilerBundle\Csp\NonceGenerator')->getMock();
$nonceGenerator->method('generate')->willReturn('dummy_nonce');
return new ProfilerController($urlGenerator, $profiler, $twig, [], new ContentSecurityPolicyHandler($nonceGenerator));
return new ProfilerController($urlGenerator, $profiler, $twig, $templates, new ContentSecurityPolicyHandler($nonceGenerator));
}
return new ProfilerController($urlGenerator, $profiler, $twig, []);
return new ProfilerController($urlGenerator, $profiler, $twig, $templates);
}
}

View File

@ -15,7 +15,8 @@ use Symfony\Bundle\WebProfilerBundle\Profiler\TemplateManager;
use Symfony\Bundle\WebProfilerBundle\Tests\TestCase;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Twig\Environment;
use Twig\Source;
use Twig\Loader\LoaderInterface;
use Twig\Loader\SourceContextLoaderInterface;
/**
* Test for TemplateManager class.

View File

@ -380,7 +380,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
continue;
}
$version -= $this->knownTagVersions[$tag][1];
if ((0 !== $version && 1 !== $version) || $this->knownTagVersionsTtl > $now - $this->knownTagVersions[$tag][0]) {
if ((0 !== $version && 1 !== $version) || $now - $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) {
// reuse previously fetched tag versions up to the ttl, unless we are storing items or a potential miss arises
$fetchTagVersions = true;
} else {

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
@ -67,6 +68,42 @@ class TagAwareAdapterTest extends AdapterTestCase
$this->assertFalse($cache->prune());
}
public function testKnownTagVersionsTtl()
{
$itemsPool = new FilesystemAdapter('', 10);
$tagsPool = $this
->getMockBuilder(AdapterInterface::class)
->getMock();
$pool = new TagAwareAdapter($itemsPool, $tagsPool, 10);
$item = $pool->getItem('foo');
$item->tag(['baz']);
$item->expiresAfter(100);
$tag = $this->getMockBuilder(CacheItemInterface::class)->getMock();
$tag->expects(self::exactly(2))->method('get')->willReturn(10);
$tagsPool->expects(self::exactly(2))->method('getItems')->willReturn([
'baz'.TagAwareAdapter::TAGS_PREFIX => $tag,
]);
$pool->save($item);
$this->assertTrue($pool->getItem('foo')->isHit());
$this->assertTrue($pool->getItem('foo')->isHit());
sleep(20);
$this->assertTrue($pool->getItem('foo')->isHit());
sleep(5);
$this->assertTrue($pool->getItem('foo')->isHit());
}
/**
* @return MockObject|PruneableCacheInterface
*/
private function getPruneableMock(): AdapterInterface
{
$pruneable = $this->createMock([PruneableInterface::class, AdapterInterface::class]);

View File

@ -271,7 +271,7 @@ trait RedisTrait
if (null !== $auth) {
$params['parameters']['password'] = $auth;
}
if (1 === \count($hosts) && !$params['redis_cluster']) {
if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) {
$hosts = $hosts[0];
} elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) {
$params['replication'] = true;

View File

@ -23,6 +23,7 @@ CHANGELOG
* `Application` implements `ResetInterface`
* marked all dispatched event classes as `@final`
* added support for displaying table horizontally
* deprecated returning `null` from `Command::execute()`, return `0` instead
4.3.0
-----

View File

@ -150,7 +150,7 @@ class Command
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @return int|void void or 0 if everything went fine, or an exit code
* @return int 0 if everything went fine, or an exit code
*
* @throws LogicException When this abstract method is not implemented
*
@ -253,6 +253,10 @@ class Command
$statusCode = ($this->code)($input, $output);
} else {
$statusCode = $this->execute($input, $output);
if (!\is_int($statusCode)) {
@trigger_error(sprintf('A non numeric or nullable $statusCode returned by Command::execute() is deprecated since Symfony 4.4, return an integer value instead.'), E_USER_DEPRECATED);
}
}
return is_numeric($statusCode) ? (int) $statusCode : 0;

View File

@ -77,5 +77,7 @@ EOF
]);
$this->command = null;
return 0;
}
}

View File

@ -74,6 +74,8 @@ EOF
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
]);
return 0;
}
private function createDefinition(): InputDefinition

View File

@ -728,6 +728,30 @@ class ApplicationTest extends TestCase
$this->assertInstanceOf('FooHiddenCommand', $application->find('afoohidden'));
}
/**
* @group legacy
* @expectedDeprecation Command "%s:hidden" is hidden, finding it using an abbreviation is deprecated since Symfony 4.4, use its full name instead.
* @dataProvider provideAbbreviationsForHiddenCommands
*/
public function testFindHiddenWithAbbreviatedName($name)
{
$application = new Application();
$application->add(new \FooHiddenCommand());
$application->add(new \BarHiddenCommand());
$application->find($name);
}
public function provideAbbreviationsForHiddenCommands()
{
return [
['foo:hidde'],
['afoohidd'],
['bar:hidde'],
];
}
public function testFindAmbiguousCommandsIfAllAlternativesAreHidden()
{
$application = new Application();
@ -1823,9 +1847,11 @@ class CustomDefaultCommandApplication extends Application
class LazyCommand extends Command
{
public function execute(InputInterface $input, OutputInterface $output)
public function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('lazy-command called');
return 0;
}
}

View File

@ -293,20 +293,6 @@ class CommandTest extends TestCase
$tester->execute(['--bar' => true]);
}
public function testRunReturnsIntegerExitCode()
{
$command = new \TestCommand();
$exitCode = $command->run(new StringInput(''), new NullOutput());
$this->assertSame(0, $exitCode, '->run() returns integer exit code (treats null as 0)');
$command = $this->getMockBuilder('TestCommand')->setMethods(['execute'])->getMock();
$command->expects($this->once())
->method('execute')
->willReturn('2.3');
$exitCode = $command->run(new StringInput(''), new NullOutput());
$this->assertSame(2, $exitCode, '->run() returns integer exit code (casts numeric to int)');
}
public function testRunWithApplication()
{
$command = new \TestCommand();

View File

@ -15,7 +15,8 @@ class BarHiddenCommand extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
return 0;
}
}

View File

@ -18,9 +18,11 @@ class Foo1Command extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->input = $input;
$this->output = $output;
return 0;
}
}

View File

@ -15,7 +15,8 @@ class Foo2Command extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
return 0;
}
}

View File

@ -14,7 +14,7 @@ class Foo3Command extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
try {
try {
@ -25,5 +25,7 @@ class Foo3Command extends Command
} catch (\Exception $e) {
throw new \Exception('Third exception <fg=blue;bg=red>comment</>', 404, $e);
}
return 0;
}
}

View File

@ -23,11 +23,13 @@ class FooCommand extends Command
$output->writeln('interact called');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->input = $input;
$this->output = $output;
$output->writeln('called');
return 0;
}
}

View File

@ -15,7 +15,8 @@ class FooHiddenCommand extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
return 0;
}
}

View File

@ -25,12 +25,14 @@ class FooOptCommand extends Command
$output->writeln('interact called');
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->input = $input;
$this->output = $output;
$output->writeln('called');
$output->writeln($this->input->getOption('fooopt'));
return 0;
}
}

View File

@ -18,9 +18,11 @@ class FooSubnamespaced1Command extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->input = $input;
$this->output = $output;
return 0;
}
}

View File

@ -18,9 +18,11 @@ class FooSubnamespaced2Command extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->input = $input;
$this->output = $output;
return 0;
}
}

View File

@ -14,8 +14,10 @@ class FooWithoutAliasCommand extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('called');
return 0;
}
}

View File

@ -17,9 +17,11 @@ class FoobarCommand extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->input = $input;
$this->output = $output;
return 0;
}
}

View File

@ -15,8 +15,10 @@ class TestAmbiguousCommandRegistering extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->write('test-ambiguous');
return 0;
}
}

View File

@ -14,8 +14,10 @@ class TestAmbiguousCommandRegistering2 extends Command
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->write('test-ambiguous2');
return 0;
}
}

View File

@ -19,6 +19,8 @@ class TestCommand extends Command
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('execute called');
return 0;
}
protected function interact(InputInterface $input, OutputInterface $output)

View File

@ -98,7 +98,7 @@ class TranslatorTest extends TestCase
$elements = $document->xpath($translator->cssToXPath($css));
$this->assertCount(\count($elementsId), $elements);
foreach ($elements as $element) {
$this->assertTrue(\in_array($element->attributes()->id, $elementsId));
$this->assertContains((string) $element->attributes()->id, $elementsId);
}
}
@ -116,7 +116,7 @@ class TranslatorTest extends TestCase
$this->assertCount(\count($elementsId), $elementsId);
foreach ($elements as $element) {
if (null !== $element->attributes()->id) {
$this->assertTrue(\in_array($element->attributes()->id, $elementsId));
$this->assertContains((string) $element->attributes()->id, $elementsId);
}
}
libxml_clear_errors();
@ -137,6 +137,33 @@ class TranslatorTest extends TestCase
$this->assertCount($count, $elements);
}
public function testOnlyOfTypeFindsSingleChildrenOfGivenType()
{
$translator = new Translator();
$translator->registerExtension(new HtmlExtension($translator));
$document = new \DOMDocument();
$document->loadHTML(<<<'HTML'
<html>
<body>
<p>
<span>A</span>
</p>
<p>
<span>B</span>
<span>C</span>
</p>
</body>
</html>
HTML
);
$xpath = new \DOMXPath($document);
$nodeList = $xpath->query($translator->cssToXPath('span:only-of-type'));
$this->assertSame(1, $nodeList->length);
$this->assertSame('A', $nodeList->item(0)->textContent);
}
public function getXpathLiteralTestData()
{
return [
@ -175,7 +202,7 @@ class TranslatorTest extends TestCase
['e:first-of-type', '*/e[position() = 1]'],
['e:last-of-type', '*/e[position() = last()]'],
['e:only-child', "*/*[(name() = 'e') and (last() = 1)]"],
['e:only-of-type', 'e[last() = 1]'],
['e:only-of-type', 'e[count(preceding-sibling::e)=0 and count(following-sibling::e)=0]'],
['e:empty', 'e[not(*) and not(string-length())]'],
['e:EmPTY', 'e[not(*) and not(string-length())]'],
['e:root', 'e[not(parent::*)]'],

View File

@ -105,11 +105,13 @@ class PseudoClassExtension extends AbstractExtension
*/
public function translateOnlyOfType(XPathExpr $xpath): XPathExpr
{
if ('*' === $xpath->getElement()) {
$element = $xpath->getElement();
if ('*' === $element) {
throw new ExpressionErrorException('"*:only-of-type" is not implemented.');
}
return $xpath->addCondition('last() = 1');
return $xpath->addCondition(sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element));
}
public function translateEmpty(XPathExpr $xpath): XPathExpr

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Loader\FileLoader;
/**
* This pass validates each definition individually only taking the information
@ -43,7 +44,7 @@ class CheckDefinitionValidityPass implements CompilerPassInterface
}
// non-synthetic, non-abstract service has class
if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass()) {
if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass() && (!$definition->getFactory() || !preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id))) {
if ($definition->getFactory()) {
throw new RuntimeException(sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id));
}

View File

@ -34,6 +34,7 @@ use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceExce
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper;
use Symfony\Component\DependencyInjection\Loader\FileLoader;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator;
@ -1210,7 +1211,7 @@ EOF;
$ids = array_keys($ids);
sort($ids);
foreach ($ids as $id) {
if (preg_match('/^\.\d+_[^~]++~[._a-zA-Z\d]{7}$/', $id)) {
if (preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id)) {
continue;
}
$code .= ' '.$this->doExport($id)." => true,\n";

View File

@ -236,7 +236,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface
}
if ('csv' === $prefix) {
return str_getcsv($env);
return str_getcsv($env, ',', '"', \PHP_VERSION_ID >= 70400 ? '' : '\\');
}
if ('trim' === $prefix) {

View File

@ -26,6 +26,8 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
*/
abstract class FileLoader extends BaseFileLoader
{
public const ANONYMOUS_ID_REGEXP = '/^\.\d+_[^~]++~[._a-zA-Z\d]{7}$/';
protected $container;
protected $isLoadingInstanceof = false;
protected $instanceof = [];

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Compiler\CheckDefinitionValidityPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
class CheckDefinitionValidityPassTest extends TestCase
{
@ -35,6 +36,19 @@ class CheckDefinitionValidityPassTest extends TestCase
$this->process($container);
}
public function testProcessDetectsFactoryWithoutClass()
{
$container = new ContainerBuilder();
$container->register('.123_anonymous_service_id_should_not_throw_~1234567')->setFactory('factory');
$this->process($container);
$this->expectException(RuntimeException::class);
$container->register('.any_non_anonymous_id_throws')->setFactory('factory');
$this->process($container);
}
public function testProcess()
{
$container = new ContainerBuilder();

View File

@ -478,4 +478,37 @@ class EnvVarProcessorTest extends TestCase
$this->assertEquals('foo', $result);
}
/**
* @dataProvider validCsv
*/
public function testGetEnvCsv($value, $processed)
{
$processor = new EnvVarProcessor(new Container());
$result = $processor->getEnv('csv', 'foo', function ($name) use ($value) {
$this->assertSame('foo', $name);
return $value;
});
$this->assertSame($processed, $result);
}
public function validCsv()
{
$complex = <<<'CSV'
,"""","foo""","\""",\,foo\
CSV;
return [
['', [null]],
[',', ['', '']],
['1', ['1']],
['1,2," 3 "', ['1', '2', ' 3 ']],
['\\,\\\\', ['\\', '\\\\']],
[$complex, \PHP_VERSION_ID >= 70400 ? ['', '"', 'foo"', '\\"', '\\', 'foo\\'] : ['', '"', 'foo"', '\\"",\\,foo\\']],
[null, null],
];
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="foo">
<call method="bar" returns-clone="true" />
</service>
</services>
</container>

View File

@ -0,0 +1,5 @@
services:
foo:
calls:
- {method: bar, arguments: [1], returns_clone: true}
- [bar, [2], true]

View File

@ -904,6 +904,15 @@ class XmlFileLoaderTest extends TestCase
$this->assertSame('overridden', $container->get('bar')->quz);
}
public function testReturnsClone()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('returns_clone.xml');
$this->assertSame([['bar', [], true]], $container->getDefinition('foo')->getMethodCalls());
}
public function testSinglyImplementedInterfacesInMultipleResources()
{
$container = new ContainerBuilder();

View File

@ -810,6 +810,19 @@ class YamlFileLoaderTest extends TestCase
$this->assertNull($iteratorArgument->getIndexAttribute());
}
public function testReturnsClone()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('returns_clone.yaml');
$expected = [
['bar', [1], true],
['bar', [2], true],
];
$this->assertSame($expected, $container->getDefinition('foo')->getMethodCalls());
}
public function testSinglyImplementedInterfacesInMultipleResources()
{
$container = new ContainerBuilder();

View File

@ -95,6 +95,8 @@ EOF
$io->newLine();
$io->table(['Format', 'Class'], $tableRows);
}
return 0;
}
private function formatClassLink(string $class): string

View File

@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RegisterListenersPassTest extends TestCase
{
@ -37,10 +38,6 @@ class RegisterListenersPassTest extends TestCase
public function testValidEventSubscriber()
{
$services = [
'my_event_subscriber' => [0 => []],
];
$builder = new ContainerBuilder();
$eventDispatcherDefinition = $builder->register('event_dispatcher');
$builder->register('my_event_subscriber', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')
@ -62,6 +59,30 @@ class RegisterListenersPassTest extends TestCase
$this->assertEquals($expectedCalls, $eventDispatcherDefinition->getMethodCalls());
}
public function testAliasedEventSubscriber(): void
{
$builder = new ContainerBuilder();
$builder->setParameter('event_dispatcher.event_aliases', [AliasedEvent::class => 'aliased_event']);
$builder->register('event_dispatcher');
$builder->register('my_event_subscriber', AliasedSubscriber::class)
->addTag('kernel.event_subscriber');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($builder);
$expectedCalls = [
[
'addListener',
[
'aliased_event',
[new ServiceClosureArgument(new Reference('my_event_subscriber')), 'onAliasedEvent'],
0,
],
],
];
$this->assertEquals($expectedCalls, $builder->getDefinition('event_dispatcher')->getMethodCalls());
}
public function testAbstractEventListener()
{
$this->expectException('InvalidArgumentException');
@ -175,9 +196,33 @@ class RegisterListenersPassTest extends TestCase
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
public function testAliasedEventListener(): void
{
$container = new ContainerBuilder();
$container->setParameter('event_dispatcher.event_aliases', [AliasedEvent::class => 'aliased_event']);
$container->register('foo', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => AliasedEvent::class, 'method' => 'onEvent']);
$container->register('event_dispatcher');
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->process($container);
$definition = $container->getDefinition('event_dispatcher');
$expectedCalls = [
[
'addListener',
[
'aliased_event',
[new ServiceClosureArgument(new Reference('foo')), 'onEvent'],
0,
],
],
];
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
}
class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
class SubscriberService implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
@ -197,3 +242,17 @@ class InvokableListenerService
{
}
}
final class AliasedSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
AliasedEvent::class => 'onAliasedEvent',
];
}
}
final class AliasedEvent
{
}

View File

@ -152,6 +152,8 @@ EOF
$options['format'] = $input->getOption('format');
$options['show_deprecated'] = $input->getOption('show-deprecated');
$helper->describe($io, $object, $options);
return 0;
}
private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, string $shortClassName)

View File

@ -18,6 +18,7 @@ use Symfony\Component\HttpKernel\UriSigner;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Loader\ExistsLoaderInterface;
use Twig\Loader\SourceContextLoaderInterface;
/**
* Implements the Hinclude rendering strategy.
@ -76,7 +77,7 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer
$uri = str_replace('&', '&amp;', $uri);
$template = isset($options['default']) ? $options['default'] : $this->globalDefaultTemplate;
if (null !== $this->twig && $template && $this->templateExists($template)) {
if (null !== $this->twig && $template && $this->twig->getLoader()->exists($template)) {
$content = $this->twig->render($template);
} else {
$content = $template;
@ -101,27 +102,6 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer
return new Response(sprintf('<hx:include src="%s"%s>%s</hx:include>', $uri, $renderedAttributes, $content));
}
private function templateExists(string $template): bool
{
$loader = $this->twig->getLoader();
if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) {
return $loader->exists($template);
}
try {
if (method_exists($loader, 'getSourceContext')) {
$loader->getSourceContext($template);
} else {
$loader->getSource($template);
}
return true;
} catch (LoaderError $e) {
}
return false;
}
/**
* {@inheritdoc}
*/

View File

@ -424,25 +424,72 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
$class = $this->getContainerClass();
$cacheDir = $this->warmupDir ?: $this->getCacheDir();
$cache = new ConfigCache($cacheDir.'/'.$class.'.php', $this->debug);
$oldContainer = null;
if ($fresh = $cache->isFresh()) {
// Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
$fresh = $oldContainer = false;
try {
if (file_exists($cache->getPath()) && \is_object($this->container = include $cache->getPath())) {
$this->container->set('kernel', $this);
$oldContainer = $this->container;
$fresh = true;
}
} catch (\Throwable $e) {
} finally {
$cachePath = $cache->getPath();
// Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
try {
if (file_exists($cachePath) && \is_object($this->container = include $cachePath) && (!$this->debug || $cache->isFresh())) {
$this->container->set('kernel', $this);
error_reporting($errorLevel);
return;
}
} catch (\Throwable $e) {
}
if ($fresh) {
return;
$oldContainer = \is_object($this->container) ? new \ReflectionClass($this->container) : $this->container = null;
try {
is_dir($cacheDir) ?: mkdir($cacheDir, 0777, true);
if ($lock = fopen($cachePath, 'w')) {
chmod($cachePath, 0666 & ~umask());
flock($lock, LOCK_EX | LOCK_NB, $wouldBlock);
if (!flock($lock, $wouldBlock ? LOCK_SH : LOCK_EX)) {
fclose($lock);
} else {
$cache = new class($cachePath, $this->debug) extends ConfigCache {
public $lock;
public function write($content, array $metadata = null)
{
rewind($this->lock);
ftruncate($this->lock, 0);
fwrite($this->lock, $content);
if (null !== $metadata) {
file_put_contents($this->getPath().'.meta', serialize($metadata));
@chmod($this->getPath().'.meta', 0666 & ~umask());
}
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
opcache_invalidate($this->getPath(), true);
}
}
public function __destruct()
{
flock($this->lock, LOCK_UN);
fclose($this->lock);
}
};
$cache->lock = $lock;
if (!\is_object($this->container = include $cachePath)) {
$this->container = null;
} elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) {
$this->container->set('kernel', $this);
return;
}
}
}
} catch (\Throwable $e) {
} finally {
error_reporting($errorLevel);
}
if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) {
@ -500,19 +547,9 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
}
}
if (null === $oldContainer && file_exists($cache->getPath())) {
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
try {
$oldContainer = include $cache->getPath();
} catch (\Throwable $e) {
} finally {
error_reporting($errorLevel);
}
}
$oldContainer = \is_object($oldContainer) ? new \ReflectionClass($oldContainer) : false;
$this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass());
$this->container = require $cache->getPath();
unset($cache);
$this->container = require $cachePath;
$this->container->set('kernel', $this);
if ($oldContainer && \get_class($this->container) !== $oldContainer->name) {

View File

@ -35,7 +35,7 @@ class RoundRobinTransport implements TransportInterface
public function __construct(array $transports, int $retryPeriod = 60)
{
if (!$transports) {
throw new TransportException(__CLASS__.' must have at least one transport configured.');
throw new TransportException(sprintf('"%s" must have at least one transport configured.', static::class));
}
$this->transports = $transports;
@ -58,9 +58,7 @@ class RoundRobinTransport implements TransportInterface
public function __toString(): string
{
return $this->getNameSymbol().'('.implode(' ', array_map(function (TransportInterface $transport) {
return (string) $transport;
}, $this->transports)).')';
return $this->getNameSymbol().'('.implode(' ', array_map('strval', $this->transports)).')';
}
/**

View File

@ -21,7 +21,7 @@ use Symfony\Component\Mime\RawMessage;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Transports implements TransportInterface
final class Transports implements TransportInterface
{
private $transports;
private $default;
@ -64,6 +64,6 @@ class Transports implements TransportInterface
public function __toString(): string
{
return 'all';
return '['.implode(',', array_keys($this->transports)).']';
}
}

View File

@ -205,6 +205,8 @@ EOF
$worker->run([
'sleep' => $input->getOption('sleep') * 1000000,
]);
return 0;
}
private function convertToBytes(string $memoryLimit): int

View File

@ -96,6 +96,8 @@ EOF
$io->warning(sprintf('No handled message found in bus "%s".', $bus));
}
}
return 0;
}
private function formatConditions(array $options): string

View File

@ -61,6 +61,8 @@ EOF
$shouldForce = $input->getOption('force');
$this->removeSingleMessage($input->getArgument('id'), $receiver, $io, $shouldForce);
return 0;
}
private function removeSingleMessage(string $id, ReceiverInterface $receiver, SymfonyStyle $io, bool $shouldForce)

View File

@ -110,11 +110,13 @@ EOF
$this->runInteractive($io, $shouldForce);
return;
return 0;
}
$this->retrySpecificIds($ids, $io, $shouldForce);
$io->success('All done!');
return 0;
}
private function runInteractive(SymfonyStyle $io, bool $shouldForce)

View File

@ -71,6 +71,8 @@ EOF
} else {
$this->showMessage($id, $io);
}
return 0;
}
private function listMessages(SymfonyStyle $io, int $max)

View File

@ -76,5 +76,7 @@ EOF
$io->note(sprintf('The "%s" transport does not support setup.', $transportName));
}
}
return 0;
}
}

View File

@ -68,5 +68,7 @@ EOF
$this->restartSignalCachePool->save($cacheItem);
$io->success('Signal successfully sent to stop any running workers.');
return 0;
}
}

View File

@ -46,7 +46,7 @@ class X509AuthenticationListener extends AbstractPreAuthenticatedListener
$user = $request->server->get($this->userKey);
} elseif (
$request->server->has($this->credentialKey)
&& preg_match('#emailAddress=(.+\@.+\.[^,/]+)($|,|/)#', $request->server->get($this->credentialKey), $matches)
&& preg_match('#emailAddress=([^,/@]++@[^,/]++)#', $request->server->get($this->credentialKey), $matches)
) {
$user = $matches[1];
}

View File

@ -81,6 +81,7 @@ class X509AuthenticationListenerTest extends TestCase
yield ['cert+something@example.com', 'CN=Sample certificate DN,emailAddress=cert+something@example.com'];
yield ['cert+something@example.com', 'emailAddress=cert+something@example.com,CN=Sample certificate DN'];
yield ['cert+something@example.com', 'emailAddress=cert+something@example.com'];
yield ['firstname.lastname@mycompany.co.uk', 'emailAddress=firstname.lastname@mycompany.co.uk,CN=Firstname.Lastname,OU=london,OU=company design and engineering,OU=Issuer London,OU=Roaming,OU=Interactive,OU=Users,OU=Standard,OU=Business,DC=england,DC=core,DC=company,DC=co,DC=uk'];
}
public function testGetPreAuthenticatedDataNoData()

View File

@ -19,6 +19,11 @@ CHANGELOG
* removed support for instantiating a `DataUriNormalizer` with a default MIME type guesser when the `symfony/mime` component isn't installed.
* removed the `XmlEncoder::TYPE_CASE_ATTRIBUTES` constant. Use `XmlEncoder::TYPE_CAST_ATTRIBUTES` instead.
4.4.0
-----
* deprecated the `XmlEncoder::TYPE_CASE_ATTRIBUTES` constant, use `XmlEncoder::TYPE_CAST_ATTRIBUTES` instead
4.3.0
-----

View File

@ -36,7 +36,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
self::AS_COLLECTION_KEY => true,
self::DELIMITER_KEY => ',',
self::ENCLOSURE_KEY => '"',
self::ESCAPE_CHAR_KEY => '\\',
self::ESCAPE_CHAR_KEY => '',
self::ESCAPE_FORMULAS_KEY => false,
self::HEADERS_KEY => [],
self::KEY_SEPARATOR_KEY => '.',
@ -46,6 +46,10 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
public function __construct(array $defaultContext = [])
{
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
if (\PHP_VERSION_ID < 70400 && '' === $this->defaultContext[self::ESCAPE_CHAR_KEY]) {
$this->defaultContext[self::ESCAPE_CHAR_KEY] = '\\';
}
}
/**

View File

@ -370,16 +370,18 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
return null;
}
if ($type->isCollection() && null !== ($collectionValueType = $type->getCollectionValueType()) && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) {
$collectionValueType = $type->isCollection() ? $type->getCollectionValueType() : null;
// Fix a collection that contains the only one element
// This is special to xml format only
if ('xml' === $format && null !== $collectionValueType && (!\is_array($data) || !\is_int(key($data)))) {
$data = [$data];
}
if (null !== $collectionValueType && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) {
$builtinType = Type::BUILTIN_TYPE_OBJECT;
$class = $collectionValueType->getClassName().'[]';
// Fix a collection that contains the only one element
// This is special to xml format only
if ('xml' === $format && !\is_int(key($data))) {
$data = [$data];
}
if (null !== $collectionKeyType = $type->getCollectionKeyType()) {
$context['key_type'] = $collectionKeyType;
}

View File

@ -36,15 +36,51 @@ class CsvEncoderTest extends TestCase
'int' => 2,
'false' => false,
'true' => true,
'int_one' => 1,
'string_one' => '1',
];
// Check that true and false are appropriately handled
$this->assertEquals(<<<'CSV'
string,int,false,true
foo,2,0,1
$this->assertSame($csv = <<<'CSV'
string,int,false,true,int_one,string_one
foo,2,0,1,1,1
CSV
, $this->encoder->encode($data, 'csv'));
, $this->encoder->encode($data, 'csv'));
$this->assertSame([
'string' => 'foo',
'int' => '2',
'false' => '0',
'true' => '1',
'int_one' => '1',
'string_one' => '1',
], $this->encoder->decode($csv, 'csv', [CsvEncoder::AS_COLLECTION_KEY => false]));
}
/**
* @requires PHP 7.4
*/
public function testDoubleQuotesAndSlashes()
{
$this->assertSame($csv = <<<'CSV'
0,1,2,3,4,5
,"""","foo""","\""",\,foo\
CSV
, $this->encoder->encode($data = ['', '"', 'foo"', '\\"', '\\', 'foo\\'], 'csv'));
$this->assertSame($data, $this->encoder->decode($csv, 'csv'));
}
/**
* @requires PHP 7.4
*/
public function testSingleSlash()
{
$this->assertSame($csv = "0\n\\\n", $this->encoder->encode($data = ['\\'], 'csv'));
$this->assertSame($data, $this->encoder->decode($csv, 'csv'));
$this->assertSame($data, $this->encoder->decode(trim($csv), 'csv'));
}
public function testSupportEncoding()

View File

@ -132,16 +132,54 @@ class AbstractObjectNormalizerTest extends TestCase
$extractor = $this->getMockBuilder(PhpDocExtractor::class)->getMock();
$extractor->method('getTypes')
->will($this->onConsecutiveCalls(
[
new Type(
'array',
false,
null,
true,
new Type('int'),
new Type('object', false, DummyChild::class)
),
],
[new Type('array', false, null, true, new Type('int'), new Type('object', false, DummyChild::class))],
null
));
$denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor);
$arrayDenormalizer = new ArrayDenormalizerDummy();
$serializer = new SerializerCollectionDummy([$arrayDenormalizer, $denormalizer]);
$arrayDenormalizer->setSerializer($serializer);
$denormalizer->setSerializer($serializer);
return $denormalizer;
}
public function testDenormalizeStringCollectionDecodedFromXmlWithOneChild()
{
$denormalizer = $this->getDenormalizerForStringCollection();
// if an xml-node can have children which should be deserialized as string[]
// and only one child exists
$stringCollection = $denormalizer->denormalize(['children' => 'foo'], StringCollection::class, 'xml');
$this->assertInstanceOf(StringCollection::class, $stringCollection);
$this->assertIsArray($stringCollection->children);
$this->assertCount(1, $stringCollection->children);
$this->assertEquals('foo', $stringCollection->children[0]);
}
public function testDenormalizeStringCollectionDecodedFromXmlWithTwoChildren()
{
$denormalizer = $this->getDenormalizerForStringCollection();
// if an xml-node can have children which should be deserialized as string[]
// and only one child exists
$stringCollection = $denormalizer->denormalize(['children' => ['foo', 'bar']], StringCollection::class, 'xml');
$this->assertInstanceOf(StringCollection::class, $stringCollection);
$this->assertIsArray($stringCollection->children);
$this->assertCount(2, $stringCollection->children);
$this->assertEquals('foo', $stringCollection->children[0]);
$this->assertEquals('bar', $stringCollection->children[1]);
}
private function getDenormalizerForStringCollection()
{
$extractor = $this->getMockBuilder(PhpDocExtractor::class)->getMock();
$extractor->method('getTypes')
->will($this->onConsecutiveCalls(
[new Type('array', false, null, true, new Type('int'), new Type('string'))],
null
));
@ -275,6 +313,12 @@ class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer
}
}
class StringCollection
{
/** @var string[] */
public $children;
}
class DummyCollection
{
/** @var DummyChild[] */

View File

@ -75,7 +75,7 @@ EOF
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$format = $input->getOption('format');