There is a Structural Design Pattern called Composite which allows you to abstract HTML and build a hierarchical structure of composite information, such as a tree.
With Composite you have several objects, each representing a branch of HTML, these branches may or may not be composed of other elements.
So the answer doesn’t get bigger than it already will, I won’t post the codes here but all together in this Gist. Also included is a very simple implementation of a autoloader to run the Application and a small visual appeal with CSS.
First, the directory structure as well as the files used:
|-index.php
|-Composite\Components
| |-Composite\Components\AbstractComponent.php
| |-Composite\Components\Component.php
| |-Composite\Components\Drawable.php
| \-Composite\Components\HTML
| |-Composite\Components\HTML\Cell.php
| |-Composite\Components\HTML\Row.php
| \-Composite\Components\HTML\Table.php
"Let’s meet now our participants":
This abstract superclass defines an interface for all objects that will compose some element, some node of the hierarchical structure, and implements a standard behavior for them.
The estate Abstractcomponent: stores all child nodes of each created element, provided that its condition of existence is respected, allowing the rendering at structure levels.
This "condition of existence" is controlled by property Abstractcomponent::Leaf that determines whether a given object is a branch (FALSE) or a sheet (TRUE). Branches can be composed of other child elements, whereas "leaves", no.
In this particular scenario, in order to simplify, I will be considering table cells as sheets, even if in a real case they are not.
The method Abstractcomponent::add() is what effectively allows Composition to occur in order to aggregate new objects to each other.
Note that there is a check so that objects defined as being a "sheet" do not accept new objects, triggering a logical exception.
Last but not least, AbstractComponent::drawChildren() offers to the objects representing the branches a means of rendering all the elements contained in them. As you can see below, "sheet" objects are the only ones that do not use it.
We have two interfaces, one to represent the components and the other to represent render objects. In fact only the interface Component could be used, but, by preference, I divided the behaviors into two interfaces making Component a guy.
For this example there are objects to represent only three HTML elements, however, you can create others as you need, simply extend the superclass Abstractcomponent and implement the method Drawable::draw(). And of course, if it is necessary to characterize an element as "sheet", overwrite the previously described protected property.
So much Table as Row are quite simple, just involve the child-knots rendered through AbstractComponent::drawChildren() by tags <table> / </table> and <tr> / </tr>, respectively.
Cell, however, it is slightly different. Besides the fact that, here exclusively, we have reversed its characteristic of not allowing other levels in Composition, we overwrite the constructor to have a way to define the text that will be written in the cell.
Normally at that time would come that gigantic warning on a neon sign saying that when overriding the constructor of a superclass, one should reinject it through Rent::__Construct(). However, this is not at all mandatory in this Application because, since the flag Abstractcomponent::Leaf has been superscripted, Abstractcomponent::add() will trigger the logical exception if invoked in the context of an object Cell.
If we do not invoke the constructor, do not define that the object that overwritten the superclass is a sheet and still invoke Abstractcomponent::add() (Boy, we could miss three times :o), we’d get one Fatal Error for the method append() would be invoked without an object. Object that, a Arrayobject, defined precisely in the superclass or superscript constructor.
Just in case, just in case, let’s leave him there. It doesn’t hurt ^_^
Equally to Table and Row, Cell::draw() is very simple, just insert the value received by the constructor, now stored in a property with visibility private (because private is ugly :p) between a couple of tags <td> / </td>
But... and how does that apply to the problem?
First let’s change your input array to something a little bit different just to demonstrate that it works:
$groups = array(
'Administração' => array(
0 => array(
'nome' => 'nome0',
'ramal' => 'ramal0'
),
1 => array(
'nome' => 'nome1',
'ramal' => 'ramal1'
),
2 => array(
'nome' => 'nome2',
'ramal' => 'ramal2'
),
3 => array(
'nome' => 'nome3',
'ramal' => 'ramal3'
)
),
'Financeiro' => array(
4 => array(
'nome' => 'nome4',
'ramal' => 'ramal4'
),
5 => array(
'nome' => 'nome5',
'ramal' => 'ramal5'
),
6 => array(
'nome' => 'nome6',
'ramal' => 'ramal6'
),
7 => array(
'nome' => 'nome7',
'ramal' => 'ramal7'
),
8 => array(
'nome' => 'nome8',
'ramal' => 'ramal8'
),
9 => array(
'nome' => 'nome9',
'ramal' => 'ramal9'
)
)
);
I just broke it to consider more than one department. Before iterating to compose our structure, we have to define the main element of it, that is, the table where each row and each cell will be aggregated:
$tableGroup = new Composite\Components\HTML\Table;
Now yes, let’s iterate:
foreach( $groups as $group => $persons ) {
// Groups
$groupRow = new Composite\Components\HTML\Row;
$groupRow -> add( new Composite\Components\HTML\Cell( $group ) );
$tableGroup -> add( $groupRow );
// Persons
foreach( $persons as $person ) {
$nameCell = new Composite\Components\HTML\Cell( $person['nome'] );
$dialCell = new Composite\Components\HTML\Cell( $person['ramal'] );
$personRow = new Composite\Components\HTML\Row;
$personRow -> add( $nameCell ) -> add( $dialCell );
$tableGroup -> add( $personRow );
}
}
First we create a line for the Department. We create an object Row and add an object to it Cell with the text due. Next we add this first composition to the previously created table.
Now let’s iterate the list of names and phones. See that this time I did it in a different way. I first created the two cells and assigned them the appropriate values. Only then did I create the line and perform the Composition.
Nothing would stop me, for example, from doing so:
$personRow = new Composite\Components\HTML\Row;
$personRow -> add( new Composite\Components\HTML\Cell( $person['nome'] ) )
-> add( new Composite\Components\HTML\Cell( $person['ramal'] ) );
However, this way you improve the flow and prevent the cells from receiving additional props (see below).
Before closing the inner loop, we add the Composition of the row to the table.
Now it remains to render everything. Outside of the loop we invoke the method Drawable::draw() in the context of Table and the AbstractComponent::drawChildren() it used will render everything at once.
If you want individual tables, one for each department, it is enough that the table created in $tableGroup be done right at the beginning of the first loop and, its rendering, right at the end.
Another point to consider is the fact that the line reserved for the Department name does not extend over the entire width of the table.
This was both deliberate and accidental. Purpose to simplify implementation by disregarding any attributes for the elements, as in the case, the colspan in the first line cell.
And accidental because at the time I wrote this code, I couldn’t remember how to merge cells >.<
But this kind of enhancement is now up to you. You can, for example, create a second argument in the Cell, with an array of attributes that you would insert into the TD declaration.
And since implode() does not work with associative arrays, it may not seem, but http_build_query() is fantastic for this.
Despite everything, it is worth noting that, today, with the existing template tools on the market, this type of implementation of Composite is not very useful, after all, it is much easier for you to open and close some PHP tags and write HTML directly in a template, that can even be curly, than opt for this approach.
a suggestion I would make: take the strings with html codes from for and popular the table directly in html, via same server or via Ajax with JSON
– Gustavo Sales
basically your code gets polluted by the injection of data into the logical part (something very bad in the concept of programming), what you need to do is remove this (as in the previous comment, popular in a JSON file or database), if you want to continue I advise you to leave the result directly in HTML to minimize, without this I see no other way =/
– Leonardo Bosquett
in JSON minimizes the impacts and we can easily assist in this part (it will still be compatible with the helper for cakephp, because we will use php to load the file);
– Leonardo Bosquett
Guys, this array is not created by me, it comes from a database, the array is just a test, to make it easier to understand the format the data is received. I want ideas for the algorithm, the rest is just a testing structure, without relying on BD.
– Marcelo Aymone
So you can easily simulate the structure...
– Marcelo Aymone
Just one detail:
echo '<div class="tables"><table border="1px" bordercolor="#000000">';
orecho "<div class='tables'><table border='$variavelPHP' bordercolor='#000000'>";
– brasofilo
Guys, I clarified that css is just for testing. Don’t get too caught up in the detail, I want to improve the php algorithm;
– Marcelo Aymone
@Marceloaymone, follow the discussion about the reopening here, and give your opinion if you think it is pertinent: http://meta.pt.stackoverflow.com/questions/1903
– Bacco
What happens, the moment I asked the question, I simplified the problem so I could have help more easily. As for breaking in layers, this was done, but I did not demonstrate here because I use a framework, and in this way I abstract the problem to focus on the algorithm.
– Marcelo Aymone