Hiërarchische structuur doorlopen

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Jan terhuijzen

jan terhuijzen

06/02/2017 22:55:59
Quote Anchor link
Bij Object georienteerd programmeren maak je soms een soort parent-child structuur van objecten.
Bijvoorbeeld je maakt een class Element dat een HTML element moet voorstellen. Elk Element kan child Elements in zich hebben.
Je hebt dan een typische, eindeloos uitbreidbare structuur:
Afbeelding
Vaak wil je ook iets doen in die structuur. Zo wil ik bijvoorbeeld een class maken, Renderer, die de structuur om kan zetten in HTML. Deze class wil ik in principe los ervan maken, dus ik wil gewoon het alles omvangende Element aan de Renderer meegeven.
Zoals $renderer->render(Element $element);
Het element dat ik dus aan de renderer geef, is het bovenop liggende element in de structuur.
Alle child elementen kunnen zo ook bereikt worden.
Ik kreeg de neiging om het volgende in te bouwen in mijn Element class.
Een typische manier die je vaak tegenkomt om de hele structuur te doorlopen is de volgende (even als voorbeeld in mijn Element class)
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
// in class Element
public function render() {
    foreach($this->childs as $child) {
        $child->render();
    }
}

Een element roept de functie render aan op elk child element dat hij bezit, en elk kind apart doet weer hetzelfde bij zijn eigen kinderen.
Normaal gesproken is dit echt een goede oplossing.
Maar nu lijkt me dit niet de beste oplossing, aangezien het moeilijk is om de volledige controle in de Renderer class te houden.
De renderer moet alle informatie uit een Element halen met getters.
Wat ik nu wil bereiken, is dat ik de bovenstaande (zie code) functie uit mijn Element class kan halen, en dat ik vanuit de renderer een manier verzin om elk child element te doorlopen. Maar dit is nog niet zo makkelijk als ik had gedacht.

Je krijgt dan eigenlijk dit:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
$element = ...; // het alles omvattende top-element;
$children = $element->getAllChildren();
foreach($children as $child) {
    
    // ok, nu de kinderen van de kinderen ophalen,
    // maar dan zou hier een nieuwe foreach loop moeten komen?
    // en in die nieuwe loop weer een loop?
}


Dus kort gezegd: hoe doorloop je een hierarchische structuur, zonder in de klassen van die structuur methodes te maken die dit regelen.
Gewijzigd op 06/02/2017 22:59:20 door Jan terhuijzen
 
PHP hulp

PHP hulp

24/12/2024 20:39:16
 
Ward van der Put
Moderator

Ward van der Put

07/02/2017 08:27:40
Quote Anchor link
Je probeert verschillende dingen tegelijk te doen en dat maakt het complex. Ik denk echter dat je dit idee om te beginnen moet laten varen:
Jan terhuijzen op 06/02/2017 22:55:59:
Maar nu lijkt me dit niet de beste oplossing, aangezien het moeilijk is om de volledige controle in de Renderer class te houden.

Als object A child B heeft en object B children C en D, dan hoeft A helemaal niets van C en D te weten. A moet gewoon iets delegeren aan B en er dan op kunnen vertrouwen dat B zijn ding doet (of een exception gooit). Weg met die "volledige controle", want dat is meestal een teken dat je objecten te sterk gekoppeld hebt.

Met de method render() heb je een ander probleem. Een object is iets anders dan de view van dat object. Bijvoorbeeld de voornaam van een gebruiker is iets anders dan het invoervak waarin je die voornaam toont in een formulier.

Je hebt daarom een tweede object nodig dat het eerst object kan omzetten in een view: een renderer dus. Er zijn echter verschillende renderers denkbaar, want je kunt de voornaam van een gebruiker ook verwerken in de aanhef van een e-mail of in een tabel met een ledenlijst. Opnieuw geldt dat het object zelf niet weet in welke views het zich bevindt of door welke renderers het wordt verwerkt.

Heb je een object dat wél specifiek voor een view is bedoeld, bijvoorbeeld dat invoervak, dan is er een simpelere oplossing om het te renderen: gebruik de magische methode __toString() zodat je het object kunt tonen met echo.

Als we een en ander combineren, krijg je verkort bijvoorbeeld de volgende opzet voor een <div>...</div> met children (niet getest, het gaat even om het idee):

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
/**
 * HTML-element <div>
 */

class Div extends AbstractElement
{
    /**
     * @var array $Attributes
     *   HTML-attributen van de <div>.
     */

    private $Attributes = array();

    /**
     * @var array $Children
     *   In de container <div>...</div> ingesloten andere HTML-elementen.
     */

    private $Children = array();

    /**
     * @param void
     * @return string
     */

    public function __toString()
    {

        // Starttag met of zonder attributen
        echo '<div';
        if (!empty($this->Attributes)) {
            foreach ($this->Attributes as $attribute => $value) {
                echo ' ' . $attribute . '="' . htmlspecialchars($value) . '"';
            }
        }

        echo '>';

        // Ingesloten HTML-elementen
        if (!empty($this->Children)) {
            foreach($this->Children as $child) {
                echo $child;
            }
        }


        // Eindtag
        echo '</div>';
    }    
}

?>
 
Frank Nietbelangrijk

Frank Nietbelangrijk

07/02/2017 09:29:19
Quote Anchor link
Een Hierarchical structure in de database is redelijk complex. Leesvoer.

Met Ward ga ik een heel stuk mee al zou ik absoluut een compleet separate class aanmaken voor iedere complexe view.
De __toString() method in Ward zijn voorbeeld zou bij mij enkel en alleen het 'label' van één treeNode uitspugen. Voor het root element zou dat dan ook iets als 'root' zijn. De separate 'RenderTree' class zou een normale method krijgen met een naam als 'render' of iets dergelijks.

Als je een tree wilt omzetten in HTML dan begin je met een statisch stuk HTML als ontwerp. Je RenderTree class moet dan hetzelfde gaan doen maar dan dynamisch.
Gewijzigd op 07/02/2017 09:33:56 door Frank Nietbelangrijk
 
Jan terhuijzen

jan terhuijzen

07/02/2017 09:57:23
Quote Anchor link
Ik was inderdaad van plan om verschillende render classes te maken, zo kun je later ook beslissen om er een XML of JSON view van te maken.
De bedoeling was dat ik een opbouw had die een Document Object Model representeert (de HTML elementen). EN dat die opbouw helemaal los te gebruiken was. Dan had ik verschillende renderers, zoals
- HTMLRenderer
- JSONRenderer
- en wat ik maar wil.

Natuurlijk moet de renderer alle informatie/eigenschappen kunnen uitlezen van de DOM structuur. En dat gebeurt dan door gettrers. Daardoor is een renderer ook weer niet volledig los te gebruiken, (Want hij weet welke getters hij kan aanroepen op het element) maar wel los te koppelen van de DOM, waardoor je een domain-driven development idee krijgt.
Als de Element class een interface implementeert die alle (getter-)functies ondersteunt die de renderer nodig heeft, is dit zelfs oké. Want wil je een nieuwe renderer class maken, dan weet je zeker dat je elk object dat die interface implementeert, kunt renderen. Want door die interface weet je zeker dat de getters aanwezig zijn, en kun je de getters gebruiken en zo de info verzamelen.

Als ik een _toString zou inbouwen, dan is dat niet flexibel.
Natuurlijk kan ik in plaats van een _toString functie in de Element class, ook een functie render maken, die een Renderer object krijgt als parameter, en dat hij de Renderer aan zijn kinderen meegeeft:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
// in class Element
public function render(Renderer $renderer) {
    
    $renderer->renderElement($this);
    
    foreach($this->childs as $child) {
        $child->render($renderer);
    }
}

Maar dan heb ik weer het probleem dat ik in de renderer class heel moeilijk indents (insprongingen met tabs) kan bepalen in de HTML, JSON, of XML.
Het lijkt misschien overdreven dat ik zelfs dat soort details wil, maar dat is nou juist de bedoeling van een rendering.
Gewijzigd op 07/02/2017 10:02:58 door jan terhuijzen
 



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.