8

I have some data like:

Array
(
    [0] => Array
        (
            [a] => largeeeerrrrr
            [b] => 0
            [c] => 47
            [d] => 0
        )

    [1] => Array
        (
            [a] => bla
            [b] => 1
            [c] => 0
            [d] => 0
        )

    [2] => Array
        (
            [a] => bla3
            [b] => 0
            [c] => 0
            [d] => 0
        )

)

And I want to produce an output like:

title1        | title2 | title3 | title4
largeeeerrrrr | 0      | 47     | 0
bla           | 1      | 0      | 0
bla3          | 0      | 0      | 0

Which is the simples way to achieve this in PHP? I'd like to avoid using a library for such simple task.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Juanjo Conti
  • 28,823
  • 42
  • 111
  • 133
  • In steps: 1) find the longest column size you need for each column (you may need to use some of PHP's array functions) 2) print the headers, right-padding each header with spaces 3) add `|` 4) print a row, right-padding each value with spaces 5) repeat #4 for every row – BoltClock Feb 22 '11 at 18:29
  • Possible duplicate of [PHP ASCII Table Library](https://stackoverflow.com/questions/1225325/php-ascii-table-library) – azerafati Jul 19 '18 at 14:21

7 Answers7

11

use printf

$i=0;
foreach( $itemlist as $items)
{
 foreach ($items as $key => $value)
 {
   if ($i++==0) // print header
   {
     printf("[%010s]|",   $key );
     echo "\n";
   }
   printf("[%010s]|",   $value);
 }
 echo "\n"; // don't forget the newline at the end of the row!
}

This uses 10 padding spaces. As BoltClock says, you probably want to check the length of the longest string first or your table will be jacked up on long strings.

Anthony Hatzopoulos
  • 10,437
  • 2
  • 40
  • 57
Byron Whitlock
  • 52,691
  • 28
  • 123
  • 168
7

Another library with auto column widths.

 <?php
 $renderer = new ArrayToTextTable($array);
 echo $renderer->getTable();
Sony
  • 1,773
  • 3
  • 23
  • 39
5

I know this question is very old, but it appears in my google search and maybe it helps someone.

There's another Stackoverflow question with good answers, especially this one that points to a Zend Framework module called Zend/Text/Table.

Hope it help.


Docs introduction

Zend\Text\Table is a component for creating text-based tables on the fly using decorators. This can be helpful for sending structured data in text emails, or to display table information in a CLI application. Zend\Text\Table supports multi-line columns, column spans, and alignment.


Basic usage

$table = new Zend\Text\Table\Table(['columnWidths' => [10, 20]]);

// Either simple
$table->appendRow(['Zend', 'Framework']);

// Or verbose
$row = new Zend\Text\Table\Row();

$row->appendColumn(new Zend\Text\Table\Column('Zend'));
$row->appendColumn(new Zend\Text\Table\Column('Framework'));

$table->appendRow($row);

echo $table;
Output
┌──────────┬────────────────────┐
│Zend      │Framework           │
|──────────|────────────────────|
│Zend      │Framework           │
└──────────┴────────────────────┘
Community
  • 1
  • 1
fernandosavio
  • 9,849
  • 4
  • 24
  • 34
2

If you don't want to use any external libraries, this is a simple class that does the job

class StringTools
{
  public static function convertForLog($variable) {
    if ($variable === null) {
      return 'null';
    }
    if ($variable === false) {
      return 'false';
    }
    if ($variable === true) {
      return 'true';
    }
    if (is_array($variable)) {
      return json_encode($variable);
    }
    return $variable ? $variable : "";
  }

  public static function toAsciiTable($array, $fields, $wrapLength) {
    // get max length of fields
    $fieldLengthMap = [];
    foreach ($fields as $field) {
      $fieldMaxLength = 0;
      foreach ($array as $item) {
        $value = self::convertForLog($item[$field]);
        $length = strlen($value);
        $fieldMaxLength = $length > $fieldMaxLength ? $length : $fieldMaxLength;
      }
      $fieldMaxLength = $fieldMaxLength > $wrapLength ? $wrapLength : $fieldMaxLength;
      $fieldLengthMap[$field] = $fieldMaxLength;
    }

    // create table
    $asciiTable = "";
    $totalLength = 0;
    foreach ($array as $item) {
      // prepare next line
      $valuesToLog = [];
      foreach ($fieldLengthMap as $field => $maxLength) {
        $valuesToLog[$field] = self::convertForLog($item[$field]);
      }

      // write next line
      $lineIsWrapped = true;
      while ($lineIsWrapped) {
        $lineIsWrapped = false;
        foreach ($fieldLengthMap as $field => $maxLength) {
          $valueLeft = $valuesToLog[$field];
          $valuesToLog[$field] = "";
          if (strlen($valueLeft) > $maxLength) {
            $valuesToLog[$field] = substr($valueLeft, $maxLength);
            $valueLeft = substr($valueLeft, 0, $maxLength);
            $lineIsWrapped = true;
          }
          $asciiTable .= "| {$valueLeft} " . str_repeat(" ", $maxLength - strlen($valueLeft));
        }
        $totalLength = $totalLength === 0 ? strlen($asciiTable) + 1 : $totalLength;
        $asciiTable .= "|\n";
      }
    }

    // add lines before and after
    $horizontalLine = str_repeat("-", $totalLength);
    $asciiTable = "{$horizontalLine}\n{$asciiTable}{$horizontalLine}\n";
    return $asciiTable;
  }
}

This is an example of how to use it and below is the result on the terminal

public function handle() {
  $array = [
      ["name" => "something here", "description" => "a description here to see", "value" => 3],
      ["name" => "and a boolean", "description" => "this is another thing", "value" => true],
      ["name" => "a duck and a dog", "description" => "weird stuff is happening", "value" => "truly weird"],
      ["name" => "with rogue field", "description" => "should not show it", "value" => false, "rogue" => "nie"],
      ["name" => "some kind of array", "description" => "array i tell you", "value" => [3, 4, 'banana']],
      ["name" => "can i handle null?", "description" => "let's see", "value" => null],
  ];
  $table = StringTools::toAsciiTable($array, ["name", "value", "description"], 50);
  print_r($table);
}

table on terminal

Stefanos Kargas
  • 10,547
  • 22
  • 76
  • 101
1

In addition to Byron Whitlock: You can use usort() with a callback to sort by longest array value. Example:

function lengthSort($a, $b){
    $a = strlen($a);
    $b = strlen($b);
    if ($a == $b) {
        return 0;
    }
    return ($a < $b) ? -1 : 1;
}
Ray
  • 1,192
  • 6
  • 10
1

For anyone interested in a more general solution, here's a function I'm using in my own code to format tabular data of the form:

$data = [
  ['Header 1', 'Header 2'],
  ['Row1Cell1', 'Row1Cell2'],
  ['Row2Cell1', 'Row2Cell2'],
];

To get:

┌───────────────────────┐
│ Header 1  │ Header 2  │
├───────────────────────┤
│ Row1Cell1 │ Row1Cell2 │
│ Row2Cell1 │ Row2Cell2 │
└───────────────────────┘

The code:

// needed because str_pad doesn't play nice with Unicode
// https://stackoverflow.com/a/67708895/337554
// https://bugs.php.net/bug.php?id=21317
public static function pad(string $string, int $length, string $pad_string = " "): string
{
    return $string . str_repeat($pad_string, $length - mb_strlen($string));
}

public static function asciiTable(array $rows): string
{
    if (count($rows) === 0) {
        return '';
    }

    $widths = [];
    foreach ($rows as $cells) {
        foreach ($cells as $j => $cell) {
            if (($width = strlen($cell) + 2) >= ($widths[$j] ?? 0)) {
                $widths[$j] = $width;
            }
        }
    }

    $hBar = str_repeat('─', array_sum($widths) + count($widths) - 1);
    $topB = sprintf("┌%s┐", $hBar);
    $midB = sprintf("├%s┤", $hBar);
    $botB = sprintf("└%s┘", $hBar);

    $result[] = $topB;
    $fn = fn(string $c, int $w): string => self::pad(" {$c} ", $w);
    foreach ($rows as $i => $cells) {
        $result[] = sprintf("│%s│", implode('│', array_map($fn, $cells, $widths)));
        if ($i === 0) {
            $result[] = $midB;
        }
    }
    $result[] = $botB;

    return implode("\n", $result);
}
Andrei
  • 1,723
  • 1
  • 16
  • 27
  • this is nice, just a note: usually data comes out of the database with column data keyed by the column name, so in order to get this code to work with that you have to prepend with a header row: `$data = array_prepend($data, array_combine(array_keys($data[0]), array_keys($data[0])));` – dave Jul 21 '23 at 06:04
0

I liked Bryon's answer but I wanted each column to be length-aware so this is what I came up with with plain PHP/no package dependency.

// set up the data set you specified
$data = [
    [ 'title1' => 'largeeeerrrrr', 'title2' => 0, 'title3' => 47, 'title4' => 0 ],
    [ 'title1' => 'bla', 'title2' => 1, 'title3' => 0, 'title4' => 0 ],
    [ 'title1' => 'bla3', 'title2' => 0, 'title3' => 0, 'title4' => 0 ]
];

// parse array to make it easier to use
foreach ($data as $i => $lines)
{
    foreach ($lines as $key => $value) {
        $columns[$key][0] = $key;
        $columns[$key][] = $value;
        $rows[$i][] = $value;
    }
}
$rows = array_prepend($rows, array_keys($columns));
$lengths = array_values(array_map(fn($x) => max(array_map('strlen', $x)) , $columns));

// output ascii table
foreach ($rows as $row) {
    foreach ($row as $key => $data) {
        $length = $lengths[$key];
        $data = (is_bool($data)) ? (($data) ? 'true' : 'false') : $data;

        echo str_pad($data, $length, ' ', STR_PAD_RIGHT) . ' | ';
    }
    echo "\n";
}

output (exactly what op had wanted, but pretty easy to customize):

title1        | title2 | title3 | title4 | 
largeeeerrrrr | 0      | 47     | 0      | 
bla           | 1      | 0      | 0      | 
bla3          | 0      | 0      | 0      |
dave
  • 2,288
  • 4
  • 23
  • 20