Práci s EPF si ukažme na příkladu jednoduché stránky s komentáři. Na stránce bude nejprve výpis všech existujících komentářů a na konci stránky pak formulář pro přidání nového.
Datový model bude velice jednoduchý. Pro každý příspěvek budeme uchovávat tyto informace:
SQL skript pro vytvoření databáze s tabulkami (pro každý krok je samostatná tabulka) a s kompletními zdrojovými kódu tohoto tutoriálu lze stáhnout zde.
V prvním kroku se omezíme na vlastní tělo zprávy, bez jména a emailu.
Postačí nám stránky tři. Stránka index.php bude zobrazovat komentáře a formulář a stránka do_add_comment.php bude "akční stránkou", která provede vlastní přidání komentáře. Akční stránkou mám na mysli PHP skript, který provede nějakou akci a pak přesměruje prohlížeč na nějakou jinou stránku, tzn. sama o sobě nemá žádný HTML výstup. Poslední stránkou bude error.php používaná pro chybový výstup.
Pro naši aplikaci si vytvoříme potřebnou adresářovou strukturu. Vytvoříme si někde adresář pro vytvářenou aplikaci. Ten musí mít dva podadresáře:
Další věcí, kterou je potřeba vytvořit, je soubor s konfigurací. Musí se jmenovat config.php a musí existovat i kdyby měl být prázdný. Protože v našem příkladu budeme používat databázovou podporu EPF, prázdný nebude. Musí nastavit proměnné pro přístup k db:
<?php $mysql_host = 'localhost'; $mysql_username = 'root'; $mysql_password = ''; $mysql_dbname = 'epf_tutor'; ?>
Hodnoty si samozřejmě upravte podle potřeby. DB vrstva v EPF je velmi jednoduchá a podporuje pouze MySQL 4.1.3 a vyšší prostřednictvím mysqli. S příchodem PHP 5.1 a PHP Data Objects již nebude v budoucnu pravděpodobně potřeba.
Dále si vytvoříme pomocnou šablonu common.xsl a umístíme ji do adresáře view. Bude obsahovat kód společný pro všechny (nebo většinu) stránek. Kořenovým elementem XML dokumentů, které EPF generuje je element page. V našem případě tato společná šablona vygeneruje jednoduchou základní kostru XHTML dokumentu a pak zavolá <xsl:apply-templates/>, které naplní obsah.
Element page má atribut title, který použijeme pro naplnění titulku a hlavního nadpisu dokumentu. Zde je celý kód:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" version="1.0" omit-xml-declaration="yes"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" />
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title><xsl:value-of select='/page/@title'/></title>
</head>
<body>
<h1><xsl:value-of select='/page/@title'/></h1>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Teď již můžeme jednoduše vytvořit stránku indexu. Jednotlivé příspěvky budou ve vstupním XML reprezentovány elementy comment s atributem message, který bude obsahovat vlastní text zprávy. Obsah stránky bude generován pravidlem vybírajícím výše zmíněný element page. Výsledný kód je poměrně přímočarý:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:import href="common.xsl"/>
<xsl:template match="/page">
<p>Hello, this is small forum.</p>
<h2>Existing comments</h2>
<xsl:choose>
<xsl:when test='count(comment) > 0'>
<xsl:for-each select='comment'>
<hr />
<p><xsl:value-of select='@message'/></p>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<hr />
<p>Sorry, currently no comments.</p>
</xsl:otherwise>
</xsl:choose>
<hr />
<h2>New comment</h2>
<form action='do_add_comment.php' method='post'>
<table>
<tr>
<th>Comment message</th>
<td>
<textarea cols='50' rows='5' name='message'></textarea>
</td>
</tr>
<tr><td><input type='submit' /></td></tr>
</table>
</form>
</xsl:template>
</xsl:stylesheet>
Poslední potřebnou šablonou je error.xsl. Tu EPF používá, pokud se nezdaří validace vstupů (GET, POST) nebo pokud v aplikaci vznikne výjimka. Chyby jsou reprezentovány elementy msg s popisem chyby v atributu desc. Vytvořit jednoduchou chybovou stránku není problém:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:import href="common.xsl"/>
<xsl:template match="/page">
<h1>Error!</h1>
<xsl:for-each select='msg'>
<p><xsl:value-of select='@desc' /></p>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Teď vytvoříme kód pro úvodní stránku, který zobrazí existující komentáře. První, co musíme udělat, je připojit EPF. K tomu slouží skript main.php v adresáři epf:
<?php
require_once('epf/main.php');
Pokud by v době spouštění skriptu nebyl nastaven adresář se skriptem jako aktuální, např. pokud ho spouští cron, bylo by nutné ho před příkazem require_once jako aktuální nastavit, např. kódem chdir(dirname(__FILE__));
Stránky v EPF jsou reprezentovány potomky třídy Servlet:
class IndexServlet extends Servlet {
Ve stránkách, které tvoří HTML výstup je vhodné překrýt metodu get_title(), která by měla vrátit titulek naší stránky:
function get_title() {
return 'EPF tutorial';
}
Vlastní činnost stránky probíhá v překryté metodě work(). Nejprve si funkcí query() vyžádáme všechny komentáře. Funkce vrátí instanci třídy DbQuery, která implementuje rozhraní Iterator, takže lze řádky vrácené databází procházet pomocí foreach. Řádky jsou vráceny ve formě objektů. Do stránky je přidáme metodou add. Prvním parametrem bude jméno elemetu, druhým pak pole atributů.
function work() {
$q = query('SELECT `message` FROM `tutor1_comments`');
foreach ($q as $comment):
$this->add('comment', array('message' => $comment->message));
endforeach;
}
Nyní již jen vytvoříme instanci a zavoláme metodu run():
} $servlet = new IndexServlet(); $servlet->run(); ?>
Nyní napíšeme obsluhu pro přidávání komentářů. Překryjeme metodu validate_input() a umístíme do ní načtení hodnoty message, která nám byla poslána z formuláře metodou POST. K tomu slouží metoda req_post_string(). Má tři parametry: název, proměnná, chybové hlášení. Těchto metod existuje celá řada. Všechny mají tvar (req|opt)_(get|post)_typ.
req znamená, že je tato hodnota vyžadována. V případě, že chybí nebo nemá správný formát (záleží na typu) je použito chybové hlášení. Metody opt jsou určeny pro nepovinné parametry a chybové hlášení jim chybí.
post je pro POST a get je pro GET. Ehm :-).
Podporované typy:
Přidání komentáře provedeme pomocí SQL, které předáme funkci update(). Existuje i funkce insert(), která je téměř shodná, ale navíc vrací hodnotu insert id pro tabulky s AUTO_INCREMENT sloupcem. Celý kód do_add_comment.php:
<?php
require_once('epf/main.php');
class DoAddCommentServlet extends Servlet {
private $message;
function validate_input() {
$this->req_post_string('message', $this->message, 'Please enter some message text.');
}
function work() {
update("INSERT INTO `tutor1_comments` SET `message`='$this->message'");
go_to('');
}
}
$servlet = new DoAddCommentServlet();
$servlet->run();
?>
A to je vše. První verze je hotová. Můžete si výsledné soubory prohlédnout on-line.
Nyní přidáme ke komentáři jméno autora.
Změny v index.xsl jsou poměrně jednoduché:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:import href="common.xsl"/>
<xsl:template match="/page">
<p>Hello, this is small forum.</p>
<h2>Existing comments</h2>
<xsl:choose>
<xsl:when test='count(comment) > 0'>
<xsl:for-each select='comment'>
<hr />
<p><b>Name: <xsl:value-of select='@name'/></b></p>
<p><xsl:value-of select='@message'/></p>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<hr />
<p>Sorry, currently no comments.</p>
</xsl:otherwise>
</xsl:choose>
<hr />
<h2>New comment</h2>
<form action='do_add_comment.php' method='post'>
<table>
<tr>
<th>Name</th>
<td>
<input type='text' name='name'></input>
</td>
</tr>
<tr>
<th>Comment message</th>
<td>
<textarea cols='50' rows='5' name='message'></textarea>
</td>
</tr>
<tr><td><input type='submit' /></td></tr>
</table>
</form>
</xsl:template>
</xsl:stylesheet>
Ani změny v do_add_comment.php nejsou nijak překvapivé.
<?php
require_once('epf/main.php');
class DoAddCommentServlet extends Servlet {
private $message;
private $name;
function validate_input() {
$this->req_post_string('message', $this->message, 'Please enter some message text.');
$this->req_post_string('name', $this->name, 'Please enter your name.');
}
function work() {
update(<<<SQL
INSERT INTO
`tutor2_comments`
SET
`message`='$this->message',
`name`='$this->name'
SQL
);
go_to('');
}
}
$servlet = new DoAddCommentServlet();
$servlet->run();
?>
A index.php:
<?php
require_once('epf/main.php');
class IndexServlet extends Servlet {
function get_title() {
return 'EPF tutorial';
}
function work() {
$q = query('SELECT `name`, `message` FROM `tutor2_comments`');
foreach ($q as $comment):
$this->add('comment', array(
'message' => $comment->message,
'name' => $comment->name
));
endforeach;
}
}
$servlet = new IndexServlet();
$servlet->run();
?>
Tak to bylo hodně jednoduché. Kompletní kód.
Nyní ke komentářům přidáme nepovinnou položku email. Aby nebyl jednoduše odhalitelný pro spam roboty, nahradíme v něm znaky @ (zavináč) a . (tečka) odpovídajícími obrázky. K tomu slouží funkce email(). Tzn. z řetězce "a@b.cz" vyrobíme XHTML "a <img src="at.gif" alt="at" /> b <img src="dot.gif" alt="dot" /> cz". Funkce email() ho ale samozřejmě nevrátí jako řetězec, ale jako pole instancí DOMNode (resp. jeho potomků DOMText a DOMElement):
Vložení emailu do databáze bude poměrně jednoduché. Do formuláře si přidáme pro email položku:
<tr>
<th>Email</th>
<td>
<input type='text' name='email'></input>
</td>
</tr>
Ani v do_add_comment.php nás překvapení nečekají. Email necháme jako nepovinný, takže proměnnou $email musíme dopředu inicializovat:
<?php
require_once('epf/main.php');
class DoAddCommentServlet extends Servlet {
private $message;
private $name;
private $email = '';
function validate_input() {
$this->req_post_string('message', $this->message, 'Please enter some message text.');
$this->req_post_string('name', $this->name, 'Please enter your name.');
$this->opt_post_string('email', $this->email);
}
function work() {
update(<<<SQL
INSERT INTO
`tutor3_comments`
SET
`message`='$this->message',
`name`='$this->name',
`email`='$this->email'
SQL
);
go_to('');
}
}
$servlet = new DoAddCommentServlet();
$servlet->run();
?>
Ke generování emailu použijeme již zmiňovanou funkci email(). Kromě vlastního řetězce jí potřebujeme — stejně jako všem ostatním XML generujícím funkcím — předat odkaz na instanci DOMDocument, kterou používáme ke generování XML. V našem případě ji získáme metodou get_doc() instance třídy Servlet. Výsledek funkce email(), tedy pole instanci DOMNode můžeme předat metodě add() Servletu stejně jako řetězec, ona si s tím poradí:
<?php
require_once('epf/main.php');
class IndexServlet extends Servlet {
function get_title() {
return 'EPF tutorial';
}
function work() {
$q = query('SELECT `name`, `email`, `message` FROM `tutor3_comments`');
foreach ($q as $comment):
$this->add('comment', array(
'message' => $comment->message,
'name' => $comment->name,
'email' => email($this->get_doc(), $comment->email)
));
endforeach;
}
}
$servlet = new IndexServlet();
$servlet->run();
?>
Poslední věcí je tento email zobrazit pomocí <xsl:copy-of>:
<xsl:for-each select='comment'>
<hr />
<p><b>Name: <xsl:value-of select='@name'/></b></p>
<xsl:if test='email != ""'>
<p><b>
Email: <xsl:copy-of select='email/node()'/>
</b></p>
</xsl:if>
<p><xsl:value-of select='@message'/></p>
</xsl:for-each>
Všimněme si, že jsme museli napsat email/node(). Kdybychom napsali jenom email okopíroval by se nám tento element do výsledného XHTML dokumentu, což nechceme. Kompletní kód.
Nyní bychom chtěli, aby se ve výpisu zprávy znaky pro přechod na nový řádek (tzn. znaky s ASCII kódem 0x0A) konvertovaly na element <br />. K tomu slouží funkce nl2brXML():
function work() {
$q = query('SELECT `name`, `email`, `message` FROM `tutor4_comments`');
foreach ($q as $comment):
$this->add('comment', array(
'message' => nl2brXML($this->get_doc(), $comment->message),
'name' => $comment->name,
'email' => email($this->get_doc(), $comment->email)
));
endforeach;
}
Tato funkce pracuje na obdobném principu jako funkce email(). Musíme tedy obdobně upravit i XSLT šablonu:
<p><xsl:copy-of select='message/node()'/></p>
Bude šikovné, když naše minifórum bude umět interpretovat základní URL. Stačí když funkci nl2brXML() nahradíme funkcí nl2br_and_link2a_XML():
function work() {
$q = query('SELECT `name`, `email`, `message` FROM `tutor5_comments`');
foreach ($q as $comment):
$this->add('comment', array(
'message' => nl2br_and_link2a_XML($this->get_doc(), $comment->message),
'name' => $comment->name,
'email' => email($this->get_doc(), $comment->email)
));
endforeach;
}
V tomto kroku se naučíme vytvářet funkce podobné funkcím email() nebo nl2brXML(). Upravíme index.php tak, aby text mezi značkami [b] a [/b] zvýraznil tučným písmem.
K nahrazování řetězců za XML elementy slouží funkce xml_convert_str2elem(), která nahradí zadaný řetězec elementem, a xml_convert_regex2elem(), která vyhledává řetězec na základě regulárního výrazu. Naše první verze by mohla vypadat takto:
$message = xml_convert_regex2elem($this->get_doc(), $message, '#(\[b\].*?\[/b\])#i', "b");
Prvním parametrem je odkaz na náš DOMDocument, $message je konvertovaná zpráva, třetím parametrem je regulární výraz a poslední jméno výsledného elementu.
Tento kód ale ve výsledném textu ponechá i značky [b] a [/b], což nechceme. Funkce xml_convert_... umožňují zadat jako čtvrtý parametr nejenom řetězec se jménem výsledného elementu, ale i instanci třídy ElementCreator a my tak lépe můžeme kontrolovat vytvářený element. V našem případě vytvoříme instanci jejího potomka, třídy ExtractingElementCreator. Té předáme regulární výraz, kde bude ozávorkována část, kterou chceme získat. Podívejme se na výsledný kód:
function work() {
$q = query('SELECT `name`, `email`, `message` FROM `tutor6_comments`');
foreach ($q as $comment):
$message = nl2br_and_link2a_XML($this->get_doc(), $comment->message);
$message = xml_convert_regex2elem($this->get_doc(), $message,
'#(\[b\].*?\[/b\])#i',
new ExtractingElementCreator('#\[b\](.*?)\[/b\]#i', 'b'));
$this->add('comment', array(
'message' => $message,
'name' => $comment->name,
'email' => email($this->get_doc(), $comment->email)
));
endforeach;
}
Všimněme si, že funkcím xml_convert_..., stejně jako email(), nl2brXML() a nl2br_and_link2a_XML() můžeme předat kromě prostého textového řetězce i pole instancí DOMNode, tedy můžeme je volat na výsledcích vrácených jiným voláním nekteré z těchto funkcí.
Kompletní zdrojové kódy tohoto tutoriálu a SQL skripty pro vytvoření potřebné struktury databáze.
Pokud chcete vidět více kódu, který používá EPF, můžete studovat zdrojové kódy portálu devpaks.org.
Veškeré poznámky, připomínky, dotazy či opravy k EPF obecně i k tomuto tutoriálu uvítám na fóru o EPF. Můžete taktéž použít můj email či ICQ, obojí najdete na mé domácí stránce.