German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)
Mit der Grundstruktur für unser Framework sind wir fertig, Funktionen hinzuzufügen. In diesem Tutorial erstellen wir einen Templatemanager und einen Datenbankhandler, die uns einem leistungsstarken Framework einen Schritt näher bringen, das für fast jedes Projekt geeignet ist. Wenn Sie es noch nicht getan haben, lesen Sie zuerst Teil 1 dieser Serie!
MVC: Passen Sie die Struktur an
Im ersten Teil dieses Tutorials haben wir einen Ordner namens controllers erstellt, in dem die Geschäftslogik für unsere Anwendungen gespeichert wird. Wie daok in einem Kommentar betonte, ist dies nicht der beste Ort für die gesamte Geschäftslogik, und es sollte ein Modell zum Speichern dieser Logik verwendet werden. Bisher habe ich in den meisten meiner Anwendungen immer die Datenbank selbst als Modell verwendet. Wenn Sie dies jedoch etwas weiter trennen, wird unser Framework noch leistungsfähiger und einfacher zu erweitern.
Was ist MVC? MVC ist ein Entwurfsmuster (wie auch die Singleton- und Registrierungsmuster, die wir in Teil 1 betrachtet haben) und steht für Model View Controller. Das Ziel dieses Musters ist die Geschäftslogik, Benutzeroberflächenaktionen und die Benutzeroberfläche von zu trennen einander. Obwohl wir noch nichts mit unseren Modellen und Controllern tun werden, aktualisieren wir unsere Frameworks-Ordnerstruktur, um den Ordner "models" einzuschließen. Das Modell enthält die Hauptgeschäftslogik, und der Controller befasst sich mit der Benutzerinteraktion (z.B. Übermitteln von Daten, z.B. eines Kommentars). NB: Unsere __autoload-Funktion muss nicht geändert werden.



Datenbankhandler
Die meisten Websites und Webanwendungen, die PHP verwenden, verwenden auch eine Datenbank-Engine wie MySQL. Wenn wir alle usere datenbankbezogenen Funktionen am selben Ort behalten, können wir (theoretisch) das von uns verwendete Datenbankmodul leicht ändern. Wir können auch bestimmte Vorgänge vereinfachen, z.B. das Einfügen von Datensätzen, das Aktualisieren von Datensätzen oder das Löschen von Datensätzen aus der Datenbank. Dies kann auch den Umgang mit mehreren Datenbankverbindungen erleichtern.
Also... was soll unser Datenbank-Handler tun:
- Verwalten Sie Verbindungen zur Datenbank
- Versuchen Sie, eine gewisse Abstraktionsebene von der Datenbank bereitzustellen
- Cache-Abfragen, damit wir sie später verwenden können
- Erleichtern Sie allgemeine Datenbankoperationen
Schauen wir uns den Code für unseren Datenbank-Handler an und diskutieren ihn anschließend.
1 |
|
2 |
<?php
|
3 |
|
4 |
/**
|
5 |
* Database management and access class
|
6 |
* This is a very basic level of abstraction
|
7 |
*/
|
8 |
class database { |
9 |
|
10 |
/**
|
11 |
* Allows multiple database connections
|
12 |
* probably not used very often by many applications, but still useful
|
13 |
*/
|
14 |
private $connections = array(); |
15 |
|
16 |
/**
|
17 |
* Tells the DB object which connection to use
|
18 |
* setActiveConnection($id) allows us to change this
|
19 |
*/
|
20 |
private $activeConnection = 0; |
21 |
|
22 |
/**
|
23 |
* Queries which have been executed and then "saved for later"
|
24 |
*/
|
25 |
private $queryCache = array(); |
26 |
|
27 |
/**
|
28 |
* Data which has been prepared and then "saved for later"
|
29 |
*/
|
30 |
private $dataCache = array(); |
31 |
|
32 |
/**
|
33 |
* Record of the last query
|
34 |
*/
|
35 |
private $last; |
36 |
|
37 |
|
38 |
/**
|
39 |
* Hello
|
40 |
*/
|
41 |
public function __construct() |
42 |
{
|
43 |
|
44 |
}
|
45 |
|
46 |
/**
|
47 |
* Create a new database connection
|
48 |
* @param String database hostname
|
49 |
* @param String database username
|
50 |
* @param String database password
|
51 |
* @param String database we are using
|
52 |
* @return int the id of the new connection
|
53 |
*/
|
54 |
public function newConnection( $host, $user, $password, $database ) |
55 |
{
|
56 |
$this->connections[] = new mysqli( $host, $user, $password, $database ); |
57 |
$connection_id = count( $this->connections )-1; |
58 |
if( mysqli_connect_errno() ) |
59 |
{
|
60 |
trigger_error('Error connecting to host. '.$this->connections[$connection_id]->error, E_USER_ERROR); |
61 |
}
|
62 |
|
63 |
return $connection_id; |
64 |
}
|
65 |
|
66 |
/**
|
67 |
* Close the active connection
|
68 |
* @return void
|
69 |
*/
|
70 |
public function closeConnection() |
71 |
{
|
72 |
$this->connections[$this->activeConnection]->close(); |
73 |
}
|
74 |
|
75 |
/**
|
76 |
* Change which database connection is actively used for the next operation
|
77 |
* @param int the new connection id
|
78 |
* @return void
|
79 |
*/
|
80 |
public function setActiveConnection( int $new ) |
81 |
{
|
82 |
$this->activeConnection = $new; |
83 |
}
|
84 |
|
85 |
/**
|
86 |
* Store a query in the query cache for processing later
|
87 |
* @param String the query string
|
88 |
* @return the pointed to the query in the cache
|
89 |
*/
|
90 |
public function cacheQuery( $queryStr ) |
91 |
{
|
92 |
if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) ) |
93 |
{
|
94 |
trigger_error('Error executing and caching query: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR); |
95 |
return -1; |
96 |
}
|
97 |
else
|
98 |
{
|
99 |
$this->queryCache[] = $result; |
100 |
return count($this->queryCache)-1; |
101 |
}
|
102 |
}
|
103 |
|
104 |
/**
|
105 |
* Get the number of rows from the cache
|
106 |
* @param int the query cache pointer
|
107 |
* @return int the number of rows
|
108 |
*/
|
109 |
public function numRowsFromCache( $cache_id ) |
110 |
{
|
111 |
return $this->queryCache[$cache_id]->num_rows; |
112 |
}
|
113 |
|
114 |
/**
|
115 |
* Get the rows from a cached query
|
116 |
* @param int the query cache pointer
|
117 |
* @return array the row
|
118 |
*/
|
119 |
public function resultsFromCache( $cache_id ) |
120 |
{
|
121 |
return $this->queryCache[$cache_id]->fetch_array(MYSQLI_ASSOC); |
122 |
}
|
123 |
|
124 |
/**
|
125 |
* Store some data in a cache for later
|
126 |
* @param array the data
|
127 |
* @return int the pointed to the array in the data cache
|
128 |
*/
|
129 |
public function cacheData( $data ) |
130 |
{
|
131 |
$this->dataCache[] = $data; |
132 |
return count( $this->dataCache )-1; |
133 |
}
|
134 |
|
135 |
/**
|
136 |
* Get data from the data cache
|
137 |
* @param int data cache pointed
|
138 |
* @return array the data
|
139 |
*/
|
140 |
public function dataFromCache( $cache_id ) |
141 |
{
|
142 |
return $this->dataCache[$cache_id]; |
143 |
}
|
144 |
|
145 |
/**
|
146 |
* Delete records from the database
|
147 |
* @param String the table to remove rows from
|
148 |
* @param String the condition for which rows are to be removed
|
149 |
* @param int the number of rows to be removed
|
150 |
* @return void
|
151 |
*/
|
152 |
public function deleteRecords( $table, $condition, $limit ) |
153 |
{
|
154 |
$limit = ( $limit == '' ) ? '' : ' LIMIT ' . $limit; |
155 |
$delete = "DELETE FROM {$table} WHERE {$condition} {$limit}"; |
156 |
$this->executeQuery( $delete ); |
157 |
}
|
158 |
|
159 |
/**
|
160 |
* Update records in the database
|
161 |
* @param String the table
|
162 |
* @param array of changes field => value
|
163 |
* @param String the condition
|
164 |
* @return bool
|
165 |
*/
|
166 |
public function updateRecords( $table, $changes, $condition ) |
167 |
{
|
168 |
$update = "UPDATE " . $table . " SET "; |
169 |
foreach( $changes as $field => $value ) |
170 |
{
|
171 |
$update .= "`" . $field . "`='{$value}',"; |
172 |
}
|
173 |
|
174 |
// remove our trailing ,
|
175 |
$update = substr($update, 0, -1); |
176 |
if( $condition != '' ) |
177 |
{
|
178 |
$update .= "WHERE " . $condition; |
179 |
}
|
180 |
|
181 |
$this->executeQuery( $update ); |
182 |
|
183 |
return true; |
184 |
|
185 |
}
|
186 |
|
187 |
/**
|
188 |
* Insert records into the database
|
189 |
* @param String the database table
|
190 |
* @param array data to insert field => value
|
191 |
* @return bool
|
192 |
*/
|
193 |
public function insertRecords( $table, $data ) |
194 |
{
|
195 |
// setup some variables for fields and values
|
196 |
$fields = ""; |
197 |
$values = ""; |
198 |
|
199 |
// populate them
|
200 |
foreach ($data as $f => $v) |
201 |
{
|
202 |
|
203 |
$fields .= "`$f`,"; |
204 |
$values .= ( is_numeric( $v ) && ( intval( $v ) == $v ) ) ? $v."," : "'$v',"; |
205 |
|
206 |
}
|
207 |
|
208 |
// remove our trailing ,
|
209 |
$fields = substr($fields, 0, -1); |
210 |
// remove our trailing ,
|
211 |
$values = substr($values, 0, -1); |
212 |
|
213 |
$insert = "INSERT INTO $table ({$fields}) VALUES({$values})"; |
214 |
$this->executeQuery( $insert ); |
215 |
return true; |
216 |
}
|
217 |
|
218 |
/**
|
219 |
* Execute a query string
|
220 |
* @param String the query
|
221 |
* @return void
|
222 |
*/
|
223 |
public function executeQuery( $queryStr ) |
224 |
{
|
225 |
if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) ) |
226 |
{
|
227 |
trigger_error('Error executing query: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR); |
228 |
}
|
229 |
else
|
230 |
{
|
231 |
$this->last = $result; |
232 |
}
|
233 |
|
234 |
}
|
235 |
|
236 |
/**
|
237 |
* Get the rows from the most recently executed query, excluding cached queries
|
238 |
* @return array
|
239 |
*/
|
240 |
public function getRows() |
241 |
{
|
242 |
return $this->last->fetch_array(MYSQLI_ASSOC); |
243 |
}
|
244 |
|
245 |
/**
|
246 |
* Gets the number of affected rows from the previous query
|
247 |
* @return int the number of affected rows
|
248 |
*/
|
249 |
public function affectedRows() |
250 |
{
|
251 |
return $this->$this->connections[$this->activeConnection]->affected_rows; |
252 |
}
|
253 |
|
254 |
/**
|
255 |
* Sanitize data
|
256 |
* @param String the data to be sanitized
|
257 |
* @return String the sanitized data
|
258 |
*/
|
259 |
public function sanitizeData( $data ) |
260 |
{
|
261 |
return $this->connections[$this->activeConnection]->real_escape_string( $data ); |
262 |
}
|
263 |
|
264 |
/**
|
265 |
* Deconstruct the object
|
266 |
* close all of the database connections
|
267 |
*/
|
268 |
public function __deconstruct() |
269 |
{
|
270 |
foreach( $this->connections as $connection ) |
271 |
{
|
272 |
$connection->close(); |
273 |
}
|
274 |
}
|
275 |
}
|
276 |
?>
|
Bevor ich den Code bespreche, möchte ich darauf hinweisen, dass dieser Datenbankhandler sehr einfach ist. Wir könnten eine vollständige Abstraktion bereitstellen, indem wir Abfragen nicht direkt ausführen, sondern Abfragen basierend auf Parametern für eine Abfragefunktion erstellen und dann ausführen.
Unsere Methoden zum Löschen, Einfügen und Aktualisieren von Datensätzen erleichtern die Ausführung einiger gängiger Aufgaben (wie oben erwähnt, können wir diese erweitern, um noch viel mehr zu tun), indem nur Informationen wie der Tabellenname, ein Array von Feldern und entsprechende Werte bereitgestellt werden. Grenzwerte und Bedingungen. Abfragen können auch "zwischengespeichert" werden, damit wir sie später bearbeiten können. Ich finde, dass diese Funktion (sowie die Möglichkeit, Datenfelder zwischenzuspeichern) in Kombination mit einem Vorlagenmanager sehr praktisch ist, da wir Datenzeilen problemlos durchlaufen und sie mit wenig Aufwand in unsere Vorlagen einfügen können, wie Sie möchten Sehen Sie, wenn wir uns den Vorlagen-Manager ansehen.
1 |
|
2 |
// insert record
|
3 |
$registry->getObject('db')->insertRecords( 'testTable', array('name'=>'Michael' ) ); |
4 |
// update a record
|
5 |
$registry->getObject('db')->updateRecords( 'testTable', array('name'=>'MichaelP' ), 'ID=2' ); |
6 |
// delete a record (well, upto 5 in this case)
|
7 |
$registry->getObject('db')->deleteRecords( 'testTable', "name='MichaelP'", 5 ); |
Wir können auch relativ einfach mit mehreren Datenbankverbindungen arbeiten, solange wir bei Bedarf zwischen den entsprechenden Verbindungen wechseln (obwohl dies beim Zwischenspeichern von Abfragen und Abrufen über unseren Vorlagenmanager ohne weitere Arbeit nicht funktioniert), z.B. mit dem folgenden Code-Snippet können wir Datensätze aus zwei Datenbanken löschen.
1 |
|
2 |
// our second database connection (let's assume we already have a connection to our main DB)
|
3 |
$newConnection = $registry->getObject('db')->newConnection('localhost', 'root', 'password', 'secondDB'); |
4 |
// delete from the primary db connection
|
5 |
$registry->getObject('db')->deleteRecords( 'testTable', "name='MichaelP'", 5 ); |
6 |
// change our active db connection, to allow future queries to be on the second connection
|
7 |
$registry->getObject('db')->setActiveConnection( $newConnection ); |
8 |
// delete from the secondary db connection
|
9 |
$registry->getObject('db')->deleteRecords( 'testTable', "name='MichaelP'", 5 ); |
10 |
// revert the active connection so future queries are on the primary db connection
|
11 |
$registry->getObject('db')->setActiveConnection( 0 ); |
Wie möchten wir diese Klasse erweitern?
- Volle Abstraktion
- Nutzen Sie die Vererbung, erstellen Sie eine Schnittstelle und lassen Sie Datenbankklassen von dieser erben, jeweis für verschiedene Datenbankmodule
- Speichern Sie die Verbindungs-IDs zusammen mit der Abfrage, wenn Sie Abfragen zwischenspeichern
- Verbessern Sie die Datenbereinigung, abhängig von der Art der Daten, die wir bereinigen möchten
Vorlagen-Manager
Der Vorlagenmanager verarbeitet die gesamte Ausgabe. Er muss in der Lage sein, mit verschiedenen Vorlagendateien zu arbeiten, Platzhalter (ich nenne sie Tags) durch Daten zu ersetzen und Teile der Vorlage mit mehreren Datenzeilen aus der Datenbank zu durchlaufen.
Zur Vereinfachung verwenden wir eine Seitenklasse, um den Inhalt der Seite zu enthalten. Das erleichtert es uns auch, diese zu erweitern und später Funktionen hinzuzufügen. Der Vorlagenmanager verwaltet dieses Objekt.
1 |
|
2 |
<?php
|
3 |
|
4 |
// prevent this file being called directly
|
5 |
if ( ! defined( 'PCAFW' ) ) |
6 |
{
|
7 |
echo 'This file can only be called via the main index.php file, and not directly'; |
8 |
exit(); |
9 |
}
|
10 |
|
11 |
/**
|
12 |
* Template manager class
|
13 |
*/
|
14 |
class template { |
15 |
|
16 |
private $page; |
17 |
|
18 |
/**
|
19 |
* Hello!
|
20 |
*/
|
21 |
public function __construct() |
22 |
{
|
23 |
include( APP_PATH . '/PCARegistry/objects/page.class.php'); |
24 |
$this->page = new Page(); |
25 |
|
26 |
}
|
27 |
|
28 |
/**
|
29 |
* Add a template bit onto our page
|
30 |
* @param String $tag the tag where we insert the template e.g. {hello}
|
31 |
* @param String $bit the template bit (path to file, or just the filename)
|
32 |
* @return void
|
33 |
*/
|
34 |
public function addTemplateBit( $tag, $bit ) |
35 |
{
|
36 |
if( strpos( $bit, 'skins/' ) === false ) |
37 |
{
|
38 |
$bit = 'skins/' . PCARegistry::getSetting('skin') . '/templates/' . $bit; |
39 |
}
|
40 |
$this->page->addTemplateBit( $tag, $bit ); |
41 |
}
|
42 |
|
43 |
/**
|
44 |
* Put the template bits into our page content
|
45 |
* Updates the pages content
|
46 |
* @return void
|
47 |
*/
|
48 |
private function replaceBits() |
49 |
{
|
50 |
$bits = $this->page->getBits(); |
51 |
foreach( $bits as $tag => $template ) |
52 |
{
|
53 |
$templateContent = file_get_contents( $bit ); |
54 |
$newContent = str_replace( '{' . $tag . '}', $templateContent, $this->page->getContent() ); |
55 |
$this->page->setContent( $newContent ); |
56 |
}
|
57 |
}
|
58 |
|
59 |
/**
|
60 |
* Replace tags in our page with content
|
61 |
* @return void
|
62 |
*/
|
63 |
private function replaceTags() |
64 |
{
|
65 |
// get the tags
|
66 |
$tags = $this->page->getTags(); |
67 |
// go through them all
|
68 |
foreach( $tags as $tag => $data ) |
69 |
{
|
70 |
if( is_array( $data ) ) |
71 |
{
|
72 |
|
73 |
if( $data[0] == 'SQL' ) |
74 |
{
|
75 |
// it is a cached query...replace DB tags
|
76 |
$this->replaceDBTags( $tag, $data[1] ); |
77 |
}
|
78 |
elseif( $data[0] == 'DATA' ) |
79 |
{
|
80 |
// it is some cached data...replace data tags
|
81 |
$this->replaceDataTags( $tag, $data[1] ); |
82 |
}
|
83 |
}
|
84 |
else
|
85 |
{
|
86 |
// replace the content
|
87 |
$newContent = str_replace( '{' . $tag . '}', $data, $this->page->getContent() ); |
88 |
// update the pages content
|
89 |
$this->page->setContent( $newContent ); |
90 |
}
|
91 |
}
|
92 |
}
|
93 |
|
94 |
/**
|
95 |
* Replace content on the page with data from the DB
|
96 |
* @param String $tag the tag defining the area of content
|
97 |
* @param int $cacheId the queries ID in the query cache
|
98 |
* @return void
|
99 |
*/
|
100 |
private function replaceDBTags( $tag, $cacheId ) |
101 |
{
|
102 |
$block = ''; |
103 |
$blockOld = $this->page->getBlock( $tag ); |
104 |
|
105 |
// foreach record relating to the query...
|
106 |
while ($tags = PCARegistry::getObject('db')->resultsFromCache( $cacheId ) ) |
107 |
{
|
108 |
$blockNew = $blockOld; |
109 |
// create a new block of content with the results replaced into it
|
110 |
foreach ($tags as $ntag => $data) |
111 |
{
|
112 |
$blockNew = str_replace("{" . $ntag . "}", $data, $blockNew); |
113 |
}
|
114 |
$block .= $blockNew; |
115 |
}
|
116 |
$pageContent = $this->page->getContent(); |
117 |
// remove the seperator in the template, cleaner HTML
|
118 |
$newContent = str_replace( '<!-- START ' . $tag . ' -->' . $blockOld . '<!-- END ' . $tag . ' -->', $block, $pageContent ); |
119 |
// update the page content
|
120 |
$this->page->setContent( $newContent ); |
121 |
}
|
122 |
|
123 |
/**
|
124 |
* Replace content on the page with data from the cache
|
125 |
* @param String $tag the tag defining the area of content
|
126 |
* @param int $cacheId the datas ID in the data cache
|
127 |
* @return void
|
128 |
*/
|
129 |
private function replaceDataTags( $tag, $cacheId ) |
130 |
{
|
131 |
$block = $this->page->getBlock( $tag ); |
132 |
$blockOld = $block; |
133 |
while ($tags = PCARegistry::getObject('db')->dataFromCache( $cacheId ) ) |
134 |
{
|
135 |
foreach ($tags as $tag => $data) |
136 |
{
|
137 |
$blockNew = $blockOld; |
138 |
$blockNew = str_replace("{" . $tag . "}", $data, $blockNew); |
139 |
}
|
140 |
$block .= $blockNew; |
141 |
}
|
142 |
$pageContent = $this->page->getContent(); |
143 |
$newContent = str_replace( $blockOld, $block, $pageContent ); |
144 |
$this->page->setContent( $newContent ); |
145 |
}
|
146 |
|
147 |
/**
|
148 |
* Get the page object
|
149 |
* @return Object
|
150 |
*/
|
151 |
public function getPage() |
152 |
{
|
153 |
return $this->page; |
154 |
}
|
155 |
|
156 |
/**
|
157 |
* Set the content of the page based on a number of templates
|
158 |
* pass template file locations as individual arguments
|
159 |
* @return void
|
160 |
*/
|
161 |
public function buildFromTemplates() |
162 |
{
|
163 |
$bits = func_get_args(); |
164 |
$content = ""; |
165 |
foreach( $bits as $bit ) |
166 |
{
|
167 |
|
168 |
if( strpos( $bit, 'skins/' ) === false ) |
169 |
{
|
170 |
$bit = 'skins/' . PCARegistry::getSetting('skin') . '/templates/' . $bit; |
171 |
}
|
172 |
if( file_exists( $bit ) == true ) |
173 |
{
|
174 |
$content .= file_get_contents( $bit ); |
175 |
}
|
176 |
|
177 |
}
|
178 |
$this->page->setContent( $content ); |
179 |
}
|
180 |
|
181 |
/**
|
182 |
* Convert an array of data (i.e. a db row?) to some tags
|
183 |
* @param array the data
|
184 |
* @param string a prefix which is added to field name to create the tag name
|
185 |
* @return void
|
186 |
*/
|
187 |
public function dataToTags( $data, $prefix ) |
188 |
{
|
189 |
foreach( $data as $key => $content ) |
190 |
{
|
191 |
$this->page->addTag( $key.$prefix, $content); |
192 |
}
|
193 |
}
|
194 |
|
195 |
public function parseTitle() |
196 |
{
|
197 |
$newContent = str_replace('<title>', '<title>'. $page->getTitle(), $this->page->getContent() ); |
198 |
$this->page->setContent( $newContent ); |
199 |
}
|
200 |
|
201 |
/**
|
202 |
* Parse the page object into some output
|
203 |
* @return void
|
204 |
*/
|
205 |
public function parseOutput() |
206 |
{
|
207 |
$this->replaceBits(); |
208 |
$this->replaceTags(); |
209 |
$this->parseTitle(); |
210 |
}
|
211 |
|
212 |
|
213 |
|
214 |
}
|
215 |
?>
|
Was genau macht diese Klasse?
Erstellt unser Seitenobjekt und basiert es auf Vorlagendateien. Das Seitenobjekt hat den Inhalt und die Informationen, die zum Erstellen des HTML-Codes der Seite erforderlich sind. Anschließend erstellen wir buildFromTemplate ('templatefile.tpl.php', 'templatefile2.tpl.php'), um den ursprünglichen Inhalt für unsere Seite zu erhalten. Diese Methode verwendet eine beliebige Anzahl von Vorlagendateien als Argumente und setzt sie in der für sie nützlichen Reihenfolge zusammen Vorlagen für Kopf-, Inhalts- und Fußzeilen.
Managt den mit der Seite verknüpften Inhalt, indem das Seitenobjekt einen Datensatz mit Daten verwaltet, die auf der Seite ersetzt werden sollen, sowie zusätzliche Vorlagenbits, die in die Seite integriert werden müssen (addTemplateBit('userbar', 'usertoolsbar.tpl.php)')).
Fügt der Seite Daten und Inhalte hinzu, indem verschiedene Ersetzungsvorgänge für den Seiteninhalt ausgeführt werden, einschließlich des Abrufs von Ergebnissen aus einer zwischengespeicherten Abfrage und des Hinzufügens zu der Seite.
Die Vorlagendatei muss in sich selbst markieren, wo eine zwischengespeicherte Abfrage abgerufen und die Daten aus der Abfrage ersetzt werden müssen. Wenn der Vorlagenmanager auf ein zu ersetzendes Tag stößt, bei dem es sich um eine Abfrage handelt, erhält er den Teil der Seite, durch den er iterieren muss, indem er getBlock('block') für das Seitenobjekt aufruft. Dieser Inhaltsblock wird dann für jeden Datensatz in der Abfrage kopiert und enthält Tags, die durch die Ergebnisse der Abfrage ersetzt werden. Wir werden uns später in diesem Tutorial ansehen, wie dies in der Vorlage aussieht.
Vorlagen-Manager: Seite
Das Seitenobjekt wird vom Vorlagenmanager verwaltet und enthält alle Details zur Seite. Dadurch kann der Vorlagenmanager frei verwaltet werden, und wir können die Funktionalität zu einem späteren Zeitpunkt leichter erweitern.
1 |
|
2 |
<?php
|
3 |
|
4 |
/**
|
5 |
* This is our page object
|
6 |
* It is a seperate object to allow some interesting extra functionality to be added
|
7 |
* Some ideas: passwording pages, adding page specific css/js files, etc
|
8 |
*/
|
9 |
class page { |
10 |
|
11 |
// room to grow later?
|
12 |
private $css = array(); |
13 |
private $js = array(); |
14 |
private $bodyTag = ''; |
15 |
private $bodyTagInsert = ''; |
16 |
|
17 |
// future functionality?
|
18 |
private $authorised = true; |
19 |
private $password = ''; |
20 |
|
21 |
// page elements
|
22 |
private $title = ''; |
23 |
private $tags = array(); |
24 |
private $postParseTags = array(); |
25 |
private $bits = array(); |
26 |
private $content = ""; |
27 |
|
28 |
/**
|
29 |
* Constructor...
|
30 |
*/
|
31 |
function __construct() { } |
32 |
|
33 |
public function getTitle() |
34 |
{
|
35 |
return $this->title; |
36 |
}
|
37 |
|
38 |
public function setPassword( $password ) |
39 |
{
|
40 |
$this->password = $password; |
41 |
}
|
42 |
|
43 |
public function setTitle( $title ) |
44 |
{
|
45 |
$this->title = $title; |
46 |
}
|
47 |
|
48 |
public function setContent( $content ) |
49 |
{
|
50 |
$this->content = $content; |
51 |
}
|
52 |
|
53 |
public function addTag( $key, $data ) |
54 |
{
|
55 |
$this->tags[$key] = $data; |
56 |
}
|
57 |
|
58 |
public function getTags() |
59 |
{
|
60 |
return $this->tags; |
61 |
}
|
62 |
|
63 |
public function addPPTag( $key, $data ) |
64 |
{
|
65 |
$this->postParseTags[$key] = $data; |
66 |
}
|
67 |
|
68 |
/**
|
69 |
* Get tags to be parsed after the first batch have been parsed
|
70 |
* @return array
|
71 |
*/
|
72 |
public function getPPTags() |
73 |
{
|
74 |
return $this->postParseTags; |
75 |
}
|
76 |
|
77 |
/**
|
78 |
* Add a template bit to the page, doesnt actually add the content just yet
|
79 |
* @param String the tag where the template is added
|
80 |
* @param String the template file name
|
81 |
* @return void
|
82 |
*/
|
83 |
public function addTemplateBit( $tag, $bit ) |
84 |
{
|
85 |
$this->bits[ $tag ] = $bit; |
86 |
}
|
87 |
|
88 |
/**
|
89 |
* Get the template bits to be entered into the page
|
90 |
* @return array the array of template tags and template file names
|
91 |
*/
|
92 |
public function getBits() |
93 |
{
|
94 |
return $this->bits; |
95 |
}
|
96 |
|
97 |
/**
|
98 |
* Gets a chunk of page content
|
99 |
* @param String the tag wrapping the block ( <!-- START tag --> block <!-- END tag --> )
|
100 |
* @return String the block of content
|
101 |
*/
|
102 |
public function getBlock( $tag ) |
103 |
{
|
104 |
preg_match ('#<!-- START '. $tag . ' -->(.+?)<!-- END '. $tag . ' -->#si', $this->content, $tor); |
105 |
|
106 |
$tor = str_replace ('<!-- START '. $tag . ' -->', "", $tor[0]); |
107 |
$tor = str_replace ('<!-- END ' . $tag . ' -->', "", $tor); |
108 |
|
109 |
return $tor; |
110 |
}
|
111 |
|
112 |
public function getContent() |
113 |
{
|
114 |
return $this->content; |
115 |
}
|
116 |
|
117 |
}
|
118 |
?>
|
Wie kann diese Klasse erweitert und verbessert werden?
- PostParseTags: Möglicherweise möchten Sie Tags ersetzen lassen, nachdem der größte Teil der Seite analysiert wurde. Möglicherweise enthält der Inhalt der Datenbank Tags, die analysiert werden müssen.
- Kennwortseiten: Weisen Sie einer Seite ein Kennwort zu. Überprüfen Sie, ob der Benutzer das Kennwort in einem Cookie oder einer Sitzung hat, damit er die Seite sehen kann.
- Eingeschränkte Seiten (obwohl wir zuerst unsere Authentifizierungskomponenten benötigen!)
- Änderung
- Dynamisches Hinzufügen von Verweisen auf Javascript- und CSS-Dateien basierend auf der Seite oder Anwendung.
Kernobjekte laden
Nachdem wir nun einige Objekte haben, die unsere Registrierung für uns speichern wird, müssen wir der Registrierung mitteilen, um welche Objekte es sich handelt. Ich habe im PCARegistry-Objekt eine Methode namens loadCoreObjects erstellt, die (wie es heißt) die Kernobjekte lädt. Dies bedeutet, dass Sie dies einfach aus unserer index.php-Datei aufrufen können, um die Registrierung mit diesen Objekten zu laden.
1 |
|
2 |
public function storeCoreObjects() |
3 |
{
|
4 |
$this->storeObject('database', 'db' ); |
5 |
$this->storeObject('template', 'template' ); |
6 |
}
|
Diese Methode kann später geändert werden, um die anderen Kernobjekte einzubeziehen, die von der Registrierung geladen werden sollen. Natürlich kann es Objekte geben, die von unserer Registrierung verwaltet werden sollen, jedoch nur abhängig von der Anwendung, für die das Framework verwendet wird. Diese Objekte würden außerhalb dieser Methode geladen.
Daten
Damit wir die neuen Funktionen demonstrieren können, die unserem Framework hinzugefügt wurden, benötigen wir eine Datenbank, um den Datenbankhandler zu verwenden, und einige der Vorlagenverwaltungsfunktionen (bei denen wir einen Inhaltsblock durch die Zeilen in der Datenbank ersetzen).
Die Demonstrationssite, die wir am Ende dieser Reihe von Tutorials mit unserem Framework erstellen werden, ist eine Website mit einem Mitgliederverzeichnis. Erstellen wir daher eine sehr einfache Datenbanktabelle für Mitgliederprofile, die eine ID, einen Namen und eine E-Mail-Adresse enthält.



Natürlich brauchen wir ein paar Datenzeilen in dieser Tabelle!
Eine schnelle Vorlage
Damit etwas angezeigt werden kann, benötigen wir eine Basisvorlage, in der wir die Daten aus unserer Mitgliedertabelle auflisten.
1 |
|
2 |
<html>
|
3 |
<head>
|
4 |
<title> Powered by PCA Framework</title> |
5 |
</head>
|
6 |
<body>
|
7 |
<h1>Our Members</h1> |
8 |
<p>Below is a list of our members:</p> |
9 |
<ul>
|
10 |
<!-- START members -->
|
11 |
<li>{name} {email}</li> |
12 |
<!-- END members -->
|
13 |
</ul>
|
14 |
</body>
|
15 |
</html>
|
Die HTML-Kommentare der START-Mitglieder und END-Mitglieder bezeichnen den Mitgliederblock (der über die Methode getBlock() auf der Seite abgerufen wird). Hier durchläuft der Vorlagenmanager die Datensätze in der Datenbank und zeigt sie an.
Framework im Einsatz
Jetzt müssen wir dies alles mit unserer index.php-Datei zusammenführen:
1 |
|
2 |
// require our registry
|
3 |
require_once('PCARegistry/pcaregistry.class.php'); |
4 |
$registry = PCARegistry::singleton(); |
5 |
|
6 |
// store those core objects
|
7 |
$registry->storeCoreObjects(); |
8 |
|
9 |
// create a database connection
|
10 |
$registry->getObject('db')->newConnection('localhost', 'root', '', 'pcaframework'); |
11 |
|
12 |
// set the default skin setting (we will store these in the database later...)
|
13 |
$registry->storeSetting('default', 'skin'); |
14 |
|
15 |
// populate our page object from a template file
|
16 |
$registry->getObject('template')->buildFromTemplates('main.tpl.php'); |
17 |
|
18 |
// cache a query of our members table
|
19 |
$cache = $registry->getObject('db')->cacheQuery('SELECT * FROM members'); |
20 |
|
21 |
// assign this to the members tag
|
22 |
$registry->getObject('template')->getPage()->addTag('members', array('SQL', $cache) ); |
23 |
|
24 |
// set the page title
|
25 |
$registry->getObject('template')->getPage()->setTitle('Our members'); |
26 |
|
27 |
// parse it all, and spit it out
|
28 |
$registry->getObject('template')->parseOutput(); |
29 |
print $registry->getObject('template')->getPage()->getContent(); |
Wenn wir diese Seite jetzt in unserem Webbrowser anzeigen, werden die Ergebnisse der Abfrage auf der Seite angezeigt:

Kommen in Teil 3...
In Teil 3 werden wir einen kleinen Umweg von der Entwicklungsseite unseres Frameworks machen und uns ansehen, wie wir mit Blick auf unser Framework entwerfen und wie wir HTML-Vorlagen so aufteilen, dass sie für unser Framework geeignet sind. Wenn wir beginnen, unsere erste Anwendung mit unserem Framework zu erstellen, werden wir uns einige Funktionen dieser Klassen genauer ansehen. Zum Schluss vielen Dank für Ihre Kommentare beim letzten Mal!
- Abonnieren Sie den NETTUTS RSS Feed für weitere tägliche Webentwicklungs-Tuts und Artikel.