mirror of
https://github.com/symfony/console.git
synced 2026-03-24 01:12:13 +01:00
Merge branch '8.0' into 8.1
* 8.0: [Serializer] Fix handling of constructor enum denormalization errors [Console] ProgressIndicator console helper display with multiple processes [HttpFoundation] Handle empty session data in updateTimestamp() to fix compat with PHP 8.6 [Console] Fix arguments set via #[Ask] wrongly considered null in profiler [Cache] Wrap `DoctrineDbalAdapter::doSave()` in savepoint to prevent transaction poisoning Update security-1.0.xsd with missing oauth2 element [Console] Silence shell_exec warning in hasSttyAvailable [Validator] Sync validators.pt.xlf Minor: Review and finalize Latvian translations for validators streamline ini settings in phpunit.xml.dist files stop using with*() without expects() stop using with*() without expects() TypeContextFactory::collectTemplates now also works with @phpstan-template and @psalm-template
This commit is contained in:
@@ -286,8 +286,8 @@ final class TraceableCommand extends Command
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
$this->arguments = $input->getArguments();
|
||||
$this->options = $input->getOptions();
|
||||
$initialArguments = $input->getArguments();
|
||||
$initialOptions = $input->getOptions();
|
||||
$event = $this->stopwatch->start($this->getName(), 'command');
|
||||
|
||||
try {
|
||||
@@ -302,9 +302,11 @@ final class TraceableCommand extends Command
|
||||
$this->duration = $event->getDuration().' ms';
|
||||
$this->maxMemoryUsage = ($event->getMemory() >> 20).' MiB';
|
||||
|
||||
if ($this->isInteractive) {
|
||||
$this->extractInteractiveInputs($input->getArguments(), $input->getOptions());
|
||||
}
|
||||
$this->arguments = $input->getArguments();
|
||||
$this->options = $input->getOptions();
|
||||
|
||||
$this->extractInteractiveInputs($initialArguments, $initialOptions);
|
||||
$this->isInteractive = $this->isInteractive || $this->interactiveInputs;
|
||||
}
|
||||
|
||||
return $this->exitCode;
|
||||
@@ -343,22 +345,24 @@ final class TraceableCommand extends Command
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
private function extractInteractiveInputs(array $arguments, array $options): void
|
||||
private function extractInteractiveInputs(array $initialArguments, array $initialOptions): void
|
||||
{
|
||||
foreach ($arguments as $argName => $argValue) {
|
||||
if (\array_key_exists($argName, $this->arguments) && $this->arguments[$argName] === $argValue) {
|
||||
$nativeDefinition = $this->command->getNativeDefinition();
|
||||
|
||||
foreach ($nativeDefinition->getArguments() as $argName => $argument) {
|
||||
if (\array_key_exists($argName, $initialArguments) && $initialArguments[$argName] === $this->arguments[$argName]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->interactiveInputs[$argName] = $argValue;
|
||||
$this->interactiveInputs[$argName] = $this->arguments[$argName];
|
||||
}
|
||||
|
||||
foreach ($options as $optName => $optValue) {
|
||||
if (\array_key_exists($optName, $this->options) && $this->options[$optName] === $optValue) {
|
||||
foreach ($nativeDefinition->getOptions() as $optName => $option) {
|
||||
if (\array_key_exists($optName, $initialOptions) && $initialOptions[$optName] === $this->options[$optName]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->interactiveInputs['--'.$optName] = $optValue;
|
||||
$this->interactiveInputs['--'.$optName] = $this->options[$optName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Symfony\Component\Console\Helper;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
@@ -142,7 +143,9 @@ class ProgressIndicator
|
||||
$this->finished = true;
|
||||
$this->message = $message;
|
||||
$this->display();
|
||||
$this->output->writeln('');
|
||||
if (!$this->output instanceof ConsoleSectionOutput) {
|
||||
$this->output->writeln('');
|
||||
}
|
||||
$this->started = false;
|
||||
}
|
||||
|
||||
@@ -207,7 +210,9 @@ class ProgressIndicator
|
||||
*/
|
||||
private function overwrite(string $message): void
|
||||
{
|
||||
if ($this->output->isDecorated()) {
|
||||
if ($this->output instanceof ConsoleSectionOutput) {
|
||||
$this->output->overwrite($message);
|
||||
} elseif ($this->output->isDecorated()) {
|
||||
$this->output->write("\x0D\x1B[2K");
|
||||
$this->output->write($message);
|
||||
} else {
|
||||
|
||||
@@ -124,7 +124,7 @@ class Terminal
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::$stty = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
|
||||
return self::$stty = (bool) @shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
|
||||
}
|
||||
|
||||
public static function supportsKittyGraphics(): bool
|
||||
|
||||
@@ -85,6 +85,22 @@ class TraceableCommandTest extends TestCase
|
||||
self::assertStringContainsString('Hello World', $commandTester->getDisplay());
|
||||
}
|
||||
|
||||
public function testArgumentsCaptureValueSetDuringInteract()
|
||||
{
|
||||
$this->application->addCommand(new InvokableWithAskCommand());
|
||||
$command = $this->application->find('invokable:ask');
|
||||
$traceableCommand = new TraceableCommand($command, new Stopwatch());
|
||||
|
||||
$commandTester = new CommandTester($traceableCommand);
|
||||
$commandTester->setInputs(['Robin']);
|
||||
$commandTester->execute([], ['interactive' => true]);
|
||||
$commandTester->assertCommandIsSuccessful();
|
||||
|
||||
self::assertSame('Robin', $traceableCommand->arguments['name']);
|
||||
self::assertTrue($traceableCommand->isInteractive);
|
||||
self::assertSame(['name' => 'Robin'], $traceableCommand->interactiveInputs);
|
||||
}
|
||||
|
||||
public function assertLoopOutputCorrectness(string $output)
|
||||
{
|
||||
$completeChar = '\\' !== \DIRECTORY_SEPARATOR ? '▓' : '=';
|
||||
|
||||
@@ -14,7 +14,9 @@ namespace Symfony\Component\Console\Tests\Helper;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Helper\ProgressIndicator;
|
||||
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
|
||||
#[Group('time-sensitive')]
|
||||
@@ -201,6 +203,58 @@ class ProgressIndicatorTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
public function testWithConsoleSectionOutput()
|
||||
{
|
||||
$sections = [];
|
||||
$stream = fopen('php://memory', 'r+', false);
|
||||
$output = new ConsoleSectionOutput($stream, $sections, StreamOutput::VERBOSITY_NORMAL, true, new OutputFormatter());
|
||||
|
||||
$bar = new ProgressIndicator($output, null, 100, ['-', '\\', '|', '/']);
|
||||
$bar->start('Starting...');
|
||||
usleep(101000);
|
||||
$bar->advance();
|
||||
$bar->finish('Done...');
|
||||
|
||||
rewind($stream);
|
||||
$content = stream_get_contents($stream);
|
||||
|
||||
// Must not use raw ANSI line-clear sequences — those corrupt ConsoleSectionOutput's internal line tracking
|
||||
$this->assertStringNotContainsString("\x0D\x1B[2K", $content);
|
||||
|
||||
// finish() must not add an extra trailing newline — ConsoleSectionOutput::overwrite() already ends with writeln()
|
||||
$this->assertStringEndsWith(' \\ Done...'.\PHP_EOL, $content);
|
||||
}
|
||||
|
||||
public function testMultipleSectionsWithProgressIndicators()
|
||||
{
|
||||
$sections = [];
|
||||
$stream = fopen('php://memory', 'r+', false);
|
||||
$formatter = new OutputFormatter();
|
||||
$section1 = new ConsoleSectionOutput($stream, $sections, StreamOutput::VERBOSITY_NORMAL, true, $formatter);
|
||||
$section2 = new ConsoleSectionOutput($stream, $sections, StreamOutput::VERBOSITY_NORMAL, true, $formatter);
|
||||
|
||||
$bar1 = new ProgressIndicator($section1, null, 100, ['-', '\\', '|', '/']);
|
||||
$bar2 = new ProgressIndicator($section2, null, 100, ['-', '\\', '|', '/']);
|
||||
|
||||
$bar1->start('Project 1...');
|
||||
$bar2->start('Project 2...');
|
||||
usleep(101000);
|
||||
$bar1->advance();
|
||||
$bar2->advance();
|
||||
$bar1->finish('Project 1 Done.');
|
||||
$bar2->finish('Project 2 Done.');
|
||||
|
||||
rewind($stream);
|
||||
$content = stream_get_contents($stream);
|
||||
|
||||
// Must not use raw ANSI line-clear sequences
|
||||
$this->assertStringNotContainsString("\x0D\x1B[2K", $content);
|
||||
|
||||
// Both finished messages must appear in the output
|
||||
$this->assertStringContainsString('Project 1 Done.', $content);
|
||||
$this->assertStringContainsString('Project 2 Done.', $content);
|
||||
}
|
||||
|
||||
protected function getOutputStream($decorated = true, $verbosity = StreamOutput::VERBOSITY_NORMAL)
|
||||
{
|
||||
return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, $decorated);
|
||||
|
||||
Reference in New Issue
Block a user