2
\$\begingroup\$

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;
    }

}
\$\endgroup\$
3
  • \$\begingroup\$ Can you please split the code in your question into parts and mention their file names? For instance, there should be a 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\$ Commented May 8, 2023 at 17:00
  • \$\begingroup\$ @KIKOSoftware, eemm, it's only one file... yes, its name is the same: 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\$
    – julio
    Commented May 8, 2023 at 17:12
  • \$\begingroup\$ I took a closer look at your code. I now see that I read the code in your comment as real code, and that's why I thought your question contained multiple code blocks. \$\endgroup\$ Commented May 10, 2023 at 23:04

1 Answer 1

4
\$\begingroup\$

I'm not overly bothered by the amount of arguments in your __construct() method. It looks fine to me.

There are however some classic coding problems:

Repetition

There is a lot of repeated code in your class. Repetition is bad because it makes your code unnecessarily long. There must be a way to program this more elegantly. For instance, this code is repeated 10 times in get():
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";
}

You could create a separate method for this:

private function getClass($classes, $className, $defaultClass)
{
    if (array_key_exists($className, $classes) &&
        (strlen($classes[$className]) !== 0)) {
        return $classes[$className];
    } else {
        return $defaultClass;
    }
}

Which can then be used as:

$html_table_class = $this->getClass($html_classes, "table_class", "table");

so that you only need 10 lines where you now use 10 times as much. This is the DRY principle.

Use modern PHP features

With modern PHP you could also write the code above as:

$html_table_class = $html_classes["table_class"] ?? "table";

and you don't need an extra method at all. See: Null coalescing operator. I do notice that you used this operator elsewhere, so you are aware of its existence.

One method with all the code in it

I think we both can agree that your get() method does all the heavy lifting in this class. The rest of the class is just used make this method into a class. This doesn't make for pleasant reading and debugging.

Methods should be short pieces of code. Your getters and setters are such methods, but the get() method is not. It sets all the classes and does everything to generate the HTML output. Apart from checkData() it doesn't use any other method in this class. I wouldn't yet call this spagetti code, but it is getting close.

Why does your class not have a general method for generating a html <div>...</div> tag, and then use that in a method to create a table cell, and that in a row method, ending in a table method? Something like:

private function getDivHtml($content = '', $class = '', $tabCount = 0)
{
   $whiteSpace = "\n" . str_repeat("    ", $tabCount + 3);
   $startTag   = "<div" . (empty($class) ? "" : " class=\"$class\"") . ">";
   $endTag     = "</div>";
   return $whiteSpace . $startTag . $content . $endTag;
}

private function getTableCellHtml($content = '', $tabCount = 0)
{
    return $this->getDivHtml($content, $this->cellClass, $tabCount);
}

private function getTableRowHtml($rowData = [], $tabCount = 0)
{
    $content = '';
    foreach ($rowData as $cellData) {
        $content .= $this->getTableCellHtml($cellData, $tabCount + 1);
    }
    return $this->getDivHtml($content, $this->rowClass, $tabCount);
}

And so on. This isn't complete, but I hope you get the idea? Short, easy to understand, methods, each responsible for a very small part of what the class needs to accomplish. Notice how I can reuse the getDivHtml() method.

Help and length of script

Your script contains a long comment at the start, and a help function that basically contains the same information. This makes the script for this class very long, and difficult to read and use. There is such a thing as too much help.

In principle, the class should be self-explanatory, without any comments or help. In practice some extra information can be useful, but this should be kept to a minimum. If you need to explain too much you didn't write a very good class.

Code flexibility

Each class should have a clear reason to exist. Your aim seems to be to make it easier to create tables. Does it do that? What if I want color rows to indicate the status of the data in that row? What if I want to attach a click event to a row? Your class makes those things impossible: It is inflexible.

I have no short answer on how to to make this class more flexible. You could argue that you didn't want your tables to have these features, but that's ignoring what I'm trying to say here. You might very well need those features in the future.

I've written similar code myself in the past, but ran into its limitation. So I know what I'm talking about. I now either use plain HTML, or I fully generate the HTML using all it's features. To get an idea what that could look like see: Document Object Model. I'm using my own code to do something similar. It's only aim is to generating HTML output, and is therefore simpler than this model. It's basic class, to create a HTML tag looks like this:

class HTML
{
    public function __construct($parent = null, 
                                $tagName = '', 
                                $content = '', 
                                $attributes = [])
}

and everything follows from that. Perhaps you can create something similar?

\$\endgroup\$
9
  • \$\begingroup\$ First: thanks for your time. I'm aware of ?? and use "a lot". In that particular case, a user may set an empty string and that is not null. Yes, we both agree on splitting get() method, flexibility and repetition. I understand that there is too much "help", as well as you do. You mentioned class HTML: aren't classes supposed to be "standalones" as far as possible? I faced this matter on other class and solved it using a couple of template variables and then using str_replace(). I think I'll take that approach. I'll re-read your answer and come back with upgraded code. Thanks again! \$\endgroup\$
    – julio
    Commented May 11, 2023 at 12:18
  • 1
    \$\begingroup\$ I'm not completely sure I understand what you mean by: "classes are supposed to be standalones". In an ideal world a class wouldn't have to depend on anything, and any dependency that does exist should be injected. See: a very basic example. However, in the real world classes do not exist in isolation, they're often part of a program with many moving parts. Injecting all dependencies would be impossible, and even in the above example, the fact the injected skills, or languages, should be an array is a kind of dependency. \$\endgroup\$ Commented May 11, 2023 at 12:28
  • 1
    \$\begingroup\$ A good programmer will know where to draw the line: Make a class flexible enough to be expandable and adaptable, while still keeping the code easy to read and manageable. This is a skill that takes years to perfect. \$\endgroup\$ Commented May 11, 2023 at 12:30
  • \$\begingroup\$ As for flexibility in particular, I'll will take the approach of generating the whole HTML inside the class adding support for all necessary attributes. But will not add support for javascript: I was told that JS events should be handled via event listeners neither for inline styling. \$\endgroup\$
    – julio
    Commented May 11, 2023 at 12:32
  • 1
    \$\begingroup\$ Part of creating a flexible class would be not to limit the attributes that are allowed. HTML has many attributes, and programmers can even add their own attributes. For instance the data attributes. \$\endgroup\$ Commented May 11, 2023 at 13:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.