laravel / ideas

Issues board used for Laravel internals discussions.
938 stars 28 forks source link

Make it possible to append rows to a rendered table in a console command. #2064

Open freekmurze opened 4 years ago

freekmurze commented 4 years ago

Symfony provides a handy Table console helper that allows rows to be appended to an already rendered table.

Unfortunately, it's currently impossible to use this method in Laravel.

When newing up Symfony\Component\Console\Helper\Table it requires a ConsoleSectionOutput to be passed it.

A fully constructed ConsoleSectionOutput is available when calling section() on Symfony\Component\Console\Output\ConsoleOutputInterface. An object that implements this interface is available on Illuminate\Console\Command as $output. Unfortunately, this property is marked as protected.

In my project, I've just made this property public so I can just reach in and get the section.

// in a command

$section = $this->output->output->section();

$this->table = new Table($section);

$this->table->setHeaders(['Column', 'Another column']);
$this->table->render();

$this->table->appendRow(['Value', 'Another Value']);

I'm not advocating making this property public, but rather for an easy way to be able to use appendRow.

crynobone commented 4 years ago
<?php

namespace Illuminate\Console;

use Illuminate\Support\Traits\ForwardsCalls;

class OutputStyle 
{
    use ForwardsCalls;

    // ...

    public function __call($method, $parameters) {
        return $this->forwardCallTo($this->output, $method, $parameters);
    }
}

This would allow you to use as long as it being executed inside Illuminate\Console\Command:

$section = $this->output->section();

$this->table = new Table($section);

$this->table->setHeaders(['Column', 'Another column']);
$this->table->render();

$this->table->appendRow(['Value', 'Another Value']);

Unfortunately Illuminate\Console\OutputStyle also have a section method so it can't forward the call automatically.

adam-prickett commented 4 years ago
<?php

namespace Illuminate\Console;

class OutputStyle extends SymfonyStyle
{
    // ...
+    /**
+     * Get the underlying Symfony output implementation.
+     *
+     * @return \Symfony\Component\Console\Output\OutputInterface
+     */
+    public function getOutput()
+    {
+        return $this->output;
+    }
}

This gives us access to the Symfony\Component\Console\Output\ConsoleOutput instance.

<?php

namespace Illuminate\Console\Concerns;

trait InteractsWithIO
{
    // ...

    /**
     * Format input to textual table.
     *
     * @param  array  $headers
     * @param  \Illuminate\Contracts\Support\Arrayable|array  $rows
     * @param  string  $tableStyle
     * @param  array  $columnStyles
-     * @return void
+     * @return \Symfony\Component\Console\Helper\Table
     */
    public function table($headers, $rows, $tableStyle = 'default', array $columnStyles = [])
    {
-        $table = new Table($this->output);
+        $table = new Table($this->output->getOutput()->section());

        if ($rows instanceof Arrayable) {
            $rows = $rows->toArray();
        }

        $table->setHeaders((array) $headers)->setRows($rows)->setStyle($tableStyle);

        foreach ($columnStyles as $columnIndex => $columnStyle) {
            $table->setColumnStyle($columnIndex, $columnStyle);
        }

        $table->render();
+
+        return $table;
    }

    // ...
}

Now, then creating a table, the Table instance is returned, allowing us to call appendRow()

public function handle()
{
    $table = $this->table(
        ['Column', 'Another column'],
        []
    );

    $table->appendRow(['Value', 'Another Value']);
    $table->appendRow(['Value', 'Another Value']);
}
+--------+----------------+
| Column | Another column |
+--------+----------------+
| Value  | Another Value  |
| Value  | Another Value  |
+--------+----------------+
freekmurze commented 4 years ago

@adam-prickett You should PR that to the framework. I you won't, I'll do it 😄

adam-prickett commented 4 years ago

@freekmurze On the way!

renepardon commented 3 years ago

This ticket is still open and I think for a reason. grafik

I want to display multiple progress bars and update them individually based on related actions. If I set this aforementioned property to public, it works perfectly. So a getter Method would be really nice instead. Did someone already create a pull request in the Symfony project?