Skip to main content
Added version to title
Link
julio
  • 229
  • 2
  • 9

PHP Class to render HTML div styled tables v1

Source Link
julio
  • 229
  • 2
  • 9

PHP Class to render HTML div styled tables

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 "&nbsp;" 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;
    }

}