Search code examples
phpsymfonysymfony-console

Is it possible to achieve side-by-side table layout with Symfony Console?


┌──────── Summary:  1.1.5 update ─┬───────┐        ┌─────────────────────── Success ─┬─────────────────────┐
│ Success │ Failure │ In Progress │ Total │        │ Site                            │ End Time            │
├─────────┼─────────┼─────────────┼───────┤        ├─────────────────────────────────┼─────────────────────┤
│         │         │             │       │        │ wpscon-clone-400years           │ 2021-10-06 23:51:08 │
│   4     │   0     │   0         │   4   │        │ wpscon-clone-academic-senate-ob │ 2021-10-06 23:51:35 │
│         │         │             │       │        │ wpscon-clone-150w               │ 2021-10-06 23:52:46 │
└─────────┴─────────┴─────────────┴───────┘        │ wpscon-clone-access-ob          │ 2021-10-06 23:51:51 │
                                                   │ wpscon-clone-200                │ 2021-10-06 23:52:46 │
Overall Progress:                                  │ wpscon-clone-200                │ 2021-10-06 23:52:46 │
 4/4 sites complete [==================] 100%      │ wpscon-clone-200                │ 2021-10-06 23:52:46 │ 
                                                   │ wpscon-clone-200                │ 2021-10-06 23:52:46 │
┌──────── Errors  ─┬─────────────────────┐         │ wpscon-clone-200                │ 2021-10-06 23:52:46 │ 
│ Site             │ End Time            │         │ wpscon-clone-200                │ 2021-10-06 23:52:46 │
├──────────────────┼─────────────────────┤         │ wpscon-clone-200                │ 2021-10-06 23:52:46 │ 
│ wpscon-clone-100 │ 2021-10-06 23:51:08 │         │ wpscon-clone-200                │ 2021-10-06 23:52:46 │
│ wpscon-clone-101 │ 2021-10-06 23:51:35 │         │ wpscon-clone-200                │ 2021-10-06 23:52:46 │ 
│ wpscon-clone-102 │ 2021-10-06 23:52:46 │         │ wpscon-clone-200                │ 2021-10-06 23:52:46 │ 
│ wpscon-clone-102 │ 2021-10-06 23:51:51 │         │ wpscon-clone-200                │ 2021-10-06 23:52:46 │
└──────────────────┴─────────────────────┘         └──────────────────────Page 1/4───┴─────────────────────┘          

To make the best use of space in the user's terminal I would like to arrange tables side-by-side similar to the above example. I looked into the possibility of nesting these tables inside a parent table, but I'm not sure if that's possible.


Solution

  • The key is to use Symfony's BufferedOutput class. Here's an example in the form of a Symfony Console command:

    <?php
    
    namespace Wps\Console\Commands\Update;
    
    use Robo\Common\OutputAwareTrait;
    use Symfony\Component\Console\Helper\Table;
    use Symfony\Component\Console\Helper\TableStyle;
    use Symfony\Component\Console\Output\BufferedOutput;
    use Wps\Console\RawBufferedOutput\RawBufferedOutput;
    
    class NestedTablesCommand extends \Wps\Console\Commands\WpsCommandBase {
    
      use OutputAwareTrait;
    
      protected $outputBuffer;
    
      /**
       * @command nested
       */
      public function execute() {
        $this->setOutputBuffer();
        $this->parentTable();
      }
    
      protected function setOutputBuffer() {
        $this->outputBuffer = new RawBufferedOutput();
      }
    
      protected function parentTable() {
        $table = new Table($this->output);
    
        $tableStyle = $this->parentTable->getStyleDefinition('box');
        $tableStyle
          ->setPadType(STR_PAD_BOTH);
        $table->setStyle($tableStyle);
    
        $this->topLeftChildTable();
        $topRightChild = $this->outputBuffer->fetch();
        $this->bottomRightChildTable();
        $bottomRightChild = $this->outputBuffer->fetch();
    
        $table->setHeaderTitle('Parent Table');
        $table->setheaders(['Left', 'Right']);
        $rows = [
          [$topRightChild, 'r1'],
          ['l2', $bottomRightChild],
        ];
        $table->setRows($rows);
        $table->render();
      }
    
      protected function topLeftChildTable() {
        $table = new Table($this->outputBuffer);
        $table->setStyle('box');
        $tableStyle = new TableStyle();
        $tableStyle
          ->setPadType(STR_PAD_BOTH);
        $table->setHeaderTitle('TL Child');
        $table->setheaders(['Left', 'Right']);
        $rows = [
          ['L1', 'R1'],
          ['L2', 'R2'],
        ];
        $table->setRows($rows);
        $table->render();
      }
    
      protected function bottomRightChildTable() {
        $table = new Table($this->outputBuffer);
        $table->setStyle('box');
    
        $tableStyle = new TableStyle();
        $tableStyle
          ->setPadType(STR_PAD_BOTH);
        $table->setHeaderTitle('BR Child');
        $table->setheaders(['Left', 'Right']);
        $rows = [
          ['L1', 'R1'],
          ['L2', 'R2'],
        ];
        $table->setRows($rows);
        $table->render();
      }
    
    }
    

    In order to preserve the ANSI code decorations when we render the tables to the buffer, we need to extend BufferedOutput:

    <?php
    
    namespace Wps\Console\RawBufferedOutput;
    
    use Symfony\Component\Console\Output\Output;
    
    class RawBufferedOutput extends \Symfony\Component\Console\Output\BufferedOutput {
    
      public function write($messages, $newline = FALSE, $options = self::OUTPUT_NORMAL) {
        parent::write($messages, $newline, self::OUTPUT_RAW);
      }
    
      public function writeln($messages, $options = self::OUTPUT_NORMAL) {
        parent::writeln($messages, $options, self::OUTPUT_RAW);
      }
    
    }
    

    This renders:

    Screen shot of nested tables with ANSI-decorated table titles and column headings.