Rollen Sie Ihr eigenes Templating-System in PHP
German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)
Schieben Sie die Grenzen Ihres PHP-Wissens mit diesem fortgeschrittenen Tutorial. Implementieren Sie Techniken wie objektorientierte Programmierung, reguläre Ausdrücke und Curren von Funktionen, um ein Vorlagensystem von Grund auf zu erstellen.
Warum muss ich ein Templating-System erstellen?
Die kurze Antwort? Du nicht. Wieso sich die Mühe machen? Aus der Tiefe meines geekigen kleinen Herzens glaube ich, dass alle Entwickler sich ständig dazu drängen sollten, neue und / oder schwierige Konzepte zu lernen. Das macht uns schlauer, hält unsere Arbeit interessant und lässt unsere tägliche Arbeitsbelastung weniger belastend erscheinen (weil Sie wissen, dass wir so viel schlauer werden). Zu diesem Zweck können Sie mit Ihrem eigenen Template-System Ihre PHP-Chops mit hirnbrechenden Konzepten wie Curry und regulären Ausdrücken verfeinern. Und, hey! Sie könnten feststellen, dass Sie ein Vorlagensystem für ein zukünftiges Projekt verwenden können.
Schritt 1 Planen Sie das System
"Alle Entwickler sollten sich ständig dazu drängen, neue und / oder schwierige Konzepte zu lernen."
Bevor wir eintauchen und mit dem Programmieren beginnen, wollen wir genau herausfinden, was wir zu tun versuchen. Wir hoffen, beim Aufbau dieses Templating-Systems:
- Trennen Sie HTML-Markup von unseren PHP-Skripten fast vollständig
- Machen Sie Daten aus unseren Backend-Skripten für Frontend-Designer zugänglicher
- Vereinfachen Sie die Wartung, indem Sie die Anzeigelogik von der Geschäftslogik abstrahieren
Was wir bauen müssen
Das Templating-System, das wir in dieser Übung erstellen werden, besteht aus einer Klasse, Template, die zwei Eigenschaften und fünf Methoden enthält, und einem Ordner, der die zu analysierenden Template enthält. Ansonsten benötigen wir nur eine Datei zur Ausgabe einiger Testdaten. Klingt ziemlich einfach, oder?
Wie funktioniert das Templating System?
Um es einem Designer wirklich leicht zu machen, die Daten aus unseren serverseitigen Skripten zu formatieren, erstellen wir ein System, das eine Vorlage mit einer optionalen Kopf- und Fußzeile mit einer Schleife ermöglicht, die zum Formatieren verwendet wird Die Einträge stammen vom Skript. Wenn wir es in seinen grundlegendsten Zustand zurückziehen, wird dieses System die folgenden Schritte durchlaufen:
- Laden Sie Einträge, die als Array von Objekten analysiert werden sollen
- Laden Sie die zu verwendende Vorlagendatei
- Trennen Sie die Kopf- und Fußzeile von der Schleife
- Suchen Sie die Vorlagen-Tags mit regulären Ausdrücken
- Überprüfen Sie, ob das Schablonen-Tag mit einer Eigenschaft im Eintrag-Objekt übereinstimmt
- Ersetzen Sie das Vorlagen-Tag durch die Daten der passenden Eigenschaft
Schritt 2 Skizzieren Sie die Klasse
Um ein Programmierprojekt (oder etwas anderes) einfacher zu machen, möchte ich zunächst die Schritte skizzieren, die ich ergreifen sollte. Wir werden diese Taktik anwenden, damit wir bei der Entwicklung wirklich nur die Lücken füllen.
Erstellen Sie die Ordnerstruktur
Erstellen Sie zuerst einen Ordner, der Ihr Projekt enthält. Ich habe mein Templating angerufen. Erstellen Sie im Projektordner zwei neue Ordner: Assets und System. Der Ordner "Assets" enthält die Vorlagen für unsere Demo. Der Systemordner enthält die Klasse Template. Erstellen Sie im Ordner "Assets" zwei neue Ordner: Vorlagen. Raten Sie, was es hält!
Erstellen Sie die Hauptdateien
Erstellen Sie als Nächstes index.php im Hauptprojektordner. Hier testen wir das Templating-System, sobald es fertig ist, und wir werden es verwenden, um sicherzustellen, dass unsere einzelnen Schritte auch auf dem Weg funktionieren. Fürs Erste können Sie es jedoch leer lassen. Erstellen Sie im Systemordner eine neue PHP-Datei namens class.template.inc.php. Im Inneren definieren Sie die Klasse, die wir Template nennen, und lassen Sie uns von oben gehen, um eine To-Do-Liste zu erstellen:
1 |
<?php
|
2 |
|
3 |
/** * A templating engine * * PHP version 5 * * LICENSE: This source file is subject to the MIT License, available at * http://www.opensource.org/licenses/mit-license.html * * @author Jason
|
4 |
Lengstorf <jason.lengstorf@copterlabs.com> * @copyright 2010 Copter Labs * @license http://www.opensource.org/licenses/mit-license.html MIT License */ class Template { |
5 |
|
6 |
//TODO: Define a class property to store the template
|
7 |
|
8 |
//TODO: Define a class property to store the entries
|
9 |
|
10 |
//TODO: Write a public method to output the result of the templating engine
|
11 |
|
12 |
//TODO: Write a private method to load the template
|
13 |
|
14 |
//TODO: Write a private method to parse the template
|
15 |
|
16 |
//TODO: Write a static method to replace the template tags with entry data
|
17 |
|
18 |
//TODO: Write a private currying function to facilitate tag replacement
|
19 |
|
20 |
}
|
HINWEIS: Die Tatsache, dass die Tag-Ersetzungs-Methode statisch ist und dass die Curry-Funktion überhaupt vorhanden ist, ist etwas, dem Sie jetzt vertrauen müssen. Ich werde alles erklären, wenn du bei mir bleibst.
Schritt 3 Definieren Sie eine "Vorlage"
Bevor wir mit der Analyse von Vorlagen beginnen, sollten wir uns entscheiden, wie unsere Vorlagen aussehen werden. Eine Vorlage sollte so einfach wie möglich zu verwenden sein. Wenn wir das richtig machen, sollte jeder semikompetente HTML-Benutzer einfach Vorlagen erstellen können. Dies bedeutet, dass unsere Vorlage so nah wie möglich an HTML geschrieben wird.
Wie würde das HTML aussehen?
Um unsere Vorlage zu definieren, lassen Sie uns einfach einen Eintrag erstellen, wie wir ihn auf einer Webseite sehen:
1 |
<h3>Other Articles to Check Out</h3> |
2 |
|
3 |
Here are some other articles on the Envato network. |
4 |
|
5 |
<ul id="entries"> |
6 |
|
7 |
<li> <h4><a href="http://net.tutsplus.com/">Some Article</a></h4> <p class="byline">Published on net.tutsplus.com <p class="read-more"><a href="http://net.tutsplus.com/">read full article |
8 |
»</a> </li> |
9 |
|
10 |
</ul><!-- end #entries --> |
Groß. Das ist einfach genug. Also lassen Sie uns die Teile heraustrennen, die von Artikel zu Artikel variieren würden:
- URL - die Artikel-URL
- Titel - der Titel des Artikels
- Publisher - die Website, die den Artikel veröffentlicht hat
Titel - der Titel des Artikels Publisher - die Website, die den Artikel veröffentlicht hat
Verwenden Sie die variablen Daten, um Vorlagen-Tags auszuwählen Anhand der oben gesammelten Informationen können wir feststellen, dass wir für das obige Beispiel insgesamt drei Template-Tags benötigen.
1 |
<h3>Other Articles to Check Out</h3> |
2 |
|
3 |
Here are some other articles on the Envato network. |
4 |
|
5 |
<ul id="entries"> |
6 |
|
7 |
<li> <h4><a href="url">title</a></h4> <p class="byline">Published on site <p class="read-more"><a href="url">read full article »</a> </li> |
8 |
|
9 |
</ul><!-- end #entries --> |
Aber wir können nicht einfach das Wort "title" als Template-Tag für den Titel verwenden; Was ist, wenn jemand das Wort "title" in seinem Markup verwendet? Es würde dazu führen, dass jedes Vorkommen des Wortes durch den Eintragsnamen ersetzt wird - offensichtlich ist dies nicht das richtige Verhalten. Um dies zu vermeiden, müssen wir unsere Vorlagen-Tags in etwas einfügen, das im Text wahrscheinlich nicht erscheint. In diesem Beispiel verwenden wir geschweifte Klammern ({}).
1 |
<h3>Other Articles to Check Out</h3> |
2 |
|
3 |
Here are some other articles on the Envato network. |
4 |
|
5 |
<ul id="entries"> |
6 |
|
7 |
<li> <h4><a href="{url}">{title}</a></h4> <p class="byline">Published on {site} <p class="read-more"><a href="{url}">read full article »</a> </li> |
8 |
|
9 |
</ul><!-- end #entries --> |
Durch die Verwendung eines Schablonen-Tags, das im Text des durchschnittlichen Markups relativ unwahrscheinlich erscheint, können wir relativ sicher sein, dass unsere Schablonen in fast allen Standardsituationen wie erwartet funktionieren.
Schritt 4 Laden Sie die Einträge
Da ich gerne in die Interna der Templating-Engine einsteigen würde, werden wir nicht viel Zeit auf die Einträge selbst verwenden. Ich werde die Envato API verwenden und die Schritte für href = "http://net.tutsplus.com/tutorials/php/display-anyything-you-want-from-the-envato-api-using-php implementieren / "> mit der Envato API von Drew Douglass. Öffne index.php im root deines Projektordners und füge den folgenden Code ein.
1 |
<?php
|
2 |
|
3 |
/** * Loads entries from the Envato API for a given site * * @link https://marketplace.envato.com/api/documentation * * @param string $site The site from which entries should be loaded * @return array An array of objects containing entry data */ function load_envato_blog_posts( $site='themeforest' ) { // Set up the request for the Envato API $url = 'http://marketplace.envato.com/api/edge/blog-posts:'.$site.'.json'; |
4 |
|
5 |
// Initialize an empty array to store entries $entries = array();
|
6 |
|
7 |
// Load the data from the API $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); $ch_data =
|
8 |
curl_exec($ch); curl_close($ch); |
9 |
|
10 |
// If entries were returned, load them into the array if(!empty($ch_data)) { // Convert the JSON into an array of entry objects $json_data = json_decode($ch_data, TRUE); foreach(
|
11 |
$json_data['blog-posts'] as $entry ) { $entries[] = (object) $entry; } |
12 |
|
13 |
return $entries; } else { die('Something went wrong with the API request!'); } } |
Wie Sie in den Kommentaren sehen können, verwenden wir cURL, um eine Anfrage an die Envato-API zu senden und die zurückgegebenen Daten in $ ch_data zu speichern. Unter der Annahme, dass Einträge zurückgegeben wurden, werden diese Einträge dann vom zurückgegebenen JSON-Format in ein Array von Objekten konvertiert. HINWEIS: Weitere Informationen zu objektorientiertem PHP finden Sie in meinem Nettuts-Artikel über objektorientierte Programmierung in PHP, in meinem Buch Pro PHP und jQuery oder in einer kurzen Erklärung von OOP auf Wikipedia. Die Eintragsobjekte enthalten die Eigenschaften, die wir in unseren Vorlagen verwenden können. Um diese Daten anzuzeigen, fügen Sie den folgenden fett gedruckten Code in index.php hinzu:
1 |
<?php
|
2 |
|
3 |
echo '<pre>', print_r(load_envato_blog_posts(), TRUE), '</pre>'; |
4 |
|
5 |
/** * Loads entries from the Envato API for a given site * ... */ function load_envato_blog_posts( $site='themeforest' ) {...} |
Hinweis: Code aus Gründen der Kürze abgeschnitten. Wenn Sie index.php in Ihrem Browser laden, sehen Sie etwas Ähnliches wie das Folgende:
1 |
Array ( [0] => stdClass Object ( [title] => Interview With "The Man": Jeffrey Way [url] => http://feedproxy.google.com/~r/themeforest/~3/5oZEgpMCn3Q/ [site] => themeforest.net [posted_at] => 2009-12-19 ) |
2 |
|
3 |
[1] => stdClass Object ( [title] => ThemeForest Week in Review [url] => http://feedproxy.google.com/~r/themeforest/~3/fAiw8Xw1Q8U/ [site] => themeforest.net [posted_at] => 2009-12-19 ) |
4 |
|
5 |
...more entries... |
6 |
|
7 |
)
|
Die Klasseneigenschaften - $ title, $ url, $ site und $ posted_at - entsprechen den Schablonentags {title}, {url}, {site} und {posted_at}. Wir werden genau sehen, wie das etwas später funktioniert.
Binden Sie die Einträge an das Vorlagensystem
Sie haben vielleicht bemerkt, dass die Einträge gerade nicht in der Klasse Template geladen oder gespeichert werden. Das liegt daran, dass unser Templating-System mit jedem Satz von Einträgen kompatibel sein soll. Es ist ziemlich einfach, einen beliebigen Satz von Einträgen aus einer Datenbank oder einem Web-Service zu übernehmen und sie in einem Array von Objekten zu organisieren. Allerdings müssen die Einträge im Templating-System gespeichert werden, damit sie analysiert werden können. Um dies einfach und einfach zu halten, öffnen Sie class.template.inc.php und erstellen Sie eine öffentliche Eigenschaft namens $ entries. Dies speichert die Einträge für die spätere Analyse.
1 |
<?php
|
2 |
|
3 |
/** * A templating engine * ... */ class Template { |
4 |
|
5 |
//TODO: Define a class property to store the template
|
6 |
|
7 |
/** * Stores the entries to be parsed in the template * @var array */ public $entries = array(); |
8 |
|
9 |
//TODO: Write a public method to output the result of the templating engine
|
10 |
|
11 |
//TODO: Write a private method to load the template
|
12 |
|
13 |
//TODO: Write a private method to parse the template
|
14 |
|
15 |
//TODO: Write a static method to replace the template tags with entry data
|
16 |
|
17 |
//TODO: Write a private currying function to facilitate tag replacement
|
18 |
|
19 |
}
|
Schritt 5 Laden Sie die Vorlagendatei
Unser nächster Schritt besteht darin, eine Methode zu erstellen, die die Vorlagendatei zum Parsen lädt. Da diese Methode nur als Teil des übergreifenden Templating-Prozesses ausgeführt werden soll, können wir diese Methode als privat definieren. Im Interesse der Einhaltung der PEAR-Namenskonventionen nennen wir diese Methode _load_template (). Darüber hinaus benötigen wir zwei neue Eigenschaften, um den Pfad der Vorlagendatei sowie die geladene Vorlage zu speichern, die wir $ template_file bzw. $ _template nennen.
1 |
<?php
|
2 |
|
3 |
/** * A templating engine * ... */ class Template { |
4 |
|
5 |
/** * Stores the location of the template file * @var string */ public $template_file, |
6 |
|
7 |
/** * Stores the entries to be parsed in the template * @var array */ $entries = array(); |
8 |
|
9 |
/** * Stores the contents of the template file * @var string */ private $_template; |
10 |
|
11 |
//TODO: Write a public method to output the result of the templating engine
|
12 |
|
13 |
/** * Loads a template file with which markup should be formatted * * @return string The contents of the template file */ private function _load_template( ) { // Load the template... } |
14 |
|
15 |
//TODO: Write a private method to parse the template
|
16 |
|
17 |
//TODO: Write a static method to replace the template tags with entry data
|
18 |
|
19 |
//TODO: Write a private currying function to facilitate tag replacement
|
20 |
|
21 |
}
|
HINWEIS: Vergessen Sie nicht, die Eigenschaft $ entries anzupassen, um das Hinzufügen von $ template zu berücksichtigen.
Stellen Sie sicher, dass die Datei vorhanden ist
Der erste Schritt beim Laden der Vorlage besteht darin, sicherzustellen, dass die Datei existiert, bevor Sie versuchen, sie zu öffnen. Dies hilft Fehler zu vermeiden, und wirklich, es ist nur eine gute Idee, um sicher zu sein. Wir machen das mit file_exists (). Da die Möglichkeit besteht, dass die Berechtigungen einer Datei es uns nicht erlauben, ihren Inhalt zu lesen, müssen wir dies auch mit is_readable () überprüfen. Da wir versuchen, dies so einfach wie möglich zu gestalten, werden wir eine Standardvorlage hinzufügen, falls die angegebene Vorlage nicht existiert oder aus irgendeinem Grund nicht korrekt geladen wird. Nachdem wir den Speicherort der Vorlagendatei ermittelt haben, können wir sie mithilfe von file_get_contents () in unsere private $ _template-Eigenschaft laden. Fügen Sie in class.template.inc.php den folgenden Fettcode zu _load_template () hinzu, um unsere Vorlage (oder einen Standard) zu laden:
1 |
<?php
|
2 |
|
3 |
/** * A templating engine * ... */ class Template { |
4 |
|
5 |
/** * Stores the location of the template file * @var string */ public $template, |
6 |
|
7 |
/** * Stores the entries to be parsed in the template * @var array */ $entries = array(); |
8 |
|
9 |
/** * Stores the contents of the template file * @var string */ private $_template; |
10 |
|
11 |
//TODO: Write a public method to output the result of the templating engine
|
12 |
|
13 |
/** * Loads a template file with which markup should be formatted * ... */ private function _load_template( ) { // Check for a custom template $template_file = 'assets/templates/' . |
14 |
$this->template_file; if( file_exists($template_file) && is_readable($template_file) ) { $path = $template_file; } |
15 |
|
16 |
// Look for a system template else if( file_exists($default_file = 'assets/templates/default.inc') && is_readable($default_file) ) { $path = $default_file; }
|
17 |
|
18 |
// If the default template is missing, throw an error else { throw new Exception( 'No default template found' ); }
|
19 |
|
20 |
// Load the contents of the file and return them $this->_template = file_get_contents($path); }
|
21 |
|
22 |
//TODO: Write a private method to parse the template
|
23 |
|
24 |
//TODO: Write a static method to replace the template tags with entry data
|
25 |
|
26 |
//TODO: Write a private currying function to facilitate tag replacement
|
27 |
|
28 |
}
|
Schritt 6 Analysieren Sie die Vorlage
Um unsere Vorlage zu analysieren, müssen wir eine Liste von Schritten planen, die ausgeführt werden, um die Daten ordnungsgemäß zu behandeln:
- Entfernen Sie alle PHP-style-Tags aus der Vorlage
- Extrahieren Sie die Haupteintragsschleife aus der Datei
- Definieren Sie einen regulären Ausdruck, der mit einem beliebigen Schablonen-Tag übereinstimmt
- Aktivieren Sie die Funktion, die die Tags durch Eingabedaten ersetzt
- Extrahieren Sie die Kopfzeile und behandeln Sie Template-Tags, falls sie existieren
- Extrahieren Sie die Fußzeile und behandeln Sie Template-Tags, falls vorhanden
- Verarbeiten Sie jeden Eintrag und fügen Sie seine Werte in die Schleife ein
- Geben Sie die formatierten Einträge mit der Kopf- und Fußzeile zurück
Aber zuerst ein Wort über reguläre Ausdrücke
Mit unserer geladenen Vorlage und einem Angriffsplan können wir den Prozess des Parsens starten. Das ist ein wenig komplizierter, weil wir in reguläre Ausdrücke kommen, die für Entwickler sowohl einschüchternd als auch berauschend sein können. Bevor wir also verrückt werden, nehmen wir uns eine Sekunde. Atme tief durch und wiederhole nach mir: "Mit großer Kraft kommt große Verantwortung. Ich werde nur reguläre Ausdrücke verwenden, wenn es keinen einfacheren Weg gibt, das gewünschte Ergebnis zu erzielen. Denn jedes Mal, wenn ich reguläre Ausdrücke missbrauche, verliert ein anderer Programmierer Das Wochenende weinte in seinem vierten Red Bull, während er versuchte, das Chaos zu entschlüsseln, das ich gemacht habe. " Mit diesem Versprechen im Hinterkopf, lassen Sie uns in Erfahrung bringen. Erstellen Sie eine neue private Methode in der Template-Klasse namens _parse_template (), um die Regexes zu behandeln, die wir schreiben werden.
1 |
<?php
|
2 |
|
3 |
/** * A templating engine * ... */ class Template { |
4 |
|
5 |
/** * Stores the location of the template file * @var string */ public $template, |
6 |
|
7 |
/** * Stores the entries to be parsed in the template * @var array */ $entries = array(); |
8 |
|
9 |
/** * Stores the contents of the template file * @var string */ private $_template; |
10 |
|
11 |
//TODO: Write a public method to output the result of the templating engine
|
12 |
|
13 |
/** * Loads a template file with which markup should be formatted * ... */ private function _load_template( ) {...} |
14 |
|
15 |
/** * Separates the template into header, loop, and footer for parsing * * @param array $extra Additional content for the header/footer * @return string The entry markup */ private function |
16 |
_parse_template( $extra=NULL ) { //TODO: Remove any PHP-style comments from the template |
17 |
|
18 |
//TODO: Extract the main entry loop from the file
|
19 |
|
20 |
//TODO: Extract the header from the template if one exists
|
21 |
|
22 |
//TODO: Extract the footer from the template if one exists
|
23 |
|
24 |
//TODO: Define a regex to match any template tag
|
25 |
|
26 |
//TODO: Process each entry and insert its values into the loop
|
27 |
|
28 |
//TODO: Curry the function that will replace the tags with entry data
|
29 |
|
30 |
//TODO: If extra data was passed to fill in the header/footer, parse it here
|
31 |
|
32 |
//TODO: Return the formatted entries with the header and footer }
|
33 |
|
34 |
//TODO: Write a static method to replace the template tags with entry data
|
35 |
|
36 |
//TODO: Write a private currying function to facilitate tag replacement
|
37 |
|
38 |
}
|
Mach dich bereit zum Testen
Zu Testzwecken benötigen wir drei Dinge:
- Eine Vorlagendatei zum Testen
- Eine Methode, um generiertes Markup aus der Template-Klasse zurückzugeben
- Änderungen an index.php, die das zurückgegebene Markup ausgeben
Lassen Sie uns zunächst eine Beispielvorlage zusammenstellen, die alle Feature testet, die wir in _parse_template erstellen werden. ( Erstellen Sie in assets / templates / eine neue Datei namens template-test.inc, und fügen Sie Folgendes ein:
1 |
/* * This is a test comment */ <h2>Template Header</h2> |
2 |
|
3 |
{loop} |
4 |
|
5 |
// Entry data will be processed here This is content that should be displayed. {test}
|
6 |
|
7 |
{/loop} |
8 |
|
9 |
/* * This is another block comment */ Template footer. |
Als nächstes müssen wir unsere öffentliche Methode zum Generieren von Markup definieren, die generate_markup () heißen wird. Diese Methode ruft einfach _load_template () und _parse_template () auf und gibt das resultierende HTML-Markup aus. Die Methode generate_markup () akzeptiert schließlich zusätzliche Daten, die in die Kopf- oder Fußzeile einer Vorlage eingefügt werden können. Daher werden wir uns auch auf diese Funktion vorbereiten, indem wir der Methode $ extra ein Argument hinzufügen.
1 |
<?php
|
2 |
|
3 |
/** * A templating engine * ... */ class Template { |
4 |
|
5 |
/** * Stores the location of the template file * @var string */ public $template, |
6 |
|
7 |
/** * Stores the entries to be parsed in the template * @var array */ $entries = array(); |
8 |
|
9 |
/** * Stores the contents of the template file * @var string */ private $_template; |
10 |
|
11 |
/** * Generates markup by inserting entry data into the template file * * @param array $extra Extra data for the header/footer * @return string The HTML with entry data inserted into the
|
12 |
template */ public function generate_markup( $extra=array() ) { $this->_load_template(); return $this->_parse_template($extra); } |
13 |
|
14 |
/** * Loads a template file with which markup should be formatted * ... */ private function _load_template( ) {...} |
15 |
|
16 |
/** * Separates the template into header, loop, and footer for parsing * ... */ private function _parse_template( $extra=NULL ) {...} |
17 |
|
18 |
//TODO: Write a static method to replace the template tags with entry data
|
19 |
|
20 |
//TODO: Write a private currying function to facilitate tag replacement
|
21 |
|
22 |
}
|
Abschließend ändern wir index.php, um den zurückgegebenen Wert von generate_markup () auszugeben. Verwenden Sie dazu require_once, um die Template-Klassendatei einzuschließen, und erstellen Sie dann eine Instanz davon. Mit unserem neuen Template-Objekt können wir den Namen der Vorlagendatei definieren und das Ergebnis von generate_markup () an den Browser zurückgeben:
1 |
<?php
|
2 |
|
3 |
// Error reporting is turned up to 11 for the purposes of this demo ini_set("display_errors",1); ERROR_REPORTING(E_ALL);
|
4 |
|
5 |
// Exception handling set_exception_handler('exception_handler'); function exception_handler( $exception ) { echo $exception->getMessage(); }
|
6 |
|
7 |
// Load the Template class require_once 'system/class.template.inc.php';
|
8 |
|
9 |
// Create a new instance of the Template class $template = new Template;
|
10 |
|
11 |
// Set the testing template file location $template->template_file = 'template-test.inc';
|
12 |
|
13 |
// Output the template markup echo $template->generate_markup();
|
14 |
|
15 |
/** * Loads entries from the Envato API for a given site * ... */ function load_envato_blog_posts( $site='themeforest' ) {...} |
Jetzt müssen wir nur noch vorübergehend
arbeiten geben Sie den Vorlageninhalt am unteren Rand von _parse_template () aus, damit wir sehen können, was beim Analysieren der Vorlage passiert:
1 |
<?php
|
2 |
|
3 |
/** * A templating engine * ... */ class Template { |
4 |
|
5 |
/** * Stores the location of the template file * @var string */ public $template, |
6 |
|
7 |
/** * Stores the entries to be parsed in the template * @var array */ $entries = array(); |
8 |
|
9 |
/** * Stores the contents of the template file * @var string */ private $_template; |
10 |
|
11 |
/** * Generates markup by inserting entry data into the template file * ... */ public function generate_markup( $extra=array() ) {...} |
12 |
|
13 |
/** * Loads a template file with which markup should be formatted * ... */ private function _load_template( ) {...} |
14 |
|
15 |
/** * Separates the template into header, loop, and footer for parsing * ... */ private function _parse_template( $extra=NULL ) { //TODO: Remove any PHP-style comments from the template |
16 |
|
17 |
//TODO: Extract the main entry loop from the file
|
18 |
|
19 |
//TODO: Extract the header from the template if one exists
|
20 |
|
21 |
//TODO: Extract the footer from the template if one exists
|
22 |
|
23 |
//TODO: Define a regex to match any template tag
|
24 |
|
25 |
//TODO: Process each entry and insert its values into the loop
|
26 |
|
27 |
//TODO: Curry the function that will replace the tags with entry data
|
28 |
|
29 |
//TODO: If extra data was passed to fill in the header/footer, parse it here
|
30 |
|
31 |
//TODO: Return the formatted entries with the header and footer
|
32 |
|
33 |
// TEMPORARY: return the template after comment removal return $this->_template; }
|
34 |
|
35 |
//TODO: Write a static method to replace the template tags with entry data
|
36 |
|
37 |
//TODO: Write a private currying function to facilitate tag replacement
|
38 |
|
39 |
}
|
Stellen Sie sicher, dass dies funktioniert, indem Sie index.php in Ihrem Browser laden. Es sollte folgendes erzeugen:
1 |
/* *
|
2 |
This is a test comment */ Template Header {loop} // Entry data will be processed here |
3 |
|
4 |
This is content that should be displayed. {test} {/loop} /* * This is another block comment */ |
5 |
|
6 |
Template footer. |
Entfernen Sie Kommentare aus der Vorlagendatei
Unsere ersten Regexes entfernen alle PHP-artigen Kommentare aus einer Vorlagendatei. Obwohl dies kein unbedingt notwendiger Schritt ist, denken Sie daran, dass wir versuchen, dieses System so benutzerfreundlich wie möglich zu halten. Es wäre sehr hilfreich für einen potenziellen Template-Builder zu wissen, welche Tags verfügbar sind, aber es ist wahrscheinlich nicht wünschenswert, diese Kommentare in Ihrem endgültigen Markup zu haben (ganz zu schweigen davon, dass ein PHP-artiger Kommentar HTML-Layouts bricht). Die beiden Kommentarstile, denen wir uns nähern, sind die empfohlenen Kommentarstile in PHP:
1 |
/* * This is a block-level comment */
|
2 |
|
3 |
// This is a one-line comment
|
Konzentrieren wir uns zunächst auf die Regex, die Kommentare auf Blockebene aufzeichnet. Dies muss mit jedem String übereinstimmen, der mit / * beginnt und mit * / endet. Direkt vor dem Tor wird unsere Regex so aussehen:
1 |
$pattern = '#/\*\*/#'; |
Da der Standard-Regex-Begrenzer der Schrägstrich (/) ist, verwenden wir einen alternativen Begrenzer, um die Anzahl der in unserem Regex benötigten Escape-Zeichen zu reduzieren. Das Nummernzeichen (#) ist ein perfekt gültiges Regex-Trennzeichen - obwohl ich normalerweise den Standard-Schrägstrich für die Übersichtlichkeit empfehle, kann in bestimmten Fällen die Verwendung des Standards die Lesbarkeit eines regulären Ausdrucks verringern. Vergleichen Sie Folgendes:
1 |
$pattern = '#/\*\*/#'; // Valid and easy to read |
2 |
|
3 |
$pattern = '/\/\*\*\//'; // Identical in function, but a little harder to read |
Wenn wir diesen Regex unverändert in _parse_template () einfügen, führt dies jedoch nicht zum gewünschten Ergebnis. Fügen Sie den folgenden fett gedruckten Code zu _parse_template () hinzu, um das Ergebnis unserer aktuellen Regex zu sehen:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) { // Create an alias of the template file property to save space $template = $this->_template; |
14 |
|
15 |
// Remove any PHP-style comments from the template $comment_pattern = '#/\*\*/#'; $template = preg_replace($comment_pattern, NULL, $template);
|
16 |
|
17 |
//TODO: To-do items snipped for brevity...
|
18 |
|
19 |
// TEMPORARY: return the template after comment removal return $template; }
|
20 |
|
21 |
//TODO: Write a static method to replace the template tags with entry data
|
22 |
|
23 |
//TODO: Write a private currying function to facilitate tag replacement
|
24 |
|
25 |
}
|
Hinweis: Für den Rest dieses Artikels werden Docblock-Kommentare weggelassen, um Platz zu sparen (es sei denn, sie sind neu). Die Ausgabe in Ihrem Browser ändert sich nicht, wenn Sie index.php neu laden; Dies geschieht, weil wir das Platzhalterzeichen nicht mit einem Modifizierer hinzugefügt haben, um null oder mehr Zeichen zwischen dem Öffnen und Schließen des Kommentars (. *) zu berücksichtigen. Außerdem müssen wir berücksichtigen, dass Kommentare auf Blockebene normalerweise mehrzeilig sind. Daher müssen wir den Modifikator s hinzufügen, um dies zu berücksichtigen. Unser modifizierter Regex sollte so aussehen:
1 |
$comment_pattern = '#/\*.*\*/#s'; |
Passen Sie dies in _parse_template () an, und laden Sie index.php in Ihrem Browser neu. Hoppla! Die Ausgabe ist:
1 |
Template footer. |
Wir haben vergessen, das Platzhalterzeichen zu träge zu machen, und anstatt am Ende des ersten Kommentars zu stoppen, ging es weiter zum Ende des zweiten Blockkommentars. Dies ist jedoch leicht zu beheben: Fügen Sie einfach ein Fragezeichen nach dem Platzhalter hinzu, um es faul zu machen. Es sollte so aussehen:
1 |
$comment_pattern = '#/\*.*?\*/#s'; |
Passen Sie _parse_template () an und laden Sie index.php erneut. Viel besser!
1 |
Template Header {loop} // Entry data will be processed here |
2 |
|
3 |
This is content that should be displayed. {test} {/loop} |
4 |
|
5 |
Template footer. |
Als nächstes müssen wir auf Inline-Kommentare abzielen (die mit zwei Schrägstrichen (//) beginnen. Da diese nicht mehrzeilig sind, ist dies eigentlich eine eigene Regex, anstatt den Kommentar-Regex auf Blockebene zu erweitern. Wir müssen nach zwei Schrägstrichen (//) einen beliebigen Text finden. Die einzige Ausnahme von dieser Regel sind die zwei Schrägstriche in einer entfernten URL (http: //) - um dies auszuschließen, verwenden wir einen negativen Lookbehind Der fertige reguläre Ausdruck sollte folgendermaßen aussehen:
1 |
#(?<!:)//.*#
|
Fügen Sie dies zu _parse_template () hinzu, indem Sie die Variable $ comment_pattern in ein Array mit unserem Regex auf Blockebene als erstem Element und dem inline-Kommentar regex als zweiten ändern:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) { // Create an alias of the template file property to save space $template = $this->_template; |
14 |
|
15 |
// Remove any PHP-style comments from the template $comment_pattern = array('#/\*.*?\*/#s', '#(?<!:)//.*#'); $template = preg_replace($comment_pattern, NULL, $template);
|
16 |
|
17 |
//TODO: To-do items snipped for brevity...
|
18 |
|
19 |
// TEMPORARY: return the template after comment removal return $template; }
|
20 |
|
21 |
//TODO: Write a static method to replace the template tags with entry data
|
22 |
|
23 |
//TODO: Write a private currying function to facilitate tag replacement
|
24 |
|
25 |
}
|
Jetzt werden die Kommentare korrekt gelöscht, wenn Sie index.php in Ihrem Browser neu laden:
1 |
Template Header |
2 |
{loop} |
3 |
|
4 |
This is content that should be displayed. {test} {/loop} |
5 |
|
6 |
Template footer. |
Trennen Sie die Kopfzeile, Fußzeile und die Schleife für die Verarbeitung
Unsere nächste Aufgabe besteht darin, die Vorlage in drei Abschnitte aufzuteilen: den Header, die Schleife und die Fußzeile. Diese werden nicht unbedingt existieren
in allen Fällen müssen wir das auch überprüfen.
Isolieren Sie die Haupteingangsschleife
Als erstes greifen wir die Schleife auf, indem wir den gesamten Inhalt zwischen den Template-Tags {loop} und {/ loop} abfangen. Diese Regex wird der gesamten Vorlage entsprechen und eine Erfassungsgruppe verwenden, um die Schleife zu identifizieren:
1 |
#.*{loop}(.*?){/loop}.*#is
|
Ändern Sie _parse_template () wie fett dargestellt, um zu testen, ob die Entry-Schleife korrekt extrahiert wird:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) { // Create an alias of the template file property to save space $template = $this->_template; |
14 |
|
15 |
// Remove any PHP-style comments from the template $comment_pattern = array('#/\*.*?\*/#s', '#(?<!:)//.*#'); $template = preg_replace($comment_pattern, NULL, $template);
|
16 |
|
17 |
// Extract the main entry loop from the file $pattern = '#.*{loop}(.*?){/loop}.*#is'; $entry_template = preg_replace($pattern, "$1", $template);
|
18 |
|
19 |
//TODO: To-do items snipped for brevity...
|
20 |
|
21 |
// TEMPORARY: return the loop after isolating it return $entry_template; }
|
22 |
|
23 |
//TODO: Write a static method to replace the template tags with entry data
|
24 |
|
25 |
//TODO: Write a private currying function to facilitate tag replacement
|
26 |
|
27 |
}
|
HINWEIS: Vergessen Sie nicht, die Funktion zu ändern, um $ entry_template zurückzugeben, damit Sie die korrekte Ausgabe sehen können. Durch Verwendung von preg_replace () zum "Ersetzen" der gesamten Vorlage durch nur die aufgezeichnete Schleifenvorlage haben wir die Hauptschleife erfolgreich isoliert. Lade index.php in deinem Browser neu ein und du solltest folgendes sehen:
1 |
This is content that should be displayed. {test}
|
Isolieren Sie die Kopfzeile
Lassen Sie uns als nächstes den Header aus der Vorlage entfernen. Die Regex wird ähnlich wie die Hauptschleife sein, aber dieses Mal werden wir den Inhalt vor dem {loop} -Template-Tag erfassen. Um den Header anzupassen, müssen wir am Anfang der Vorlage beginnen und alles bis zum Tag {loop} erfassen. Da wir preg_replace () verwenden, um den Header zu extrahieren, müssen wir auch alles nach dem Tag {loop} abgleichen, um sicherzustellen, dass es entfernt wird, wenn der Austausch stattfindet. Diese Regex sollte, wenn sie fertig ist, so aussehen:
1 |
/^(.*)?{loop.*$/is |
Da einige Vorlagen keinen Header benötigen, müssen wir auch überprüfen, dass die im Header zurückgegebenen Daten nicht die gesamte Vorlage sind. Wenn dies geschieht, sollte der Header auf NULL gesetzt werden, um doppelte Daten zu vermeiden. Ändern Sie _parse_template () mit dem fett gedruckten Code und setzen Sie sie so, dass der extrahierte Header zurückgegeben wird:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) { // Create an alias of the template file property to save space $template = $this->_template; |
14 |
|
15 |
// Remove any PHP-style comments from the template $comment_pattern = array('#/\*.*?\*/#s', '#(?<!:)//.*#'); $template = preg_replace($comment_pattern, NULL, $template);
|
16 |
|
17 |
// Extract the main entry loop from the file $pattern = '#.*{loop}(.*?){/loop}.*#is'; $entry_template = preg_replace($pattern, "$1", $template);
|
18 |
|
19 |
// Extract the header from the template if one exists $header = trim(preg_replace('/^(.*)?{loop.*$/is', "$1", $template)); if( $header===$template ) { $header = NULL; }
|
20 |
|
21 |
//TODO: To-do items snipped for brevity...
|
22 |
|
23 |
// TEMPORARY: return the header after isolating it return $header; }
|
24 |
|
25 |
//TODO: Write a static method to replace the template tags with entry data
|
26 |
|
27 |
//TODO: Write a private currying function to facilitate tag replacement
|
28 |
|
29 |
}
|
Wie erwartet, führt das erneute Laden von index.php in Ihrem Browser dazu, dass die Kopfdaten angezeigt werden:
1 |
Template Header |
Isolieren Sie die Fußzeile
Die Fußzeile ist der Kopfzeile sehr ähnlich, in der sie extrahiert wird, außer dass wir diesmal die Daten nach dem {/ loop} -Tag erfassen und sicherstellen, dass sie nicht mit der gesamten Vorlage, die diese Regex verwendet, übereinstimmen:
1 |
#^.*?{/loop}(.*)$#is
|
Fügen Sie dies in _parse_template () ein und legen Sie die Methode so fest, dass der Fußzeileninhalt wie beim Header mit dem Fettcode zurückgegeben wird:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) { // Create an alias of the template file property to save space $template = $this->_template; |
14 |
|
15 |
// Remove any PHP-style comments from the template $comment_pattern = array('#/\*.*?\*/#s', '#(?<!:)//.*#'); $template = preg_replace($comment_pattern, NULL, $template);
|
16 |
|
17 |
// Extract the main entry loop from the file $pattern = '#.*{loop}(.*?){/loop}.*#is'; $entry_template = preg_replace($pattern, "$1", $template);
|
18 |
|
19 |
// Extract the header from the template if one exists $header = trim(preg_replace('/^(.*)?{loop.*$/is', "$1", $template)); if( $header===$template ) { $header = NULL; }
|
20 |
|
21 |
// Extract the footer from the template if one exists $footer = trim(preg_replace('#^.*?{/loop}(.*)$#is', "$1", $template)); if( $footer===$template ) { $footer = NULL; }
|
22 |
|
23 |
//TODO: To-do items snipped for brevity...
|
24 |
|
25 |
// TEMPORARY: return the footer after isolating it return $footer; }
|
26 |
|
27 |
//TODO: Write a static method to replace the template tags with entry data
|
28 |
|
29 |
//TODO: Write a private currying function to facilitate tag replacement
|
30 |
|
31 |
}
|
Laden Sie die Indexdatei neu, um die Fußzeilenausgabe anzuzeigen:
1 |
Template footer. |
Identifizieren Sie Vorlagen-Tags mit regulären Ausdrücken
Der nächste Schritt in unserem Prozess besteht darin, eine Regex zu erstellen, die mit jedem Schablonen-Tag übereinstimmt, sodass wir sie durch Eintragsdaten ersetzen können. Anders als die anderen, die wir geschrieben haben, sollte diese nicht mit der gesamten Vorlage übereinstimmen. Dieses Muster sollte nur mit dem Template-Tag übereinstimmen, das ersetzt wird. Um dies zu erreichen, können wir die Kurzschrift verwenden, um ein beliebiges Wortzeichen (\ w entspricht [A-Za-z0-9_]) zu finden und einem oder mehreren Zeichen zwischen geschweiften Klammern zu entsprechen. Die komplette Regex sieht so aus:
1 |
/{(\w+)}/ |
Wir werden überprüfen, ob dies nur ein bisschen funktioniert, aber lassen Sie uns zunächst das Muster für später definieren, indem Sie _parse_template () den Fettcode hinzufügen:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) { // Create an alias of the template file property to save space $template = $this->_template; |
14 |
|
15 |
// Remove any PHP-style comments from the template $comment_pattern = array('#/\*.*?\*/#s', '#(?<!:)//.*#'); $template = preg_replace($comment_pattern, NULL, $template);
|
16 |
|
17 |
// Extract the main entry loop from the file $pattern = '#.*{loop}(.*?){/loop}.*#is'; $entry_template = preg_replace($pattern, "$1", $template);
|
18 |
|
19 |
// Extract the header from the template if one exists $header = trim(preg_replace('/^(.*)?{loop.*$/is', "$1", $template)); if( $header===$template ) { $header = NULL; }
|
20 |
|
21 |
// Extract the footer from the template if one exists $footer = trim(preg_replace('#^.*?{/loop}(.*)$#is', "$1", $template)); if( $footer===$template ) { $footer = NULL; }
|
22 |
|
23 |
// Define a regex to match any template tag $tag_pattern = '/{(\w+)}/';
|
24 |
|
25 |
//TODO: To-do items snipped for brevity... }
|
26 |
|
27 |
//TODO: Write a static method to replace the template tags with entry data
|
28 |
|
29 |
//TODO: Write a private currying function to facilitate tag replacement
|
30 |
|
31 |
}
|
Mit all unseren regulären Ausdrücken, die bereit sind zu rocken, können wir uns der nächsten großen Aufgabe zuwenden.
Schritt 7 Machen Sie sich bereit für den Austausch von Vorlagen-Tags: Curry
Um unsere Template-Tags durch die Eigenschaften unserer Einträge zu ersetzen, müssen wir ein Konzept namens currying verwenden, bei dem eine Funktion erstellt wird, die mehrere Argumente in eine Kette von Funktionen aufnimmt, die ein einzelnes Argument akzeptieren.
Warten. Was hast du gerade gesagt?
Curry ist ein etwas komisches Konzept, um den Kopf herumzukriegen, also nehmen wir uns eine Sekunde Zeit, um genau zu sehen, wie es funktioniert. Das Ziel einer Curry-Funktion ist es, ein Argument gleichzeitig einer Funktion zuzuführen, die mehrere Argumente erfordert, ohne Fehler zu verursachen. Betrachten wir zum Beispiel eine einfache mathematische Funktion:
1 |
function add( $x, $y ) { return $x + $y; } |
Wenn wir diese Funktion normal aufrufen wollten, machen wir einfach folgendes:
1 |
echo add(1, 2); // Output: 3 |
Aber lassen Sie uns sagen, dass wir - aus welchen Gründen auch immer - die Funktion add () inkrementell aufrufen mussten; Wir können jetzt nicht einfach ein Argument hinzufügen und ein anderes später (indem wir add (1) aufrufen). Das gibt eine Warnung aus:
1 |
Warning: Missing argument 2 for add()... |
2 |
|
3 |
Notice: Undefined variable: y in ... |
Wir brauchen also einen Zwischenschritt, der prüft, ob die richtige Anzahl an Argumenten übergeben wurde. Wenn dies der Fall ist, wird die Funktion wie üblich ausgeführt. Wenn es jedoch zu wenige Argumente gibt, wird eine neue Funktion mit dem Funktionsnamen und dem ersten Argument zurückgegeben. Wenn das zweite Argument übergeben wird, kann die ursprüngliche Funktion ordnungsgemäß aufgerufen werden. Wenn wir unsere add () - Funktion als Beispiel verwenden und annehmen, dass wir diese Funktion mit der imaginären Funktion curry () curren können, können wir diesen Prozess demonstrieren:
1 |
/* * Start by currying the function - argument 1 is the function name, * argument 2 is the number of arguments expected */ $curried = curry('add', 2); // Returns the curried add function |
2 |
|
3 |
$partial = $curried(1); // Returns a function with the argument '1' stored |
4 |
|
5 |
echo $partial(2); // Output: 3 |
6 |
|
7 |
// Try passing the expected number of arguments to the curried function echo $curried(2, 2); // Output: 4
|
Schreibe die Curry-Methode
Jetzt, wo wir wissen, wie Currying funktioniert, fangen wir an, die Funktion zu schreiben. Die Curry-Funktion selbst gibt immer eine Funktion zurück, die wir mit create_function () ausführen. Die erstellte Funktion prüft, ob die richtige Anzahl von Argumenten existiert und führt die Curry-Funktion wie gewohnt aus, wenn sie dies mit call_user_func_array () tut. Wenn nicht genügend Argumente vorhanden sind, wird eine andere Funktion mit create_function () zurückgegeben. Da das Zusammenstellen von Funktionen die Erstellung von Funktionen innerhalb von Funktionen erfordert, gibt es eine Menge zu entkommen. Dies macht unsere Curry-Methode verwirrender als sie wirklich ist. Fügen Sie es mit folgendem fettem Code zur Template-Klasse hinzu:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) {...} |
14 |
|
15 |
//TODO: Write a static method to replace the template tags with entry data
|
16 |
|
17 |
/** * A currying function * * Currying allows a function to be called incrementally. This means that if * a function accepts two arguments, it can be curried with only one * argument supplied,
|
18 |
which returns a new function that will accept the * remaining argument and return the output of the original curried function * using the two supplied parameters. * * Example:
|
19 |
|
20 |
function add($a, $b) { return $a + $b; }
|
21 |
|
22 |
$func = $this->_curry('add', 2);
|
23 |
|
24 |
$func2 = $func(1); // Stores 1 as the first argument of add()
|
25 |
|
26 |
echo $func2(2); // Executes add() with 2 as the second arg and outputs 3
|
27 |
|
28 |
* @param string $function The name of the function to curry * @param int $num_args The number of arguments the function accepts * @return mixed Function or return of the curried function */
|
29 |
private function _curry( $function, $num_args ) { return create_function('', " // Store the passed arguments in an array \$args = func_get_args(); |
30 |
|
31 |
// Execute the function if the right number of arguments were passed if( count(\$args)>=$num_args ) { return call_user_func_array('$function', \$args); }
|
32 |
|
33 |
// Export the function arguments as executable PHP code \$args = var_export(\$args, 1);
|
34 |
|
35 |
// Return a new function with the arguments stored otherwise return create_function('',' \$a = func_get_args(); \$z = ' . \$args . '; \$a = array_merge(\$z,\$a); return
|
36 |
call_user_func_array(\'$function\', \$a); '); "); } |
37 |
|
38 |
}
|
Wie wirkt sich Curry auf das Templating System aus?
Im Moment ist vielleicht nicht klar, wie das alles auf das Templating-System zutrifft. Wenn wir zum nächsten Schritt kommen, werden wir das im Detail besprechen, aber kurz gesagt, wir müssen in der Lage sein, eine Funktion mit zwei Argumenten aufzurufen, um die Template-Tags zu ersetzen: Ein Argument ist der Eintrag, aus dem die Daten stammen sollen gezogen, und der andere ist der Template-Tag ersetzt werden. Da wir reguläre Ausdrücke verwenden, um die Tags zu ersetzen, müssen wir preg_replace_callback () verwenden, um die Ersetzungen vorzunehmen. Da der an diese Funktion übergebene Callback jedoch nur ein Argument - den übereinstimmenden Text - akzeptieren kann, müssen wir eine Curry-Funktion übergeben, in der bereits der Eintrag gespeichert ist. Sinn ergeben? Machen wir es möglich!
Schritt 8 Ersetzen Sie Vorlagen-Tags durch übereinstimmende Eintragsdaten
Alle Teile sind vorhanden. Jetzt müssen wir nur die Punkte verbinden und es fertig machen.
Schreiben Sie die Tag-Ersetzungsmethode
Das Ersetzen von Tags ist relativ einfach: Nehmen Sie den übereinstimmenden Text aus dem Schablonentag und überprüfen Sie, ob im Eintragsobjekt eine Eigenschaft mit diesem Namen vorhanden ist. Wenn ja, geben Sie die in der Eigenschaft gespeicherten Daten zurück; Wenn nicht, geben Sie einfach das Schablonen-Tag zurück, damit der Designer sehen kann, dass etwas schief gelaufen ist (oder in Randfällen bricht die ungerade Zeichenfolge, die in geschweifte Klammern eingeschlossen wurde, nicht ab). Diese Funktion wird replace_tags () heißen, und sie wird statisch sein, damit sie als gültiger Callback an die Curry-Funktion übergeben werden kann. Fügen Sie es mit dem folgenden fettgedruckten Code zur Template-Klasse hinzu:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) {...} |
14 |
|
15 |
/** * Replaces template tags with the corresponding entry data * * @param string $entry A serialized entry object * @param array $params Parameters for replacement * @param array $matches The match array from preg_replace_callback() * @return string The replaced template value */ public static function replace_tags($entry, $matches) { // Unserialize the object $entry = unserialize($entry); |
16 |
|
17 |
// Make sure the template tag has a matching array element if( property_exists($entry, $matches[1]) ) { // Grab the value from the Entry object return $entry->{$matches[1]}; }
|
18 |
|
19 |
// Otherwise, simply return the tag as is else { return "{".$matches[1]."}"; } }
|
20 |
|
21 |
private function _curry( $function, $num_args ) {...} |
22 |
|
23 |
}
|
Hinweis: der Aufruf von unserialize () am Anfang dieser Methode ist auf ein Problem mit dem Übergeben eines Objekts durch eine Curryfunktion zurückzuführen. Wir werden das Objekt im nächsten Schritt serialisieren.
Ändern Sie die Template-Analysemethode, um Template-Tags zu ersetzen
Um unser Templating-System zu vervollständigen, müssen wir zunächst unsere Curriculum-Callback-Funktion für alle Ersatzanrufe bereitstellen. Dies geschieht, indem Sie Template :: replace_tags () curren und in einer Variablen namens $ callback speichern. Fügen Sie dies zu _parse_template () mit dem folgenden fett gedruckten Code hinzu:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) { // Create an alias of the template file property to save space $template = $this->_template; |
14 |
|
15 |
// Remove any PHP-style comments from the template $comment_pattern = array('#/\*.*?\*/#s', '#(?<!:)//.*#'); $template = preg_replace($comment_pattern, NULL, $template);
|
16 |
|
17 |
// Extract the main entry loop from the file $pattern = '#.*{loop}(.*?){/loop}.*#is'; $entry_template = preg_replace($pattern, "$1", $template);
|
18 |
|
19 |
// Extract the header from the template if one exists $header = trim(preg_replace('/^(.*)?{loop.*$/is', "$1", $template)); if( $header===$template ) { $header = NULL; }
|
20 |
|
21 |
// Extract the footer from the template if one exists $footer = trim(preg_replace('#^.*?{/loop}(.*)$#is', "$1", $template)); if( $footer===$template ) { $footer = NULL; }
|
22 |
|
23 |
// Define a regex to match any template tag $tag_pattern = '/{(\w+)}/';
|
24 |
|
25 |
// Curry the function that will replace the tags with entry data $callback = $this->_curry('Template::replace_tags', 2);
|
26 |
|
27 |
//TODO: To-do items snipped for brevity... }
|
28 |
|
29 |
public static function replace_tags($entry, $matches) {...} |
30 |
|
31 |
private function _curry( $function, $num_args ) {...} |
32 |
|
33 |
}
|
Als nächstes müssen wir eine Schleife einrichten, um jedes Eintragsobjekt im Array $ entries zu durchlaufen. Mit jedem Eintrag müssen wir preg_replace_callback () mit dem Template-Tag regex als erstes Argument, dem Callback mit dem serialisierten entry-Objekt als zweitem Argument und der Schleife als drittem Argument aufrufen. Das von jedem Aufruf zurückgegebene Markup sollte an eine Variable mit dem Namen $ Markup angehängt werden, die das gesamte Markup des Eintrags speichert, das an den Browser ausgegeben werden soll. Fügen Sie dies zu _parse_template () mit dem Fettcode hinzu:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) { // Create an alias of the template file property to save space $template = $this->_template; |
14 |
|
15 |
// Remove any PHP-style comments from the template $comment_pattern = array('#/\*.*?\*/#s', '#(?<!:)//.*#'); $template = preg_replace($comment_pattern, NULL, $template);
|
16 |
|
17 |
// Extract the main entry loop from the file $pattern = '#.*{loop}(.*?){/loop}.*#is'; $entry_template = preg_replace($pattern, "$1", $template);
|
18 |
|
19 |
// Extract the header from the template if one exists $header = trim(preg_replace('/^(.*)?{loop.*$/is', "$1", $template)); if( $header===$template ) { $header = NULL; }
|
20 |
|
21 |
// Extract the footer from the template if one exists $footer = trim(preg_replace('#^.*?{/loop}(.*)$#is', "$1", $template)); if( $footer===$template ) { $footer = NULL; }
|
22 |
|
23 |
// Define a regex to match any template tag $tag_pattern = '/{(\w+)}/';
|
24 |
|
25 |
// Curry the function that will replace the tags with entry data $callback = $this->_curry('Template::replace_tags', 2);
|
26 |
|
27 |
// Process each entry and insert its values into the loop $markup = NULL; for( $i=0, $c=count($this->entries); $i<$c; ++$i ) { $markup .= preg_replace_callback( $tag_pattern,
|
28 |
$callback(serialize($this->entries[$i])), $entry_template ); } |
29 |
|
30 |
//TEMPORARY: Output the markup after replacing template tags return $markup; }
|
31 |
|
32 |
public static function replace_tags($entry, $matches) {...} |
33 |
|
34 |
private function _curry( $function, $num_args ) {...} |
35 |
|
36 |
}
|
Bevor wir dies testen können, müssen wir einen Eintrag erstellen, damit die Engine etwas durchlaufen kann. Fügen Sie in index.php einen Dummy-Eintrag mit einer Eigenschaft namens
$ test, um dem Schablonen-Tag in unserer Testvorlagendatei zu entsprechen:
1 |
<?php
|
2 |
|
3 |
// Error reporting is turned up to 11 for the purposes of this demo ini_set("display_errors",1); ERROR_REPORTING(E_ALL);
|
4 |
|
5 |
// Exception handling set_exception_handler('exception_handler'); function exception_handler( $exception ) { echo $exception->getMessage(); }
|
6 |
|
7 |
// Load the Template class require_once 'system/class.template.inc.php';
|
8 |
|
9 |
// Create a new instance of the Template class $template = new Template;
|
10 |
|
11 |
// Set the testing template file location $template->template_file = 'template-test.inc';
|
12 |
|
13 |
$template->entries[] = (object) array( 'test' => 'This was inserted using template tags!' ); |
14 |
|
15 |
// Output the template markup echo $template->generate_markup();
|
16 |
|
17 |
/** * Loads entries from the Envato API for a given site * ... */ function load_envato_blog_posts( $site='themeforest' ) {...} |
Wenn ein Dummy-Eintrag vorhanden ist, können wir den Template-Tag-Austausch testen, indem wir index.php in unserem Browser neu laden. Die Ausgabe sollte lesen:
1 |
This is content that should be displayed. This was inserted using template tags! |
Dies wurde mit Template-Tags eingefügt! Wir sind jedoch noch nicht ganz fertig: Wir müssen noch die Möglichkeit hinzufügen, Template-Tags in der Kopf- und Fußzeile unserer Vorlagendatei zu ersetzen.
Ersetzen Sie Vorlagen-Tags in der Kopf- und Fußzeile der Vorlage
Der Prozess zum Ersetzen von Header- und Footer-Schablonentags ist identisch mit dem Prozess für die Benutzer in der Schleife, es sind jedoch andere Daten erforderlich, um dies zu tun. Sie erinnern sich vielleicht daran, dass wir ein Argument namens $ extra eingefügt haben, als wir die Methoden generate_markup () und _parse_template () geschrieben haben; Diese Variable wird als ein Objekt verwendet, das Daten zum Ersetzen der Kopf- und Fußschablonentags speichert. Für unsere Zwecke kann $ extra zwei Eigenschaften enthalten, $ header und $ footer, die beide ein Objekt speichern, dessen Eigenschaften zum Ersetzen von Vorlagen-Tags im entsprechenden Abschnitt der Vorlage verwendet werden. Wenn keine Kopf- oder Fußzeilen-Tags vorhanden sind, werden natürlich keine Daten in $ extra zur Verarbeitung gespeichert. Aus diesem Grund prüfen wir zunächst, ob $ extra ein Objekt ist. Wenn dies der Fall ist, durchlaufen wir seine Eigenschaften und führen preg_replace_callback () mit dem Schablonen-Tag Regex, dem Callback nach Übergabe des serialisierten Kopf- oder Fußzeilenobjekts und dem Kopf- oder Fußbereich der Schablone aus. Fügen Sie den folgenden fett gedruckten Code hinzu, um unser Vorlagensystem zu vervollständigen:
1 |
<?php
|
2 |
|
3 |
class Template { |
4 |
|
5 |
public $template, $entries = array(); |
6 |
|
7 |
private $_template; |
8 |
|
9 |
public function generate_markup( $extra=array() ) {...} |
10 |
|
11 |
private function _load_template( ) {...} |
12 |
|
13 |
private function _parse_template( $extra=NULL ) { // Create an alias of the template file property to save space $template = $this->_template; |
14 |
|
15 |
// Remove any PHP-style comments from the template $comment_pattern = array('#/\*.*?\*/#s', '#(?<!:)//.*#'); $template = preg_replace($comment_pattern, NULL, $template);
|
16 |
|
17 |
// Extract the main entry loop from the file $pattern = '#.*{loop}(.*?){/loop}.*#is'; $entry_template = preg_replace($pattern, "$1", $template);
|
18 |
|
19 |
// Extract the header from the template if one exists $header = trim(preg_replace('/^(.*)?{loop.*$/is', "$1", $template)); if( $header===$template ) { $header = NULL; }
|
20 |
|
21 |
// Extract the footer from the template if one exists $footer = trim(preg_replace('#^.*?{/loop}(.*)$#is', "$1", $template)); if( $footer===$template ) { $footer = NULL; }
|
22 |
|
23 |
// Define a regex to match any template tag $tag_pattern = '/{(\w+)}/';
|
24 |
|
25 |
// Curry the function that will replace the tags with entry data $callback = $this->_curry('Template::replace_tags', 2);
|
26 |
|
27 |
// Process each entry and insert its values into the loop $markup = NULL; for( $i=0, $c=count($this->entries); $i<$c; ++$i ) { $markup .= preg_replace_callback( $tag_pattern,
|
28 |
$callback(serialize($this->entries[$i])), $entry_template ); } |
29 |
|
30 |
// If extra data was passed to fill in the header/footer, parse it here if( is_object($extra) ) { foreach( $extra as $key=>$props ) { $$key = preg_replace_callback( $tag_pattern,
|
31 |
$callback(serialize($extra->$key)), $$key ); } } |
32 |
|
33 |
// Return the formatted entries with the header and footer reattached return $header . $markup . $footer; }
|
34 |
|
35 |
public static function replace_tags($entry, $matches) {...} |
36 |
|
37 |
private function _curry( $function, $num_args ) {...} |
38 |
|
39 |
}
|
Wenn Sie index.php in Ihrem Browser neu laden, sehen Sie folgende Ausgabe:
1 |
Template Header |
2 |
|
3 |
This is content that should be displayed. This was inserted using template tags! |
4 |
|
5 |
Template footer. |
Das letzte, was zu tun ist, ist das Ersetzen der Header- und Footer-Template-Tags zu testen. Öffnen Sie template-test.inc und fügen Sie zwei neue Vorlagen-Tags hinzu, eines in der Kopfzeile und eines in der Fußzeile:
1 |
/* * This is a test comment */ <h2>Template Header</h2> {header_stuff} |
2 |
|
3 |
{loop} |
4 |
|
5 |
// Entry data will be processed here This is content that should be displayed. {test}
|
6 |
|
7 |
{/loop} |
8 |
|
9 |
/* * This is another block comment */ Template footer. {footerStuff} |
Als nächstes gehen Sie zurück zu index.php und fügen ein neues Objek namens $ extra mit zwei Objekten
hinzu in den Eigenschaften $ header und $ footer gespeichert, deren Eigenschaften den neuen Schablonentags entsprechen:
1 |
<?php
|
2 |
|
3 |
// Error reporting is turned up to 11 for the purposes of this demo ini_set("display_errors",1); ERROR_REPORTING(E_ALL);
|
4 |
|
5 |
// Exception handling set_exception_handler('exception_handler'); function exception_handler( $exception ) { echo $exception->getMessage(); }
|
6 |
|
7 |
// Load the Template class require_once 'system/class.template.inc.php';
|
8 |
|
9 |
// Create a new instance of the Template class $template = new Template;
|
10 |
|
11 |
// Set the testing template file location $template->template_file = 'template-test.inc';
|
12 |
|
13 |
$template->entries[] = (object) array( 'test' => 'This was inserted using template tags!' ); |
14 |
|
15 |
$extra = (object) array( 'header' => (object) array( 'header_stuff' => 'Some extra content.' ), 'footer' => (object) array( 'footerStuff' => 'More extra content.' ) ); |
16 |
|
17 |
// Output the template markup echo $template->generate_markup($extra);
|
18 |
|
19 |
/** * Loads entries from the Envato API for a given site * ... */ function load_envato_blog_posts( $site='themeforest' ) {...} |
HINWEIS: Vergessen Sie nicht, $ extra an generate_markup () zu übergeben! Speichern Sie diese Änderungen, laden Sie die Datei in Ihrem Browser neu, und Sie sehen Folgendes:
1 |
Template Header Some extra content. |
2 |
|
3 |
This is content that should be displayed. This was inserted using template tags! |
4 |
|
5 |
Template footer. More extra content. |
Schritt 9 Verwenden Sie echte Einträge
Als letzte Übung verwenden wir einige echte Einträge aus dem Envato Marketplace und entwerfen eine Vorlage, um sie anzuzeigen.
Erstellen Sie eine Vorlage
Erstellen Sie für eine Vorlage ein neues Element im Vorlagenordner mit dem Namen "entry-list.inc". Fügen Sie den folgenden Code hinzu:
1 |
/** * This template has the following tags available: * title Article title * url Permalink of the article * site Site on which the article was originally published * posted_at Original posting date */
|
2 |
|
3 |
<h3>Entry Short List</h3> |
4 |
|
5 |
<ol id="entry-list"> |
6 |
|
7 |
// This is the main entry loop {loop} <li><a href="{url}">{title}</a> (published on {site})</li> {/loop}
|
8 |
|
9 |
</ol><!-- end #entry-list --> |
Alles, was wir tun müssen, um echte Einträge aus der Envato-API zu laden, ist die Funktion, die wir bereits in diesem Tutorial geschrieben haben.
Alles, was wir tun müssen, um echte Einträge aus der Envato-API zu laden, ist die Funktion, die wir bereits in diesem Tutorial geschrieben haben. Im
index.php, ändere den Code, um die neue Template-Datei zu verwenden und die neuesten Einträge von audiojungle zu laden:
1 |
<?php
|
2 |
|
3 |
// Error reporting is turned up to 11 for the purposes of this demo ini_set("display_errors",1); ERROR_REPORTING(E_ALL);
|
4 |
|
5 |
// Exception handling set_exception_handler('exception_handler'); function exception_handler( $exception ) { echo $exception->getMessage(); }
|
6 |
|
7 |
?> <!DOCTYPE html> <html lang="en"> |
8 |
|
9 |
<head>
|
10 |
|
11 |
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> |
12 |
|
13 |
<!-- Meta information --> <title>Demo: Roll Your Own Templating System in PHP</title> <meta name="description" content="A demo of the templating system by Jason Lengstorf" /> </head> |
14 |
|
15 |
<body> <?php |
16 |
|
17 |
// Load the Template class require_once 'system/class.template.inc.php';
|
18 |
|
19 |
// Create a new instance of the Template class $template = new Template;
|
20 |
|
21 |
// Set the testing template file location $template->template_file = 'entry-list.inc';
|
22 |
|
23 |
// Load sample entries from Envato $template->entries = load_envato_blog_posts('audiojungle');
|
24 |
|
25 |
// Output the template markup echo $template->generate_markup();
|
26 |
|
27 |
?> </body> </html> <?php |
28 |
|
29 |
/** * Loads entries from the Envato API for a given site * ... */ function load_envato_blog_posts( $site='themeforest' ) {...} |
HINWEIS: Ich habe eine Doctype-Deklaration und einfache HTML-Tags hinzugefügt, um Zeichencodierungsprobleme zu vermeiden.
Zusammenfassung
An dieser Stelle haben Sie erfolgreich objektorientiertes PHP, reguläre Ausdrücke und das Curren von Funktionen zu einem einfach zu verwendenden Template-System kombiniert. Die Techniken in diesem Tutorial können zu Ihrem Entwicklungsarsenal hinzugefügt werden, um sie in zukünftigen Projekten zu verwenden, und hoffentlich fühlen Sie sich sofort wie ein besserer Entwickler. Hast du eine Abkürzung entdeckt, die ich vermisst habe? Kannst du dir einen Weg vorstellen, das Templating-System zu verbessern? Wie fühlen Sie sich bei der Verwendung dieses Systems in einem Projekt, um Markup getrennt von Geschäftslogik zu halten? Lass mich deine Gedanken in den Kommentaren wissen!



