Deleting lines of the terminal using a Symfony command

Published on 2021-08-01 • Modified on 2021-08-01

In this snippet, we see how to delete lines of the terminal using a Symfony command. Symfony 5.1 has introduced a significant change that allows having complete control of the cursor in the terminal thanks to the Symfony\Component\Console\Cursor object. Before this, we could use sections that provide a clear function. We can even do it manually using custom code like the clear() function below.


<?php

declare(strict_types=1);

// src/Command/DeleteLinesDemoCommand.php

namespace App\Command;

use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

final class DeleteLinesDemoCommand extends BaseCommand
{
    public const CMD = 'delele-lines-demo';

    protected static $defaultName = self::NAMESPACE.':'.self::CMD;
    protected static $defaultDescription = 'Demo on hom to delete lines of the terminal.';

    protected function configure(): void
    {
        [$desc, $class] = [self::$defaultDescription, self::class];
        $this->setHelp(
                <<<EOT
$desc

COMMAND:
<comment>$class</comment>

DEV:
<info>%command.full_name% -vv</info>

PROD:
<info>%command.full_name% --env=prod --no-debug</info>
EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        if ($output instanceof ConsoleOutputInterface) {
            $section = $output->section();
            $section->write('Dummy section I will clear at next line');
            $section->clear();
        }

        $io->write('delete me!');
        $cursor = new Cursor($output);
        $cursor->clearLine();
        $cursor->moveLeft(10);
        $io->write('delete me 2!');
        $cursor->clearLine();

        $io->writeln('delete me 3!');
        $this->clear($output);

        $io->writeln('delete me 4!');
        $io->writeln('delete me 5!');
        $this->clear($output, 2);

        $io->success('Done!');

        return self::SUCCESS;
    }

    /**
     * @see ConsoleSectionOutput::popStreamContentUntilCurrentSection
     */
    private function clear(OutputInterface $output, int $lines = 1): void
    {
        // move cursor up n lines
        $output->write("\x1b[{$lines}A");

        // erase to end of screen
        $output->writeln("\x1b[0J");
    }
}

 ≪ this.showUnitTest ? this.trans.hide_unit_test : this.trans.show_unit_test ≫  More on Stackoverflow   Read the doc  More on the web  Random snippet

<?php

declare(strict_types=1);

namespace App\Tests\Controller\Snippets;

use App\Command\BaseCommand;
use App\Command\DeleteLinesDemoCommand;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;

final class Snippet157Test extends KernelTestCase
{
    private ?DeleteLinesDemoCommand $deleteLinesDemoCommand;

    protected function setUp(): void
    {
        self::bootKernel();
        $this->deleteLinesDemoCommand = self::$kernel->getContainer()->get(DeleteLinesDemoCommand::class);
    }

    /**
     * @covers DeleteLinesDemoCommand::execute
     */
    public function testSnippet157(): void
    {
        $app = new Application(self::$kernel);
        if (!$this->deleteLinesDemoCommand instanceof DeleteLinesDemoCommand) {
            throw new \RuntimeException('Command not found.');
        }
        $app->add($this->deleteLinesDemoCommand);
        $command = $app->find(BaseCommand::NAMESPACE.':'.DeleteLinesDemoCommand::CMD);
        $commandTester = new CommandTester($command);
        $commandTester->execute([
            'command' => $command->getName(),
        ]);

        self::assertStringContainsString('delete', $commandTester->getDisplay(), 'delete');

        // 🤔 can seem weird but yes the delete string is found, it is not displayed
        // because of the ANSI output.
        // Here is the real content of the output (you must log it in a file)

        /*
delete me!delete me 2!delete me 3!

delete me 4!
delete me 5!


 [OK] Done!
         */
    }
}