Use a partial buffer in SymfonyStyle
authorJérémy Derussé <jeremy@derusse.com>
Tue, 24 Nov 2020 12:45:24 +0000 (13:45 +0100)
committerJérémy Derussé <jeremy@derusse.com>
Wed, 25 Nov 2020 11:18:08 +0000 (12:18 +0100)
src/Symfony/Component/Console/Output/TrimmedBufferOutput.php [new file with mode: 0644]
src/Symfony/Component/Console/Style/SymfonyStyle.php
src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php

diff --git a/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php b/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php
new file mode 100644 (file)
index 0000000..c014d43
--- /dev/null
@@ -0,0 +1,67 @@
+<?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\Console\Output;
+
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Formatter\OutputFormatterInterface;
+
+/**
+ * A BufferedOutput that keeps only the last N chars.
+ *
+ * @author Jérémy Derussé <jeremy@derusse.com>
+ */
+class TrimmedBufferOutput extends Output
+{
+    private $maxLength;
+    private $buffer = '';
+
+    public function __construct(
+        ?int $verbosity = self::VERBOSITY_NORMAL,
+        bool $decorated = false,
+        OutputFormatterInterface $formatter = null,
+        int $maxLength
+    ) {
+        if ($maxLength <= 0) {
+            throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength));
+        }
+
+        parent::__construct($verbosity, $decorated, $formatter);
+        $this->maxLength = $maxLength;
+    }
+
+    /**
+     * Empties buffer and returns its content.
+     *
+     * @return string
+     */
+    public function fetch()
+    {
+        $content = $this->buffer;
+        $this->buffer = '';
+
+        return $content;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function doWrite($message, $newline)
+    {
+        $this->buffer .= $message;
+
+        if ($newline) {
+            $this->buffer .= \PHP_EOL;
+        }
+
+        $this->buffer = substr($this->buffer, 0 - $this->maxLength);
+    }
+}
index b40c16e..a5edac3 100644 (file)
@@ -21,8 +21,8 @@ use Symfony\Component\Console\Helper\Table;
 use Symfony\Component\Console\Helper\TableCell;
 use Symfony\Component\Console\Helper\TableSeparator;
 use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\BufferedOutput;
 use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Output\TrimmedBufferOutput;
 use Symfony\Component\Console\Question\ChoiceQuestion;
 use Symfony\Component\Console\Question\ConfirmationQuestion;
 use Symfony\Component\Console\Question\Question;
@@ -46,7 +46,7 @@ class SymfonyStyle extends OutputStyle
     public function __construct(InputInterface $input, OutputInterface $output)
     {
         $this->input = $input;
-        $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
+        $this->bufferedOutput = new TrimmedBufferOutput($output->getVerbosity(), false, clone $output->getFormatter(), \DIRECTORY_SEPARATOR === '\\' ? 4 : 2);
         // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
         $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
         $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);
@@ -449,9 +449,8 @@ class SymfonyStyle extends OutputStyle
 
     private function writeBuffer(string $message, bool $newLine, int $type): void
     {
-        // We need to know if the two last chars are PHP_EOL
-        // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer
-        $this->bufferedOutput->write(substr($message, -4), $newLine, $type);
+        // We need to know if the last chars are PHP_EOL
+        $this->bufferedOutput->write($message, $newLine, $type);
     }
 
     private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array
index 943b941..2444d89 100644 (file)
@@ -14,8 +14,10 @@ namespace Symfony\Component\Console\Tests\Style;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\Formatter\OutputFormatter;
+use Symfony\Component\Console\Input\ArrayInput;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\NullOutput;
 use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Style\SymfonyStyle;
 use Symfony\Component\Console\Tester\CommandTester;
@@ -115,4 +117,17 @@ class SymfonyStyleTest extends TestCase
 
         $this->assertInstanceOf(SymfonyStyle::class, $style->getErrorStyle());
     }
+
+    public function testMemoryConsumption()
+    {
+        $io = new SymfonyStyle(new ArrayInput([]), new NullOutput());
+        $str = 'teststr';
+        $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET);
+        $start = memory_get_usage();
+        for ($i = 0; $i < 100; ++$i) {
+            $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET);
+        }
+
+        $this->assertSame(0, memory_get_usage() - $start);
+    }
 }