Preudographics

Php CLI how to draw table

The goal

Php shine whenever you work with a lot of text data. So php-cli applications are quite demandable. But for cli (command line interface) we do not have all this fancy html tags and css3 styles. Cli applications are intendent for advanced users, who often want output as stream, which can be directed to next tool in chain. So, in most cases writing results with simple echo works just fine.. unless you need to add a table to the output.

Solution

My first idea was to use ncurses, but.. there is much simpler solution.

All you need is str_repeat()

It’s also possible to use str_pad() in some cases. But, unfortunately, str_pad() does not support unicode.

The idea is to calculate max width for every column, and than fill all cells to this width with spaces. After that, just concatenate array with implode to display each table row.

Example of the following drawTable() function output:

php-cli table

Source code:

<?php

/**
 @param Array data - array of table rows
 @return String representing the table
 @example echo drawTable([
    [ "id"=>1, "name"=>"Test A", "description"=>"short text" ],
    [ "id"=>2, "name"=>"Test B", "description"=>"long text, describing something" ],
    [ "id"=>3, "name"=>"Test C", "description"=>"long text with multibyte string ä ë ï ö ü" ],
    [ "id"=>4, "name"=>"Test ö", "description"=>"myltybyte in name column" ],
    [ "id"=>5, "name"=>"Test E", "description"=>"weeeee!!! äëïöü äëïöü äëïöü äëïöü äëïöü äëïöü" ],
 ]);
 */
function drawTable(Array $data):String {
    // sanity check
    if(empty($data)) { return ""; } 
    // get column names
    $keys = array_keys( reset($data) );
    // calculate maximum width for each column
    $columnWidths = [];
    foreach($keys as $key) {
        $columnWidths[$key] = mb_strlen($key);
        foreach($data as $item) {
            $width = mb_strlen($item[$key]);
            if( $width > $columnWidths[$key] ) {
                $columnWidths[$key] = $width;
            }
        }
    }
    $columnNames = $horizontalLines = [];
    foreach( $keys as $key ) {
        // fill column titles with spaces
        $columnNames[$key] = $key.str_repeat(" ",$columnWidths[$key] - mb_strlen($key)); 
        // prepare horisontal lines
        $horizontalLines[$key] =  str_repeat("─",$columnWidths[$key]); 
    }
    // fill every cell to max column width
    $data = array_map(function($row) use ($columnWidths) {
        array_walk($row, function(&$cell, $key) use ($columnWidths){
            $cell .= str_repeat(" ", $columnWidths[$key] - mb_strlen($cell));
        });
        return $row;
    }, $data);
    ob_start();
    // draw table header
    echo "╭─".implode("─┬─", $horizontalLines)."─╮\n";
    echo "│ ".implode(" │ ", $columnNames    )." │\n";
    echo "├─".implode("─┼─", $horizontalLines)."─┤\n";
    // draw lines
    foreach( $data as $row ) {
        echo "│ ".implode(" │ ", $row)." │\n";        
    }
    // draw bottom line
    echo "╰─".implode("─┴─", $horizontalLines)."─╯\n";
    return ob_get_clean();
}

Note: This solution draw to string buffer, not stdout. For actual display use echo drawTable(...)

Of cause, you can just print it to stdout directly, without ob_start(), ob_get_clean() and use different symbols for table borders.

Here, take some:

    ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
    ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
    ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
    ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
    ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
    ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
    ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯
    ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿