1unitedpower: Projektvorstellung: Teein/Html eine Virtual DOM basierte Templating Engine für PHP 7.1

problematische Seite

Holla,

ich habe die letzten Wochen an einer Virtual DOM Templating-Engine für PHP 7.1 gearbeitet und sie heute endlich veröffentlicht. Ich denke, damit kann man einigen Spaß haben. Feedback, Kritik Fragen, und Anregungen nehme ich immer gerne entgegen, entweder hier auf Deutsch oder bei GitHub auf Englisch. Ich kopiere das "Hello World"-Beispiel mal hierhin, damit man sieht worum es geht:

echo toHtml(beautify(document(
    html(lang('en'))(
        head()(
            meta(charset('utf8')),
            title()(text('Hello World!'))
        ),
        body()(
            h1()(text('Hello World!'))
        )
    )
)));

Ausgabe:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf8">
        <title>Hello World!</titel>
    </head>
    <body>
        <h1>Hello World!</h1>
    </body>
</html>
  1. problematische Seite

    Tach!

    ich habe die letzten Wochen an einer Virtual DOM Templating-Engine für PHP 7.1 gearbeitet

    Erzähl mal, was der Use Case ist, oder die Motivation dahinter, diese Engine zu schreiben. Im Moment sehe ich erstmal nur HTML, bei dem sozusagen die Zeichen <>=" durch ()-Klammern ausgetauscht wurden.

    dedlfix.

    1. problematische Seite

      Erzähl mal, was der Use Case ist, oder die Motivation dahinter, diese Engine zu schreiben. Im Moment sehe ich erstmal nur HTML, bei dem sozusagen die Zeichen <>=" durch ()-Klammern ausgetauscht wurden.

      Richtig, die Nähe zu HTML ist beabsichtigt. Kurz gesagt hat Teein/Html ein Pendant für jedes HTML-Element und -Attribut. Interessant ist, was Teein/Html darüber hinaus anzubieten hat: Anstelle eines Elements oder Attributs kann man jeden belibeigen PHP-Ausdruck angeben, der ein Element oder Attribut produziert. Das ermöglicht es Daten von deren Präsentation zu trennen und gemeinsames Markup wiederzuverwenden.

      In HTML mischt man Daten und deren (semantische) Präsentation. Das kann u.U. langatmig werden:

      <table>
          <tr>
              <td>Lorem</td>
              <td>ipsum</td>
              <td>dolor</td>
          </tr>
          <tr>
              <td>sit</td>
              <td>amet</td>
              <td>consetetur</td>
          </tr>
          <tr>
              <td>sadipscing</td>
              <td>elitr</td>
              <td>sed</td>
          </tr>
      </table>
      

      Teein/Html ermöglich es Gemeinsamkeiten herausfaktoriesieren und in wiederverwendbare View-Helper auslagern, z.B.:

      function viewTable(array $table) : Element
      {
          return table()(...array_map('viewRow', $data));
      }
      
      function viewRow(array $row) : Element
      {
          return tr()(...array_map('viewColumn', $row));
      }
      
      function viewColumn(string $column) : Element
      {
          return td()(text($column));
      }
      

      Mit diesen Helfern, kann ich dann das vorherige HTML-Beispiel prägnanter und leserlicher ausdrücken:

      $table = viewTable([
          ['Lorem', 'ipsum', 'dolor'],
          ['sit', 'amet', 'consetetur'],
          ['sadipscing', 'elitr', 'sed']
      ]);
      

      Die Trennung von Daten und Präsentation macht sich natürlich besonders dann bemerkbar, wenn die Daten zum Zeitpunkt der Entwicklung nicht bekannt sind sondern später dynamisch geladen werden (zum Beispiel aus einer Datenbank). In so einer Situation kann ich gar kein statisches HTML schreiben. Ich kann aber schonmal die Schablone festlegen und die Vorschrift, wie dynamische Inhalte später darin eingefügt werden. Klassische Templating-Engines verwenden dafür Platzhalter und oft noch Zusatzkonstrukte für Verzweigungen und Schleifen. Das bedeutet, dass da zusätzliche Komplexitäts-Schichten eingeführt werden: Zu HTML und PHP kommt nun auch noch eine wie auch immer geartete Templating-Sprache. Teein/Html führt dagegen keine neue Sprache ein, sondern bedient sich an dem, was PHP bereits zu bieten hat. Immerhin hat PHP schon Ausdrücke für Platzhaler (Variablen), Verzweigungen (Ternärer-Operator) und Schleifen (array_map, array_reduce).

      1. problematische Seite

        Lieber 1unitedpower,

        $table = viewTable([
            ['Lorem', 'ipsum', 'dolor'],
            ['sit', 'amet', 'consetetur'],
            ['sadipscing', 'elitr', 'sed']
        ]);
        

        vermutlich erschafft das eine 3x3-Tabelle:

        |Lorem|ipsum|dolor |sit|amet|consetetur |sadipscing|elitr|sed

        Wie macht man das mit Tabellenüberschriften dazu?

        Liebe Grüße,

        Felix Riesterer.

        1. problematische Seite

          Moin moin,

          $table = viewTable([
              ['Lorem', 'ipsum', 'dolor'],
              ['sit', 'amet', 'consetetur'],
              ['sadipscing', 'elitr', 'sed']
          ]);
          

          vermutlich erschafft das eine 3x3-Tabelle:

          |Lorem|ipsum|dolor |sit|amet|consetetur |sadipscing|elitr|sed

          Korrekt.

          Wie macht man das mit Tabellenüberschriften dazu?

          Da gibt es viele Möglichkeiten, naheliegend ist es den View-Helper mit einem zusätzlichen Parameter für die Überschrift auszustatten:

          function viewTableWithCaption(string $caption, array $table) : Element
          {
              return table()(
                  caption()(text($caption)),
                  ...array_map('viewRow', $table)
              );
          }
          

          Um den View-Helper mit Leben zu füllen, übergibt man ihm beim Aufruf die passende Überschrift und die tabellarischen Daten:

          $table = viewTableWithCaption(
              'Hello World',
              [
                  ['Lorem', 'ipsum', 'dolor'],
                  ['sit', 'amet', 'consetetur'],
                  ['sadipscing', 'elitr', 'sed']
              ]
          );
          

          An dem Beispiel lässt sich auch gut verdeutlichen, dass sich Teein/Html-Templates leicht wiederverwenden lassen: Statt viewTable musste man eine neue Funktion viewTableWithCaption schreiben, die View-Helper viewRow und viewColumn werden beide wiederverwendet. viewRow explizit, viewColumn implizit, da er von viewRow benutzt wird. Und man musste keinen bestehenden Code verändern.

          1. problematische Seite

            Lieber 1unitedpower,

            Wie macht man das mit Tabellenüberschriften dazu?

            Da gibt es viele Möglichkeiten, naheliegend ist es den View-Helper mit einem zusätzlichen Parameter für die Überschrift auszustatten:

            ich hatte den Plural verwendet und mich auf <th> anstelle von td bezogen.

            Wie macht man das Folgende?

            +-----------------------+
            |     November 2017     | <caption>
            +--+--+--+--+--+--+--+--+
            |KW|Mo|Di|Mi|Do|Fr|Sa|So| <th>...
            +--+--+--+--+--+--+--+--+
            |45|30|31| 1| 2| 3| 4| 5|
            |46| 6| 7| 8| 9|10|11|12| <th><td><td>...
            

            Liebe Grüße,

            Felix Riesterer.

            1. problematische Seite

              @@Felix Riesterer

              Wie macht man das Folgende?

              +-----------------------+
              |     November 2017     | <caption>
              +--+--+--+--+--+--+--+--+
              |KW|Mo|Di|Mi|Do|Fr|Sa|So| <th>...
              +--+--+--+--+--+--+--+--+
              |45|30|31| 1| 2| 3| 4| 5|
              |46| 6| 7| 8| 9|10|11|12| <th><td><td>...
              

              Und das noch mit thead
              |KW|Mo|Di|Mi|Do|Fr|Sa|So|

              Und mit vordefinierten Spalten?

              LLAP 🖖

              --
              “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
              1. problematische Seite

                @@Felix Riesterer

                Wie macht man das Folgende?

                +-----------------------+
                |     November 2017     | <caption>
                +--+--+--+--+--+--+--+--+
                |KW|Mo|Di|Mi|Do|Fr|Sa|So| <th>...
                +--+--+--+--+--+--+--+--+
                |45|30|31| 1| 2| 3| 4| 5|
                |46| 6| 7| 8| 9|10|11|12| <th><td><td>...
                

                Und das noch mit thead
                |KW|Mo|Di|Mi|Do|Fr|Sa|So|

                Und mit vordefinierten Spalten?

                Ich früchtige euch mal beide in einer Antwort ab. Bevor ich den dynamischen Teil modelliere, will ich hier noch kurz eine 1-zu-1-Überstzung von Gunnars Codepen-Beispiel in Teein/Html vorführen.

                table(class_('month'))(
                    caption()(text('October 2017')),
                    colgroup()(
                        col(class_('week"')),
                        col(class_('mo"')),
                        col(class_('tu"')),
                        col(class_('we"')),
                        col(class_('th"')),
                        col(class_('fr"')),
                        col(class_('sa"')),
                        col(class_('su"'))
                    ),
                    thead()(
                        tr()(
                            th()(text('week')),
                            th()(text('Monday')),
                            th()(text('Tuesday')),
                            th()(text('Wednesday')),
                            th()(text('Thursday')),
                            th()(text('Friday')),
                            th()(text('Saturday')),
                            th()(text('Sunday'))
                        )
                    ),
                    tbody()(
                        tr()(
                            th()(text('39')),
                            td(class_('prev'))(text('25')),
                            td(class_('prev'))(text('26')),
                            td(class_('prev'))(text('27')),
                            td(class_('prev'))(text('28')),
                            td(class_('prev'))(text('29')),
                            td(class_('prev'))(text('30')),
                            td()(text('1'))
                        ),
                        tr()(
                            th()(text('40')),
                            td()(text('2'))),
                            td(class_('holiday'))(text('3')),
                            td()(text('4')),
                            td()(text('5')),
                            td()(text('6')),
                            td()(text('7')),
                            td()(text('8'))
                        ),
                        tr()(
                            th()(text('41')),
                            td()(text('9')),
                            td()(text('10')),
                            td()(text('11')),
                            td()(text('12')),
                            td()(text('13')),
                            td()(text('14')),
                            td()(text('15'))
                        ),
                        tr()(
                            th()(text('42')),
                            td()(text('16')),
                            td()(text('17')),
                            td()(text('18')),
                            td()(text('19')),
                            td()(text('20')),
                            td()(text('21')),
                            td()(text('22'))
                        ),
                        tr()(
                            th()(text('43')),
                            td()(text('23')),
                            td()(text('24')),
                            td()(text('25')),
                            td()(text('26')),
                            td()(text('27')),
                            td()(text('28')),
                            td()(text('29'))
                        ),
                        tr()(
                            th()(text('41')),
                            td()(text('30')),
                            td()(text('31')),
                            td(class_('next'))(text('1')),
                            td(class_('next'))(text('2')),
                            td(class_('next'))(text('3')),
                            td(class_('next'))(text('4')),
                            td(class_('next'))(text('5'))
                        )
                    )
                )
                

                Angenommen wir haben jetzt ein Model, aus dem die Daten für die Monatsansicht stammen, sagen wir sowas hier:

                interface MonthSheet
                {
                    public function getMonthName() : string
                    public function getYear() : int;
                
                    /**
                     * @return Week[]
                     */
                    public function getWeeks() : array
                }
                
                interface Week
                {
                    public function getNumber() : int;
                
                    /**
                     * @return Day[]
                     */
                    public function getDays() : array;
                }
                
                interface Day
                {
                    public function getDate() : int;
                    public function isHoliday() : bool;
                    public function isPrev() : bool;
                    public function isNext() : bool;
                }
                

                Dann könnte man das folgende Template dafür bauen:

                function viewMonthSheet(MonthSheet $sheet) : Element
                {
                    return table(class_('month'))(
                        caption()(text("{$sheet->getMonthName()} {$sheet->getYear()}")),
                        colgroup()(
                            col(class_('week"')),
                            col(class_('mo"')),
                            col(class_('tu"')),
                            col(class_('we"')),
                            col(class_('th"')),
                            col(class_('fr"')),
                            col(class_('sa"')),
                            col(class_('su"'))
                        ),
                        thead()(
                            tr()(
                                th()(text('week')),
                                th()(text('Monday')),
                                th()(text('Tuesday')),
                                th()(text('Wednesday')),
                                th()(text('Thursday')),
                                th()(text('Friday')),
                                th()(text('Saturday')),
                                th()(text('Sunday'))
                            )
                        ),
                        tbody()(...array_map('viewWeek', $sheet->getWeeks()))
                    );
                }
                
                function viewWeek(Week $week) : Element
                {
                    return tr()(
                        th()(text("{$week->getNumber()}")),
                        ...array_map('viewDay', $week->getDays())
                    );
                }
                
                function viewDay(Day $day) : Element
                {
                    $isPrev = $day->isPrev() ? 'prev' : '';
                    $isNext = $day->isNext() ? 'next' : '';
                    $isHolidy = $day->isHolidy() ? 'holiday' : '';
                    return td(class_("{$isPrev} {$isNext} {$isHoliday}"))(text("{$day->getDate()}"));
                }
                
            2. problematische Seite

              Tach!

              Wie macht man das mit Tabellenüberschriften dazu?

              Da gibt es viele Möglichkeiten, naheliegend ist es den View-Helper mit einem zusätzlichen Parameter für die Überschrift auszustatten:

              ich hatte den Plural verwendet und mich auf <th> anstelle von td bezogen.

              Wie macht man das Folgende?

              Der Viewhelper ist nicht Bestandteil der Template-Engine. Am Ende muss das HTML wie im DOM zusammengesetzt werden. Der Viewhelper versteckt diese Einzelheiten. Er muss entsprechend gestaltet sein, damit er alle benötigten Parameter entgegennehmen kann. Wenn die Anforderung ist, Header neben den Unhaltsdaten zu definieren, dann muss man da eben neben dem Array mit den Inhaltsdaten ein weiteres Array mit Überschriften als Parameter vorgesehen werden, oder ein anderes passendes Konstrukt.

              dedlfix.

  2. problematische Seite

    Ich sehe in Deinem "Hello World" Beispiel keine Anwendung einer Template Engine. Bei mir würde das eher so aussehen:

    sub init{
      my $self = shift;
      $self->{STASH}{headline} = "Hello World";
    }
    
    __DATA__
    <!-- template -->
    <h1> %headline% </h1>
    

    Wobei allein $self->eav('title','Hello World') die Sache auch schon erledigen würde. Interessant wirds erst mit Schleifendurchläufen:

      $self->{STASH}{numbers} = [{n=>1},{n=>2},{n=>3}];
    __DATA__
    %loop_numbers%
      <li> %n% </li>
    %endloop%
    

    Und selbstversändlich dürfen die Templates aus beliebigen Quellen kommen, z.B. aus Datenbanken oder Dateien..

    Schöne Grüße

    1. problematische Seite

      Ich sehe in Deinem "Hello World" Beispiel keine Anwendung einer Template Engine. Bei mir würde das eher so aussehen:

      sub init{
        my $self = shift;
        $self->{STASH}{headline} = "Hello World";
      }
      
      __DATA__
      <!-- template -->
      <h1> %headline% </h1>
      

      Das "Hello World"-Beispiel ist mit Absicht so simpel wie möglich gestrickt, deswegen kommt auch noch kein Platzhalter darin vor. Dein Beispiel in Teein/Html würde so aussehen:

      $headline = "Hello World";
      $html = h1()(text($headline));
      

      Teein/Html sorgt automatisch dafür, dass $headline entsprechend maskiert wird, XSS-Angriffe werden also automatisch erkannt und unschädlich gemacht. Außerdem gibt Teein/Html dir eine minifizierte HTML-Version aus, um das Datenübertraungsvolumen möglichst gering zu halten.

      1. problematische Seite

        Hallo 1unitedpower,

        Teein/Html sorgt automatisch dafür, dass $headline entsprechend maskiert wird, XSS-Angriffe werden also automatisch erkannt und unschädlich gemacht. Außerdem gibt Teein/Html dir eine minifizierte HTML-Version aus, um das Datenübertraungsvolumen möglichst gering zu halten.

        Klingt spannend. Und was muss man tun, um das nutzen zu können?

        Bis demnächst
        Matthias

        --
        Rosen sind rot.
        1. problematische Seite

          Klingt spannend. Und was muss man tun, um das nutzen zu können?

          Zwei Voraussetzungen müssen erfüllt sein: Das Zielsystem benötigt mindestens PHP 7.1 und eine aktuelle Version von Composer. Composer ist ein Paket-Manager für PHP und der regelt alle übrigen Abhängigkeiten. Sind die Voraussetzungen erfüllt, kann man mit dem Kommandozeilen-Befehl composer require teein/html die Bibliothek installieren. Wenn man ein Framework wie Laravel oder Symfony einsetzt ist dann nichts mehr zu tun, die wissen automatisch wo sie die Bibliothek finden. Wenn man ohne Framework arbeitet, muss man außerdem eine autoload.php-Datei in das Skript einbetten, in dem man Teein/Html nutzen möchte. Das geht mit der Zeile require __DIR__ . '/vendor/autoload.php';.

      2. problematische Seite

        Aha. Also mir kommt das irgendwie bekannt vor, aber daß manchmal unterschiedliche Entwickler fast zur selben Zeit dieselben Ideen haben soll ja vorkommen 😉

        MfG

        1. problematische Seite

          Aha. Also mir kommt das irgendwie bekannt vor, aber daß manchmal unterschiedliche Entwickler fast zur selben Zeit dieselben Ideen haben soll ja vorkommen 😉

          Oh, die Lorbeeren für Teein/Html gebühren nicht mir. Mein Beitrag ist lediglich eine Übertragung längst bekannter Ideen anderer Entwickler in den PHP-Bereich. Namentlich sind das die Teams hinter React, XHP und Elm.

  3. problematische Seite

    Hello,

    was mir immer nicht klar ist bei diesen vielen Template-Engines:
    Wieso sollte ich´eine komplizierte Sprache lernen und damit eine andere komplizierte zu erzeugen?

    Der Mehrnutzen wird mir immer dann nicht transparent, wenn keinerlei Trennung von Anwender, Backend, Datenhaltung, Events und Ergebnis stattfindet.

    Seite mit Drag & Drop grafisch zusammenbauen und dann macht die Engine im Hintergrund AUszeichnugs-Template, Datenbeschaffung und Datenbindung, nebst Zurückschreiben automatisch, das wäre doch viel geiler!

    Liebe Grüße
    Tom S.

    --
    Es gibt nichts Gutes, außer man tut es!
    Das Leben selbst ist der Sinn.
    1. problematische Seite

      was mir immer nicht klar ist bei diesen vielen Template-Engines:
      Wieso sollte ich´eine komplizierte Sprache lernen und damit eine andere komplizierte zu erzeugen?

      Teein/Html führt keine zusätzliche Sprache ein, ein Teein/Html-Template wird in handelsüblichem PHP verfasst. Es gibt keine extra Syntax für Platzhalter oder Kontrollstrukturen wie Verzweigungen und Schleifen. Es gibt defakto überhaupt keine syntaktischen Erweiterungen in Teein/Html.

      Der Mehrnutzen wird mir immer dann nicht transparent, wenn keinerlei Trennung von Anwender, Backend, Datenhaltung, Events und Ergebnis stattfindet.

      Meine Bibliothek beschränkt sich auf die Trennung von Daten und deren Präsentation. Datenhaltung und Laufzeit-Komponenten sind ganz bewusst außen vor gehalten. Teein/Html funktioniert wunderbar mit bestehenden Lösungen für diese Probleme. Dem Anwender ist es freigstellt, ob er mit nacktem PHP oder Frameworks wie Laravel oder Symfony arbeiten möchte.

      Seite mit Drag & Drop grafisch zusammenbauen und dann macht die Engine im Hintergrund AUszeichnugs-Template, Datenbeschaffung und Datenbindung, nebst Zurückschreiben automatisch, das wäre doch viel geiler!

      Ich glaube, das wäre ein orthoganler Anwendungsfall. Drag&Drop setzt ja clientseitige Logik voraus, Teein/Html arbeitet exklusiv auf dem Server. Wenn man möchte, könnte man natürlich so eine Benutzeroberfläche bauen und daruas Teein/Html-Templates erzeugen. Auch wenn das kein Anwendungsfall ist an den ich primär gedacht habe, ist die öffentliche Schnittstelle allgemein genug gehalten, um auch solche Szenarien abzudecken. Ich denke aber, dass in solchen Fällen React oder Elm besser geeignet wären.

      1. problematische Seite

        was mir immer nicht klar ist bei diesen vielen Template-Engines:
        Wieso sollte ich´eine komplizierte Sprache lernen und damit eine andere komplizierte zu erzeugen?

        Teein/Html führt keine zusätzliche Sprache ein,

        Das ist Ansichtssache.

        Ich würde etwas wie

        table(class_('month'))(
            caption()(text('October 2017')),
            colgroup()(
                col(class_('week"')),
        #[…]
        

        durchaus als zusätzliche Sprache ansehen, denn es hat eigene Begriffe und eine eigene "Grammatik". Ist sowas wie "Russisch mit deutscher Grammatik". Das trifft auch deshalb zu, weil diese Begriffe und Regeln wechselseitig bei HTML und PHP entlehnt sind. Problematisch wird die Verwendung wohl dann, wenn ein HTML-Tag verwendet werden soll, welcher in der "Engine" nicht vorgesehen ist.

        Mir selbst fehlt auch irgendwie ein "use case" welcher den Lernaufwand und das Vertrauen begründen könnte.

        1. problematische Seite

          Liebe Regina,

          Mir selbst fehlt auch irgendwie ein "use case" welcher den Lernaufwand und das Vertrauen begründen könnte.

          so, wie ich das bisher sehe, ist das Projekt im Grunde ein Ersatzangebot für SimpleXML und DOM. Für Fälle, in denen man deren Komplexität und Umständlichkeit oder zu starke Vereinfachung ($xml->body['lang'] = 'de' oder $body->setAttribute('lang', 'de')) mit einer anderen ersetzen möchte.

          Liebe Grüße,

          Felix Riesterer.

        2. problematische Seite

          Ich würde etwas wie

          table(class_('month'))(
              caption()(text('October 2017')),
              colgroup()(
                  col(class_('week"')),
          #[…]
          

          durchaus als zusätzliche Sprache ansehen, denn es hat eigene Begriffe und eine eigene "Grammatik". Ist sowas wie "Russisch mit deutscher Grammatik".

          So kann man es definitiv auch betrachten, das hier ist HTML mit PHP-Grammatik.

          Problematisch wird die Verwendung wohl dann, wenn ein HTML-Tag verwendet werden soll, welcher in der "Engine" nicht vorgesehen ist.

          Momentan werden alle HTML-Elemente und -Attribute aus dem Living HTML Standard unterstützt. Custom-Elemente muss man sich selber stricken (hier muss ich aber noch die Dokumentation ausbauen):

          function youtubePlayer(Attribute ...$attributes) : callable
          {
              return function (Node ...$elements) use ($attributes) : NormalElement {
                  return new NormalElement(
                      'youtube-player',
                      $attributes,
                      $elements
                  );
              };
          }
          

          Mir selbst fehlt auch irgendwie ein "use case" welcher den Lernaufwand und das Vertrauen begründen könnte.

          Völlig in Ordnung. Teein/Html ist nicht für jeden, es ist eine sehr passionierte Engine für Leute, die gerne funktional programmieren, hohen Wert auf Typsicherheit und Testbarkeit legen und die vielleicht schon Vorerfahrung mit React oder XHP gesammelt haben, und etwas vergleichbares für PHP suchen. Was das Vertrauen anbelangt: Teein/Html ist ein FOSS-Projekt, du kannst dich also gerne selber von der Code-Qualität überzeugen. Außerdem ist der CI-Service Scrutinizer im Einsatz und analysiert das Projekt am laufenden Band nach Bugs, Sicherheitslücken, Code-Smells usw. Und schließlich gibt es eine sehr ausführliche Test-Suite (aktuell etwas mehr als 300 Checks), die die Code-Basis auf Herz und Nieren überprüft.

          1. problematische Seite

            es ist eine sehr passionierte Engine für Leute, die gerne funktional programmieren,

            Hatte DOM nicht das O von Object?

            Warum also funktional und nicht als Objekt formulieren?

            define( 'HtmlVersion', 5 );
            define( 'GetOutHuman', 0 );
            define( 'GetOutMinimized', 1 );
            
            $title = 'Hallo Welt';
            
            $doc   =  new DOM();
            $doc   -> setTitle( $title );
            $elem  =  new Headline( 1, $title );
            $elem  =  addClass( 'docTitle' );
            $doc   -> addElem( $elem );
            $table =  new Table();
            $table -> setCaption( 'Monat' );
            $table -> setId( 'Monatstabelle' );
            $cg    -> new Colgroup();
            $cols  =  array();
            $col[0] =  new Col;
            $col[0] -> addClass( 'woche');
            for ( $i = 1; $i < 8; $i++ ) {
                $col[$i] =  newCol;
                $col[$i] -> addClass( 'tag');
            }
            $cg    -> addCols( $col );
            $table -> addColgroup( $cg );
            
            $tr = new TableRow();
            $tds = array();
            for ( $i = 1; $i < 8; $i++ ) {
              $tds[$i] =  new TableData();
              $tds[$i] -> setValue( $i );
              if ( $i = date( 'd' ) ) {
                 $tds[$i] -> addClass( 'heute' );
              }
            }
            $tr ->    addTableData( $tds );
            $table -> addTableRow( $tr );
            $doc ->   addElem( $table );
            
            
            [...]
            echo $doc -> getOut( GetOutMinimized );
            

            Über die Setter (auch addClass() ) könnte man Attribute fest bestimmen, Werte säubern(entities, specialchars,...), die checker prüfen das Objekt auf Vollständigkeit, die Adder könnten prüfen ob ein Element oder ein Array hinzugefügt wird, die Methode addElem() könnte einen Validator der Objekte aufrufen und das Objekt zuvor darauf testen, ob alle Pflichtangaben (img:src, alt) vorhanden sind.

            Ich weiß nur noch immer nicht wozu ich so einen Overhead brauche.

            1. problematische Seite

              es ist eine sehr passionierte Engine für Leute, die gerne funktional programmieren,

              Hatte DOM nicht das O von Object?

              Warum also funktional und nicht als Objekt formulieren?

              Weil ich gerne funktional programmiere 😉 Im direkten Vergleich finde ich dein Beispiel deutlich langatmiger und die Baumstruktur nur sehr mühevoll zu erfassen, was auch mit deiner ungewohnten Einrückung zusammenhängt. Aber es gibt auch vergleichbare OOP-Lösungen, die mir gut gefallen, wie html-document von royalltheforth. Der sagt aber auch, dass er inzwischen funktionale Programmierung bevorzugt. Wenn du funktionaler Programmierung nichts abgewinnen kannst, dann mach eben einen Bogen um Teein/Html, ich bin nicht hier um zu missionieren. Programmierstile haben immer auch viel mit persönlichem Geschmack zu tun, für den funktionalen Geschmack, gab es in der PHP-Templating-Welt bisher nur wenig Feinkost.

              1. problematische Seite

                Hello,

                es gibt doch bereits einen eingebauten DOM-Parser in PHP, der mMn gut funktioniert. Man muss damit zwar auch etwas üben, aber er stellt eigentlich alles zur Verfügung, was man braucht.

                Man muss ihn nicht extra laden, er gehört inziwschen zum "harten Kern" von PHP.

                Liebe Grüße
                Tom S.

                --
                Es gibt nichts Gutes, außer man tut es!
                Das Leben selbst ist der Sinn.
                1. problematische Seite

                  es gibt doch bereits einen eingebauten DOM-Parser in PHP, der mMn gut funktioniert. Man muss damit zwar auch etwas üben, aber er stellt eigentlich alles zur Verfügung, was man braucht.

                  Teein/Html ist kein HTML-Parser, sondern nur ein Helfer, um HTML-Dokumente zu erzeugen. Sehr richtig, das kann man auch mit PHPs DOM-Implementierung machen, aber das ist erfahrungsgemäß sehr unhandlich. Entscheide selbst, was du besser lesen kannst:

                  $document = new DOMDocument();
                  $table = $document->createElement('table');
                  $tr = $document->createElement('tr');
                  $td1 = $document->createElement('td');
                  $td2 = $document->createElement('td');
                  $td3 = $document->createElement('td');
                  $tr->appendChild($td1);
                  $tr->appendChild($td2);
                  $tr->appendChild($td3);
                  $table->appendChild($table);
                  
                  $table = table()(
                      tr()(
                          td()(),
                          td()(),
                          td()(),
                      )
                  );
                  

                  Man muss ihn nicht extra laden, er gehört inziwschen zum "harten Kern" von PHP.

                  Das ist ein unbetstrittener Vorteil von PHPs nativem DOM. Ich kann nur versuchen die Installation und das Laden so einfach wie möglich zu machen, im Moment reicht ein composer require teein/html für die meisten Fälle aus. Wer ohne Framework arbeitet, muss noch die Zeile require __DIR__ . '/vendor/autoload.php'; in sein PHP-Skript einfügen.

    2. problematische Seite

      Hello,

      was mir immer nicht klar ist bei diesen vielen Template-Engines:
      Wieso sollte ich´eine komplizierte Sprache lernen und damit eine andere komplizierte zu erzeugen?

      Bei einer richtigen TE musst Du das nicht, da schreibst Du HTML wie gewohnt und baust da lediglich Platzhalter ein. Das wäre der Part für den Frontmann, der Programmierer hingegen muss nur die Namen der Platzhalter kennen um die zum Leben zu erwecken.

      Der Mehrnutzen wird mir immer dann nicht transparent, wenn keinerlei Trennung von Anwender, Backend, Datenhaltung, Events und Ergebnis stattfindet.

      Sehe ich auch so. Eine TE ist umso wertvoller, je einfacher sie anwendbar ist und je besser sie Programmlogik vom Layout trennt. Letzteres war in PHP noch nie der Fall, vielmehr erzeugt PHP selbst die HTML Ausgabe und daran ändert das hier vorliegende Projekt gar nichts.

      Seite mit Drag & Drop grafisch zusammenbauen und dann macht die Engine im Hintergrund AUszeichnugs-Template, Datenbeschaffung und Datenbindung, nebst Zurückschreiben automatisch, das wäre doch viel geiler!

      Die Datenbeschaffung ist nicht Sache einer TE. Eine TE kriegt zwei Dinge, einmal das Template und zum Anderen die Daten selbst. Aufgabe der TE ist es hauptsächlich, die übergebenen Daten in das übergebene Template zu rendern, wobei ein Template auch einfache Kontrollstrukturen haben darf:

      %if_ads%
        <p> Hie könnte Ihre Werbung stehen </p>
      %else%
        <p> Diese Seite ist frei von Werbung </p>
      %endif%
      

      Ohne daß die gesamte Programmmlogik im Template selbst abgebildet wird. Was automatisch gehen sollte, ist das Durchreichen der an den URL gehefteten Eigenschaften der Art ads=1 oder no_tt=1 wobei letztere z.B. das Rendern komplett abschaltet für den Fall dass die Seite gar keine Platzhalter hat die gerendert werden müssen.

      Ein nettes Feature ist, wenn man der TE mitteilen kann, ob aus bestimmten Zeichen NCRs oder HTML Entities gemacht werden sollen. Und bei von text/html abweichenden Content-Type wird die TE gar nicht erst geladen.

      MfG

      1. problematische Seite

        Ein nettes Feature ist, wenn man der TE mitteilen kann, ob aus bestimmten Zeichen NCRs oder HTML Entities gemacht werden sollen.

        Ich finde da liegt einer der großen Vorteile von Virtual DOM basierten Templating Enginges im Vergleich zu String-basierten Engines: Der Anwender muss sich nicht selber um Maskierungen kümmern, sondern kann sich entspannt zurücklehnen. Das ist weniger fehleranfällig und vor allem eine Sache weniger, die der Anwender berücksichtigen muss. Gerade weil vergessene Maskierungen schnell zu XSS-Sicherheitslücken führen, ist mir das ein wichtiges Feature. Sozusagen als Nebenprodukt ist es unmöglich™ mit Virtual Dom basierten Engines syntaktisch fehlerhaftes HTML als Ausgabe zu produzieren.

        1. problematische Seite

          Ein nettes Feature ist, wenn man der TE mitteilen kann, ob aus bestimmten Zeichen NCRs oder HTML Entities gemacht werden sollen.

          Ich finde da liegt einer der großen Vorteile von Virtual DOM basierten Templating Enginges im Vergleich zu String-basierten Engines: Der Anwender muss sich nicht selber um Maskierungen kümmern, sondern kann sich entspannt zurücklehnen. Das ist weniger fehleranfällig und vor allem eine Sache weniger, die der Anwender berücksichtigen muss. Gerade weil vergessene Maskierungen schnell zu XSS-Sicherheitslücken führen, ist mir das ein wichtiges Feature. Sozusagen als Nebenprodukt ist es unmöglich™ mit Virtual Dom basierten Engines syntaktisch fehlerhaftes HTML als Ausgabe zu produzieren.

          Ja das ist gut. Grundsätzlich ist ja die Idee nicht schlecht und ich will die auch nicht schlechtreden. Mich hat nur der Begriff Virtual DOM basierte Templating Engine verwirrt weil sich das mit meinem bisherigen TE Begriff überhaupt nicht vereinbart. MfG

          1. problematische Seite

            Mich hat nur der Begriff Virtual DOM basierte Templating Engine verwirrt weil sich das mit meinem bisherigen TE Begriff überhaupt nicht vereinbart.

            Das ist auch ein recht jungen Design für Templating-Engines. Populär gemacht wurde es Facebook mit React und XHP. Die Idee ist eigentlich so simpel, dass man sich fragen muss wieso es so lange gedauert hat bis sie jemandem eingefallen ist: HTML hat ja eine Baumstruktur, in string-basierten Templating-Engines findet sich das aber nirgendwo wieder. Zeichenketten lassen sich nur miteinander verketten, aber nicht verschachteln. Für Bäume ist aber die Verschachtelung eine viel natürlichere Operation als die Verkettung. Außerdem kann man in einem Baum den Knoten verschiedene Typen geben, zum Beispiel Element, Attribut, Kommentar usw. und damit mehr Informationen an den Knoten speichern als es mit Zeichenketten geht. Das ermöglicht Features wie Syntax-Checks und automatischen Kontextwechsel, die mit string-basierten Engines nicht möglich sind. Ein weiterer Vorteil ist, dass man keine eigene Template-Sprache braucht Platzhalter und Kontrollfluss-Strukturen im Template auszuzeichnen, sondern einfach die Ausdrücke der Programmiersprache, in der die Engine geschrieben ist, verwenden kann. Teein/Html hat keine extra Syntax für Platzhalter, stattdessen dienen PHP-Variablen als Platzhalter. Insofern bieten Virtual DOM basierte Engines dem Anwender mehr Unterstützung und Automatisierung bei der Entwicklung von Templates bei gleichzeitig einfacherem Gebrauch.

            1. problematische Seite

              Moin,

              Die Idee ist eigentlich so simpel, dass man sich fragen muss wieso es so lange gedauert hat bis sie jemandem eingefallen ist: HTML hat ja eine Baumstruktur, in string-basierten Templating-Engines findet sich das aber nirgendwo wieder.

              Ach ne: HTML ist eine Sequenz, eine Solche wird immer sequentiell bearbeitet. Wahlfreien Zugriff jedoch gibt es nicht in Sequenzen sondern im Hauptspeicher und das gilt auch für Baumstrukturen die sich im RAM als abstrakte Datentypen wiederfinden.

              Diese grundlegenden Zusammenhänge hat Niklaus Wirth bereits in dem 80ern ausformuliert und selbstverständlich gelten die auch heute noch. D.h., daß man z.B. aus einer in PHP vorliegenden Baumstruktur beliebige Sequenzen (Dateien) erzeugen kann, also auch solche wo man die Baumstruktur mit einem Editor gar nicht sieht obwohl sie aus der Datei mit demselben Algorithmus wiederhergestellt werden kann, also nach wie vor in der Datei vorhanden ist.

              Deine Engine ist also nur ein Vermittler der mit einem speziellen Algorithmus zwischen einer Sequenz und einem abstrakten Datentype im Hauptspeicher vermittelt.

              Das ermöglicht Features wie Syntax-Checks und automatischen Kontextwechsel, die mit string-basierten Engines nicht möglich sind

              Oh doch. Wenn Du eine Sequenz im Hauptspeicher hast für den wahlfreien Zugriff also, kannste alles damit machen. Perls Template Toolkit kann auch Syntax für Platzhalter checken.

              MfG