Přestože má RSS nejspíše svá nejlepší léta za sebou a tato situace se asi ani nezmění, dá se pro něj najít veliké pole uplatnění. Podporuje ho velké množství webových služeb, pro generování výpisu článků např. z blogů se hodí báječně. A navíc se na něm dá ukázat, jak programovat v PHP pěkně objektově.
RSS má svoji danou strukturu, a tak si hned z počátku ukážeme, jak by měl takový soubor vypadat. Nejdříve se uvádí klasické definice souboru, poté informace o zdroji (titulek, klíčová slova, popis apod.) a nakonec seznam jednotlivých položek:
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Muj web</title>
<link>http://mujweb.cz</link>
<webMaster>pepa.omacka@mujweb.cz (Pepa Omacka)</webMaster>
<description>Moje webove stranky o vsem moznem...</description>
<category>clanky, fotky, lidi, humor</category>
<docs>http://mujweb.cz/rss.xml</docs>
<lastBuildDate>Tue, 3 Nov 2009 22:00:05 GMT</lastBuildDate>
<image>
<url>http://mujweb.cz/favicon.png</url>
<title>Muj web/title>
<link>http://mujweb.cz</link>
</image>
<atom:link href="http://mujweb.cz/rss.xml" rel="self" type="application/rss+xml" />
<item>
<title>Prvni clanek</title>
<link>http://mujweb.cz/index.php?page=prvni-clanek</link>
<guid>http://mujweb.cz/index.php?page=prvni-clanek</guid>
<description>Tak tohle je prvni testovaci clanek.</description>
<pubDate>Tue, 3 Nov 2009 19:23:23 GMT</pubDate>
</item>
</channel>
</rss>
K vlastnostem obecným title
, link
, description
, category
a docs
asi není potřeba nic dodávat. Atribut webMaster
vyžaduje e-mail a jméno autora, u lastBuildDate
je potřeba dodržet daný formát. Položka image
dává možnost přidat k výstupu obrázek webu.
Ani v bloku item
není potřeba nic moc vysvětlovat. Guid
udává unikátní ID položky, mně se osvědčilo používat adresu umístění, která by neměla s ničím kolidovat.
Když už víme, jak má takové RSS vypadat, můžeme se pustit do tvorby třídy, která nám takovýto soubor vygeneruje. Pro jednoduchost si ji pojmenujeme RSS
. Bude obsahovat veřejné proměnné $title
, $link
, $webMaster
, $description
, $category
, $rssLink
a $imageUrl
a soukromou proměnnou $_items
, do které budeme ukládat jednotlivé položky. Bude obsahovat metodu render()
, která nám vrátí konečná data, a metodu pro přidání nové položky, jednoduše add()
. Určitě bychom si měli vytvořit metodu, která ošetří zadané hodnoty - zbaví je ošklivých znaků, odstraní HTML apod., pojmenujeme ji třeba escape()
.
Problém, který budeme muset řešit jak ve třídě RSS
, tak i u dat z RSSItem
, je správný formát data. Patrně nám bude dodáno ve tvaru YYYY-MM-DD, a tak by se hodila funkce, která by nám datum převedla. Umístíme ji do třídy RSS
a nazveme convertDate()
.
Takže námi navrhnutá struktura třídy RSS
může vypadat následovně:
class RSS {
public $title;
public $link;
public $webMaster;
public $description;
public $category;
public $rssLink;
public $imageUrl;
private $_items = array();
public function render() {
}
public function add() {
}
public function escape() {
}
public function convertDate() {
}
}
Protože i každá jednotlivá položka má svoji přesně danou strukturu, nebylo by na škodu vytvořit si třídu i pro ni. Pojmenujeme ji třeba RSSItem
. Bude obsahovat neveřejnou proměnnou $_values
, do které budeme ukládat data položky, a pak metody, kterými jednotlivá data budeme stanovovat - title()
, link()
, description()
a date()
. Nakonec bude obsahovat metodu, kterou získáme zadaná data - get()
.
class RSSItem {
private $_values = array();
public function title($title) {
}
public function link($link) {
}
public function description($description) {
}
public function date($date) {
}
public function get() {
}
}
Teď když už máme strukturu připravenou, můžeme se pustit do tvorby jednotlivých metod. Začneme nejjednoduším, třídou RSSItem
. Každá z jejích metod ukládá daný atribut do pole a pak vrací celou metodu, abychom mohli použít zápis RSSItem::title($title)->link($link)...
. Takže po vyplnění jednotlivých funkcí bude třída vypadat nějak takto:
class RSSItem {
private $_values = array();
public function title($title) {
$this->_values['title'] = $title;
return $this;
}
public function link($link) {
$this->_values['link'] = $link;
return $this;
}
public function description($description) {
$this->_values['description'] = $description;
return $this;
}
public function date($date) {
$this->_values['date'] = $date;
return $this;
}
public function get() {
return $this->_values;
}
}
Tím máme tuto třídu hotovou a můžeme se vesele pustit do jejího používání v hlavní třídě. Začneme od konce, s vytvořením metody escape()
. Ta má sloužit k odstranění nepříjemných znaků. Pokud si nejsme jistí, co v XML být může a co ne, sáhneme po příručce Davida Grudla a s jeho pomocí vytvoříme takovouto metodu:
public function encode($string) {
return htmlspecialchars(preg_replace('#[\x00-\x08\x0B\x0C\x0E-\x1F]+#', '', $string), ENT_QUOTES);
}
Dále se vrhneme na metodu add()
. Ta nebude o moc složitější - pouze vytvoří novou položku, uloží ji do pole a vrátí.
public function add() {
$item = new RSSItem;
$this->_items[] = &$item;
return $item;
}
Ani metoda convertDate()
nemusí být nutně složitá. Využijeme v ní funkci strtotime()
, která nám dodaný řetězec převede na časový otisk a ten pak už pouze musíme dostat do správného tvaru.
public function convertDate($date) {
return gmdate("D, d M Y H:i:s", strtotime($date))." GMT";
}
Poslední, co nám zbývá, je vytvořit metodu pro vykreslování. Ta bude trochu složitější a nebude na škodu si ji rozdělit na více menších funkcí. Zaprvé bude mít za úkol načíst všechny vložené položky, ošetřit zadaná data a vygenerovat XML kód se seznamem těchto položek, který bude muset dosadit do připraveného řetězce obsahujícího informace o dokumentu. Funkce, která ještě neobsahuje výpis položek, bude vypadat následovně:
public function render() {
$ret = '<?xml version="1.0" encoding="utf-8"?>'
.'<?xml-stylesheet type="text/css" href="rss.css"?>'
.'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">'
.'<channel>'
.'<title>'.self::encode($this->title).'</title>'
.'<link>'.$this->link.'</link>'
.'<webMaster>'.self::encode($this->webMaster).'</webMaster>'
.'<description>'.self::encode($this->description).'</description>'
.'<category>'.self::encode($this->category).'</category>'
.'<docs>'.$this->rssLink.'</docs>'
.'<lastBuildDate>'.gmdate('D, d M Y H:i:s').' GMT</lastBuildDate>'
.'<image>'
.'<url>'.$this->imageUrl.'</url>'
.'<title>'.self::encode($this->title).'</title>'
.'<link>'.$this->link.'</link>'
.'</image>'
.'<atom:link href="'.$this->rssLink.'" rel="self" type="application/rss+xml" />';
// ziskani polozek...
$ret .= '</channel>'
.'</rss>';
return $ret;
}
Pro přehlednost si vytvoříme na získání výpisu položek další funkci, ku příkladu _renderItems()
. Ta bude definovaná jako privátní. V ní musíme projít seznam položek, získat jejich obsah, ošetřená data uložit a pak všechna vrátit. Tuto metodu pak budeme volat ve funkci render()
:
public function render() {
$ret = '<?xml version="1.0" encoding="utf-8"?>'
.'<?xml-stylesheet type="text/css" href="rss.css"?>'
.'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">'
.'<channel>'
.'<title>'.self::encode($this->title).'</title>'
.'<link>'.$this->link.'</link>'
.'<webMaster>'.self::encode($this->webMaster).'</webMaster>'
.'<description>'.self::encode($this->description).'</description>'
.'<category>'.self::encode($this->category).'</category>'
.'<docs>'.$this->rssLink.'</docs>'
.'<lastBuildDate>'.gmdate('D, d M Y H:i:s').' GMT</lastBuildDate>'
.'<image>'
.'<url>'.$this->imageUrl.'</url>'
.'<title>'.self::encode($this->title).'</title>'
.'<link>'.$this->link.'</link>'
.'</image>'
.'<atom:link href="'.$this->rssLink.'" rel="self" type="application/rss+xml" />';
$ret .= $this->_renderItems();
$ret .= '</channel>'
.'</rss>';
return $ret;
}
public function _renderItems() {
$ret = '';
foreach($this->_items as $item) {
$values = $item->get();
$ret .= '<item>'
.'<title>'.self::encode($values['title']).'</title>'
.'<link>'.$values['link'].'</link>'
.'<guid>'.$values['link'].'</guid>'
.'<description>'.$values['description'].'</description>'
.'<pubDate>'.self::convertDate($values['date']).'</pubDate>'
.'</item>';
}
return $ret;
}
A tím je naše třída pro generování RSS vlastně hotová. A tak si ji sem hodíme celou a rovnou i s použitím:
<?php
class RSS {
public $title;
public $link;
public $webMaster;
public $description;
public $category;
public $rssLink;
public $imageUrl;
private $_items = array();
public function render() {
$ret = '<?xml version="1.0" encoding="utf-8"?>'
.'<?xml-stylesheet type="text/css" href="rss.css"?>'
.'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">'
.'<channel>'
.'<title>'.self::encode($this->title).'</title>'
.'<link>'.$this->link.'</link>'
.'<webMaster>'.self::encode($this->webMaster).'</webMaster>'
.'<description>'.self::encode($this->description).'</description>'
.'<category>'.self::encode($this->category).'</category>'
.'<docs>'.$this->rssLink.'</docs>'
.'<lastBuildDate>'.gmdate('D, d M Y H:i:s').' GMT</lastBuildDate>'
.'<image>'
.'<url>'.$this->imageUrl.'</url>'
.'<title>'.self::encode($this->title).'</title>'
.'<link>'.$this->link.'</link>'
.'</image>'
.'<atom:link href="'.$this->rssLink.'" rel="self" type="application/rss+xml" />';
$ret .= $this->_renderItems();
$ret .= '</channel>'
.'</rss>';
return $ret;
}
public function _renderItems() {
$ret = '';
foreach($this->_items as $item) {
$values = $item->get();
$ret .= '<item>'
.'<title>'.self::encode($values['title']).'</title>'
.'<link>'.$values['link'].'</link>'
.'<guid>'.$values['link'].'</guid>'
.'<description>'.$values['description'].'</description>'
.'<pubDate>'.self::convertDate($values['date']).'</pubDate>'
.'</item>';
}
return $ret;
}
public function add() {
$item = new RSSItem;
$this->_items[] = &$item;
return $item;
}
public function encode($string) {
return htmlspecialchars(preg_replace('#[\x00-\x08\x0B\x0C\x0E-\x1F]+#', '', $string), ENT_QUOTES);
}
public function convertDate($date) {
return gmdate("D, d M Y H:i:s", strtotime($date))." GMT";
}
}
class RSSItem {
private $_values = array();
public function title($title) {
$this->_values['title'] = $title;
return $this;
}
public function link($link) {
$this->_values['link'] = $link;
return $this;
}
public function description($description) {
$this->_values['description'] = $description;
return $this;
}
public function date($date) {
$this->_values['date'] = $date;
return $this;
}
public function get() {
return $this->_values;
}
}
header('Expires: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Content-Type: text/xml; charset=utf-8');
$rss = new RSS;
$rss->title = 'Muj web';
$rss->link = 'http://mujweb.cz/';
$rss->webMaster = 'pepa.omacka@mujweb.cz (Pepa Omacka)';
$rss->description = 'Takovy web o vsem moznem, treba o PHP.';
$rss->category = 'php, web, internet, server, humor';
$rss->rssLink = 'http://mujweb.cz/rss.xml';
$rss->imageUrl = 'http://mujweb.cz/favicon.png';
$rss->add()
->title('Druhy test')
->link('http://mujweb.cz/index.php?page=druhy-test')
->description('A co kdyz budou clanky dva?')
->date('2007-10-01 12:43:23');
$rss->add()
->title('Test')
->link('http://mujweb.cz/index.php?page=test')
->description('Prvni pokusny clanek s popisem.')
->date('2009-11-04 12:43:23');
echo $rss->render();
Vytvořená třída není zdaleka dokonalá. Vůbec nehlídá, co jí kdo podstrčí, dovoluje vypisovat prázdné položky, nehlídá unikátnost guid
, nevyužívá všech možností RSS. Hodilo by se, kdyby položky řadila podle data. Není ale problém tyto nedostatky doplnit.