This class aims to render an HTML compliant div styled table. I added a help()
method that provides usage and styling in a more user-friendly way. Besides the review of the code and every advice I will receive I would like to hear your thoughts about the fact __construct()
method taking all arguments. Thanks in advance.
<?php
/**
*
* This class produces a DIV styled table in HTML
*
* @author julio
* @date 04/05/2023
*
* IMPORTANT NOTE: this class is written for PHP 7.0.33. Even though I tried to
* accomplish as much as possible PSR-12 and standard conventions. So you may
* notice that some methods return a bool value when it could be a void value.
* Also, I am not allowed to set visibility for class constants. Among others.
* However, I check my codes in webcodesniffer.net to accomplish PSR-12.
*
* Upon instantiation this class can take the following optional parameters:
*
* - string $orientation: sets table orientation: "hz" (default) for
* horizontal or "vt" for vertical
*
* - string $table_title: the title for the table
*
* - array $headers: a simple array where each element is a column header
*
* - array $data: a multidimensional array where first level is each row
* and each subarray contains cells data. Note that first level keys are
* ignored so they are not used and are dismissed
*
* - array $footers: a simple array where each element is a column footer
*
* - int $tabs: the number of tabs (4 spaces) for the first element. Subsequent
* elements are indented properly to provide a correctly indented output
*
*
* Each parameter can be set explicitly with their corresponding method.
*
* When $headers are set, the number of headers will be the number of columns.
* Otherwise the number of columns will be set to the number of elements of
* first $data subarray. Any data beyond the number of columns will be
* dismissed.
*
* There is a help() method that prints the basic usage and styling.
*
* Full copy-paste example:
$title = "Testing Table";
$orientation = "hz";
$headers = ["Col1", "Col2", "Col3", "Col4", "Col5"];
$data = array(
"r1" => ["dato11", "dato12", "dato13"],
"r2" => ["dato21", "dato22", "", "dato24"],
"r3" => ["dato31", "dato32", "dato33", "dato34", "dato35"],
"r4" => ["dato41"]
);
$footers = ["", "foot2", "", "foot4"];
$tabs = 3;
$html_classes = [];
require("Table.php");
$table = new Table();
$table->setTitle($title);
$table->setOrientation($orientation);
$table->setHeaders($headers);
$table->setData($data);
$table->setFooters($footers);
$table->setTabs($tabs);
$html = $table->get($html_classes);
echo $html;
* This example should output something like this:
<div class='table'>
<div class='table-title'>Testing Table</div>
<div class='table-header'>
<div class='table-header-cell'>Col1</div>
<div class='table-header-cell'>Col2</div>
<div class='table-header-cell'>Col3</div>
<div class='table-header-cell'>Col4</div>
<div class='table-header-cell'>Col5</div>
</div>
<div class='table-body'>
<div class='table-row'>
<div class='table-row-cell'>dato11</div>
<div class='table-row-cell'>dato12</div>
<div class='table-row-cell'>dato13</div>
<div class='table-row-cell'></div>
<div class='table-row-cell'></div>
</div>
<div class='table-row'>
<div class='table-row-cell'>dato21</div>
<div class='table-row-cell'>dato22</div>
<div class='table-row-cell'></div>
<div class='table-row-cell'>dato24</div>
<div class='table-row-cell'></div>
</div>
<div class='table-row'>
<div class='table-row-cell'>dato31</div>
<div class='table-row-cell'>dato32</div>
<div class='table-row-cell'>dato33</div>
<div class='table-row-cell'>dato34</div>
<div class='table-row-cell'>dato35</div>
</div>
<div class='table-row'>
<div class='table-row-cell'>dato41</div>
<div class='table-row-cell'></div>
<div class='table-row-cell'></div>
<div class='table-row-cell'></div>
<div class='table-row-cell'></div>
</div>
</div>
<div class='table-footer'>
<div class='table-footer-cell'></div>
<div class='table-footer-cell'>foot2</div>
<div class='table-footer-cell'></div>
<div class='table-footer-cell'>foot4</div>
<div class='table-footer-cell'></div>
</div>
</div>
<h4>CSS3 example for styling the table</h4>
<pre>
<code>
.table {
width:100%;
display:table;
}
.table-title {
display: table-caption;
text-align: center;
font-size: 20px;
font-weight: bold;
}
.table-header {
display: table-header-group;
background-color: gray;
font-weight: bold;
font-size: 15px;
background-color: #D1D1D1;
}
.table-header-cell {
display: table-cell;
padding:10px;
border-bottom:1px solid black;
font-weight:bold;
text-align:justify;
}
.table-body { display:table-row-group; }
.table-row { display:table-row; }
.table-row-cell {
display:table-cell;
padding:5px 10px 5px 10px;
}
.table-footer {
display: table-footer-group;
background-color: gray;
font-weight: bold;
font-size: 15px;
color:#FFF;
}
.table-footer-cell {
display: table-cell;
padding: 10px;
text-align: justify;
border-bottom: 1px solid black;
}
.table-row:hover { background-color:#d9d9d9; }
</code>
</pre>
* You can also achieve the same results with:
require("Table.php");
$table = new Table($orientation, $title, $headers, $data, $footers, $tabs);
$html = $table->get($html_classes);
echo $html;
* As you can see, any cell can be left empty.
* You can also add HTML markup.
*
*/
class Table
{
private $orientation = "hz"; // "vt" not implemented yet
private $table_title = "";
private $headers = array();
private $data = array();
private $footers = array();
private $tabs = 0;
public function __construct(
string $orientation = "",
string $table_title = "",
array $headers = [],
array $data = [],
array $footers = [],
int $tabs = 0
) {
if (strlen($orientation) !== 0) {
$this->setOrientation($orientation);
}
if (strlen($table_title) !== 0) {
$this->setTitle($table_title);
}
if (count($headers) > 0) {
$this->setHeaders($headers);
}
if (count($data) > 0) {
$this->setData($data);
}
if (count($footers) > 0) {
$this->setFooters($footers);
}
if ($tabs !== 0) {
$this->setTabs($tabs);
}
}
/**
* Set table orientation
* @param string $orientation "hz" for horizontal (default), "vt" for vertical
* @return bool
*/
public function setOrientation(string $orientation = "hz"): bool
{
if ($orientation === "vt") {
$this->orientation = "vt";
}
return true;
}
public function setTitle(string $table_title = ""): bool
{
if (strlen($table_title) !== 0) {
$this->table_title = $table_title;
}
return true;
}
public function setHeaders(array $headers = []): bool
{
if (count($headers) > 0) {
if (count($headers) !== count($headers, COUNT_RECURSIVE)) {
throw new Exception("Headers array should be a simple array");
}
$this->headers = $headers;
}
return true;
}
/**
* Sets the table cells data
* @param array $data Multidimensional non-associative array
* @return bool
*
* This function takes a multidimensional array as parameter where each
* key is an array where each element is a cell data.
*
* Empty cells are filled with " " to keep table format from breaking.
*
* So, the array format:
*
* array(
* "header1" => ["data11", "data12", "data13", ...],
* "header2" => ["data21", "data22", "data23", ...],
* ...
* )
*
* Note that first level keys can be indexed, so this is also valid:
*
* array(
* ["data11", "data12", "data13", ...],
* ["data21", "data22", "data23", ...],
* ...
* )
*
*/
public function setData(array $data = []): bool
{
$this->checkData($data);
$this->data = $data;
return true;
}
private function checkData(array $data = []): bool
{
if (count($data) < 1) {
throw new Exception("With no data there is no table to render");
}
if (count($data) === count($data, COUNT_RECURSIVE)) {
throw new Exception("Table data array has the wrong format: it must be a multidimensional array");
}
return true;
}
public function setFooters(array $footers = []): bool
{
if (count($footers) > 0) {
if (count($footers) !== count($footers, COUNT_RECURSIVE)) {
throw new Exception("Footers should be a simple array");
}
$this->footers = $footers;
}
return true;
}
/**
* Sets the number of tabs for first element so indentation is correct
* Note that 1 tab is considered as 4 spaces
* @param int $tabs
* @return bool
*/
public function setTabs(int $tabs = 0): bool
{
$this->tabs = ($tabs > 0) ? $tabs : 0;
return true;
}
/**
* Renders the table
*
* @param array $html_attrs [optional] Associative array with HTML
* classes and table id
* @return string
*
* This method takes un optional array as parameter which holds
* HTML ids and classes for rows, columns and cells.
* It can take none or any of the following keys:
*
* table_id: HTML id for table container.
* table_class: HTML class for table container. Mandatory.
* title_class: HTML class for title. Mandatory.
* header_class: HTML class for header row. Mandatory.
* header_cell_class: HTML class for header cells. Mandatory.
* body_class: HTML class for table body. Mandatory.
* row_class: HTML class for rows. Mandatory.
* row_cell_class: HTML class for row's cells. Mandatory.
* footer_class: HTML class for footer. Mandatory
* footer_cell_class: HTML class for footer cells. Mandatory.
*
*
*/
public function get(array $html_classes = []): string
{
$this->checkData($this->data);
$output = null;
/**
* When orientation is horizontal (hz) column count is set by
* the number of headers. When vertical (vt) column count in set by
* the number of elements of first subarray
* @var integer $col_count
*/
$col_count = 0;
$counter = 1;
if (
array_key_exists("table_id", $html_classes) &&
(strlen($html_classes['table_id']) !== 0)
) {
$html_table_id = $html_classes['table_id'];
} else {
$html_table_id = "";
}
// mandatory
if (
array_key_exists("table_class", $html_classes) &&
(strlen($html_classes['table_class']) !== 0)
) {
$html_table_class = $html_classes['table_class'];
} else {
$html_table_class = "table";
}
// mandatory
if (
array_key_exists("title_class", $html_classes) &&
(strlen($html_classes['title_class']) !== 0)
) {
$html_title_class = $html_classes['title_class'];
} else {
$html_title_class = "table-title";
}
// mandatory
if (
array_key_exists("header_class", $html_classes) &&
(strlen($html_classes['header_class']) !== 0)
) {
$html_header_class = $html_classes['header_class'];
} else {
$html_header_class = "table-header";
}
// mandatory
if (
array_key_exists("header_cell_class", $html_classes) &&
(strlen($html_classes['header_cell_class']) !== 0)
) {
$html_header_cell_class = $html_classes['header_cell_class'];
} else {
$html_header_cell_class = "table-header-cell";
}
// mandatory
if (
array_key_exists("body_class", $html_classes) &&
(strlen($html_classes['body_class']) !== 0)
) {
$html_table_body_class = $html_classes['body_class'];
} else {
$html_table_body_class = "table-body";
}
// mandatory
if (
array_key_exists("row_class", $html_classes) &&
(strlen($html_classes['row_class']) !== 0)
) {
$html_row_class = $html_classes['row_class'];
} else {
$html_row_class = "table-row";
}
// mandatory
if (
array_key_exists("row_cell_class", $html_classes) &&
(strlen($html_classes['row_cell_class']) !== 0)
) {
$html_cell_class = $html_classes['row_cell_class'];
} else {
$html_cell_class = "table-row-cell";
}
// mandatory
if (
array_key_exists("footer_class", $html_classes) &&
(strlen($html_classes['footer_class']) !== 0)
) {
$html_footer_class = $html_classes['footer_class'];
} else {
$html_footer_class = "table-footer";
}
// mandatory
if (
array_key_exists("footer_cell_class", $html_classes) &&
(strlen($html_classes['footer_cell_class']) !== 0)
) {
$html_footer_cell_class = $html_classes['footer_cell_class'];
} else {
$html_footer_cell_class = "table-footer-cell";
}
// open table: begin
$output = "\n" . str_repeat(" ", $this->tabs) . "<div";
if (strlen($html_table_id) != 0) {
$output .= " id='$html_table_id'";
}
$output .= " class='$html_table_class'";
$output .= ">";
// open table: end
if (strlen($this->table_title) !== 0) {
$output .= "\n" . str_repeat(" ", $this->tabs + 1) . "<div class='$html_title_class'>" .
$this->table_title . "</div>";
}
if ($this->orientation === "hz") {
/**
* column count: if $this->headers is set it takes its count.
* Otherwise it takes the count of first data subarray
*/
if (count($this->headers) > 0) {
$col_count = count($this->headers);
// header row: begin
$output .= "\n" . str_repeat(" ", $this->tabs + 1) . "<div class='$html_header_class'>";
foreach ($this->headers as $header) {
$output .= "\n" . str_repeat(" ", $this->tabs + 2) . "<div class='$html_header_cell_class'>$header</div>";
}
$output .= "\n" . str_repeat(" ", $this->tabs + 1) . "</div>";
// header row: end
} else {
$col_count = count($this->data[array_keys($this->data)[0]]);
}
// open table body
$output .= "\n" . str_repeat(" ", $this->tabs + 1) . "<div class='$html_table_body_class'>";
// process cells
foreach ($this->data as $data) {
// open row
$output .= "\n" . str_repeat(" ", $this->tabs + 2) . "<div class='$html_row_class'>";
$counter = 1;
foreach ($data as $cell_data) {
if ($counter <= $col_count) {
$output .= "\n" . str_repeat(" ", $this->tabs + 3) . "<div class='$html_cell_class'>";
$output .= $cell_data ?? ""; // <--
$output .= "</div>";
$counter++;
}
}
// complete empty cells to preserve row:hover on full row
while ($counter <= $col_count) {
$output .= "\n" . str_repeat(" ", $this->tabs + 3) . "<div class='$html_cell_class'>";
$output .= ""; // <--
$output .= "</div>";
$counter++;
}
// close row
$output .= "\n" . str_repeat(" ", $this->tabs + 2) . "</div>";
}
// close table body
$output .= "\n" . str_repeat(" ", $this->tabs + 1) . "</div>";
// footer: begin
$counter = 1;
if (count($this->footers) > 0) {
$output .= "\n" . str_repeat(" ", $this->tabs + 1) . "<div class='$html_footer_class'>";
foreach ($this->footers as $footer_cell) {
$output .= "\n" . str_repeat(" ", $this->tabs + 2) . "<div class='$html_footer_cell_class'>";
$output .= (strlen($footer_cell) != 0) ? $footer_cell : ""; // <--
$output .= "</div>";
$counter++;
}
// complete empty footer cells to have footer as long as table
while ($counter <= $col_count) {
$output .= "\n" . str_repeat(" ", $this->tabs + 2) . "<div class='$html_footer_cell_class'>";
$output .= ""; // <--
$output .= "</div>";
$counter++;
}
$output .= "\n" . str_repeat(" ", $this->tabs + 1) . "</div>";
}
// footer: end
}
// close table
$output .= "\n" . str_repeat(" ", $this->tabs) . "</div>";
return $output;
}
/**
* Renders a sample table where you can see the HTML generated code and
* the CSS3 styling
* @return string
*/
public function help(): string
{
$output = <<<'EOT'
<h1>Class Table usage</h1>
<h2>How to render a table</h2>
<p>There are two ways for rendering a table. You can pass all arguments
when the class is instantiated or you can call the desired method. Below
you have two examples that render the exact same HTML code:</p>
<pre>
<code>
// parameter setting
$title = "Testing Table";
$orientation = "hz";
$headers = ["Col1", "Col2", "Col3", "Col4", "Col5"];
$data = array(
"r1" => ["dato11", "dato12", "dato13"],
"r2" => ["dato21", "dato22", "", "dato24"],
"r3" => ["dato31", "dato32", "dato33", "dato34", "dato35"],
"r4" => ["dato41"]
);
$footers = ["", "foot2", "", "foot4"];
$tabs = 3;
$html_classes = [];
// Method 1:
require("Table.php");
$table = new Table($orientation, $title, $headers, $data, $footers, $tabs);
$html = $table->get($html_classes);
echo $html;
// Method 2:
require("Table.php");
$table = new Table();
$table->setTitle($title);
$table->setOrientation($orientation);
$table->setHeaders($headers);
$table->setData($data);
$table->setFooters($footers);
$table->setTabs($tabs);
$html .= $table->get($html_classes);
echo $html;
</code>
</pre>
<h3>Output:</h3>
<pre>
<code>
EOT;
$output .= htmlspecialchars("
<div class='table'>
<div class='table-title'>Testing Table</div>
<div class='table-header'>
<div class='table-header-cell'>Col1</div>
<div class='table-header-cell'>Col2</div>
<div class='table-header-cell'></div>
<div class='table-header-cell'>Col4</div>
<div class='table-header-cell'>Col5</div>
</div>
<div class='table-body'>
<div class='table-row'>
<div class='table-row-cell'>dato11</div>
<div class='table-row-cell'>dato12</div>
<div class='table-row-cell'>dato13</div>
<div class='table-row-cell'></div>
<div class='table-row-cell'></div>
</div>
<div class='table-row'>
<div class='table-row-cell'>dato21</div>
<div class='table-row-cell'>dato22</div>
<div class='table-row-cell'></div>
<div class='table-row-cell'>dato24</div>
<div class='table-row-cell'></div>
</div>
<div class='table-row'>
<div class='table-row-cell'>dato31</div>
<div class='table-row-cell'>dato32</div>
<div class='table-row-cell'>dato33</div>
<div class='table-row-cell'>dato34</div>
<div class='table-row-cell'>dato35</div>
</div>
<div class='table-row'>
<div class='table-row-cell'>dato41</div>
<div class='table-row-cell'></div>
<div class='table-row-cell'></div>
<div class='table-row-cell'></div>
<div class='table-row-cell'></div>
</div>
</div>
<div class='table-footer'>
<div class='table-footer-cell'></div>
<div class='table-footer-cell'>foot2</div>
<div class='table-footer-cell'></div>
<div class='table-footer-cell'>foot4</div>
<div class='table-footer-cell'></div>
</div>
</div>");
$output .= <<<'EOT'
</code>
</pre>
<h2>The CSS3 styling</h2>
<p>This code allows you to style the table. Take into consideration
that HTML classes, if changed, should be updated. Read forward to
learn how to set your own HTML classes</p>
<pre>
<code>
.tabla, .table {
width:100%;
display:table;
}
.tabla-titulo, .table-title {
display: table-caption;
text-align: center;
font-size: 20px;
font-weight: bold;
}
.tabla-encabezado, .table-header {
display: table-header-group;
background-color: gray;
font-weight: bold;
font-size: 15px;
background-color: #D1D1D1;
}
.tabla-celda-encabezado, .table-header-cell {
display: table-cell;
padding:10px;
border-bottom:1px solid black;
font-weight:bold;
text-align:justify;
}
.tabla-body, .table-body { display:table-row-group; }
.tabla-fila, .table-row { display:table-row; }
.tabla-fila-celda, .table-row-cell {
display:table-cell;
padding:5px 10px 5px 10px;
}
.tabla-footer, .table-footer {
display: table-footer-group;
background-color: gray;
font-weight: bold;
font-size: 15px;
color:#FFF;
}
.tabla-footer-celda, .table-footer-cell {
display: table-cell;
padding: 10px;
text-align: justify;
border-bottom: 1px solid black;
}
.tabla-fila:hover, .table-row:hover { background-color:#d9d9d9; }
</code>
</pre>
<h2>How to set your own HTML classes</h2>
<p>Classes for table elements are passed to get() method. It must be an array that
that has none or any of the following keys:</p>
<pre>
<code>
table_id: HTML id for table container.
table_class: HTML class for table container. Mandatory.
title_class: HTML class for title. Mandatory.
header_class: HTML class for header row. Mandatory.
header_cell_class: HTML class for header cells. Mandatory.
body_class: HTML class for table body. Mandatory.
row_class: HTML class for rows. Mandatory.
row_cell_class: HTML class for row's cells. Mandatory.
footer_class: HTML class for footer. Mandatory
footer_cell_class: HTML class for footer cells. Mandatory.
</code>
</pre>
<p>So if you want to specify your own classes, you should do something like this:</p>
<pre>
<code>
$html_classes = ["table_id"=>"mytable01", "table_class"=>"mytable-class", ...];
$table->get($html_classes);
</code>
</pre>
<p>When HTML classes are not present, defaults will be used (the ones in the styling example).</p>
<br>
<br>
<p>If you need further information read the class' docblock's</p>
EOT;
return $output;
}
}
Table.php
file. And because I am curious: Why use all div tags instead of the normal table tags? Is there an advantage to that? Or is it just because you can do it? \$\endgroup\$Table.php
. Isn't it supposed to be that way? I personally don't like<table>
and also yes, because I can do it. I really don't know if there's an advantage. This way suits my needs. \$\endgroup\$