W niniejszym wpisie chciałbym się podzielić mechanizmem do zarządzania kanałami RSS/Atom. Jako, iż jest to bardzo pożądana funkcjonalność postaram się napisać coś łatwego w rozbudowie i zarządzaniu minimalnym nakładem pracy.
Jak zapewne wiesz, mamy kilka protokołów do publikowania swoich kanałów z nowościami ze strony, m.in. RSS czy Atom. Postaramy się obsłużyć każdy z nich (i każdy inny).
Główne założenia
Moim zamiarem było napisanie prostej klasy, którą będę mógł doklejać do kolejnych projektów (budowanych w oparciu o mini-cms). Poza tym musi spełniać następujące wymagania:
- prosta w zarządzaniu,
- prosta w rozbudowie,
- do wielokrotnego wykorzystania.
Zaznaczę także, że napisana klasa nie będzie wykorzystywała wszystkich funkcjonalności protokołu RSS czy Atom. Mimo to stosunkowo łatwo będzie dopisać brakujące elementy.
RSS czy Atom mimo swej prostoty (RDF to skrót od Really Simple Syndication), posiada także bardziej zaawansowane możliwości. Ba, nawet widziałem książki o tym protokole mające kilkaset stron, także ogarnięcie wszystkich możliwości protokołu nie wchodziło w grę. Skupiłem się wyłącznie na podstawowej funkcjonalności, która wystarcza w 90% przypadkach.
Poza tym część kodu zrzuciłem na Smarty – system szablonów służący rozdzieleniu logiki od widoku. Klasa do zarządzania kanałami jest częścią większego projektu, przy którym korzystam z tych szablonów, stąd taka decyzja.
Klasa Feed do zarządzania kanałami
Czas skończyć gadać, a zacząć pisać. Stwórzmy więc klasę do naszych kanałów, Feed:
1 2 3 | class Feed { } |
Na początek będziemy potrzebowali kilka właściwości:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Feed { private $tpl; private $feed; private $template_filetype = 'text/xml'; private $template_filename; private $template_content; protected $channel_name; protected $channel_link; protected $channel_description; protected $channel_items; protected $channel_items_num = 5; } |
Słowem wyjaśnienia:
- tpl – „uchwyt” do systemu szablonów (Smarty),
- feed – odwołanie do konkretnej klasy do zarządzania kanałami,
- template_filetype – domyślny typ kanału – zazwyczaj jest to text/xml,
- template_filename – przechowuje nazwę do szablonu z kanałem,
- template_content – przechowuje kod widoku,
- channel_name, channel_link, channel_description, channel_items, channel_items_num – przechowuje kolejno: nazwę kanału, adres strony z kanałem, krótki opis, tablicę z wyświetlanymi pozycjami oraz ilość pozycji do wyświetlenia.
I to by było na tyle. Teraz przejdźmy do konstruktora:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Feed { public function __construct($feed) { $this->tpl = new TemplateFeed; $this->feed = $feed; $this->template_filename = $feed->getFileName(); $this->template_filetype = $feed->getFileType(); $this->feed->setOtherAssigns($this->tpl); } } |
Konstruktor przyjmuje jeden argument – jest nim konkretna klasa, typu RSS czy Atom (o czym dokładniej za chwilę).
Ponadto wywołujemy tutaj klasę TemplateFeed, która będzie zajmowała się separacją kodu HTML i PHP.
Wywołujemy też kilka nieistniejących jeszcze metod klasy przekazanej w argumencie – getFileName, getFileType, setOtherAssigns. Za chwilę zobaczymy czym one się zajmują.
Czas dodać kilka podstawowych metod do zarządzania kanałem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | /** * Set Channel Name * * @param string channel name **/ public function setChannelName($channel) { $this->channel_name = $channel; } /** * Set Channel Link * * @param string channel link **/ public function setPageLink($link) { $this->channel_link = $link; } /** * Set Channel Description * * @param string channel description **/ public function setPageDescription($description) { $this->channel_description = $description; } /** * Set Channel Items Num * * @param integer channel items num **/ public function setItemsNum($num) { $this->channel_items_num = (int)$num; } /** * Get FileType * * @return string template filetype **/ public function getFileType() { return $this->template_filetype; } /** * Add Items * add items to channel with details * * @param array channel items **/ public function addItems(array $items) { $this->channel_items = array_slice($items, 0, $this->channel_items_num); // improve data out foreach ($items as $id => &$item) { // description in xml format if (isset($item['description'])) { $item['description'] = htmlspecialchars($item['description'], ENT_COMPAT, 'UTF-8'); } // link in xml format if (isset($item['link'])) { $item['link'] = str_replace('&', '&', $item['link']); } // improve publications date if (isset($item['pubDate'])) { $item['pubDate'] = date($this->feed->getFormatDate(), strtotime($item['pubDate'])); } } } |
Wyjaśnienia wymaga chyba tylko ostatnia metoda, addItems. W metodzie tej jako argument pobieramy tablicę nowości do naszego kanału.
Na samym początku ucinamy tą tablicę, by nie pozostało w niej więcej elementów niż ma liczyć tworzony kanał.
Następnie przy pomocy pętli przechodzimy przez każdy po kolei element. Gdy natrafimy na link zamieniamy znak & na &, by parser XML nie wywalał błędu. Poza tym musimy też skonwertować datę do zgodnej z protokołem Atom/RSS. To wszystko.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * Execute * generate channel, send headers, etc * * @return string channel code **/ public function execute() { $this->tpl->assign('channel_name', $this->channel_name); $this->tpl->assign('channel_link', $this->channel_link); $this->tpl->assign('channel_description', $this->channel_description); $this->tpl->assign('channel_items', $this->channel_items); // execute the template $this->template_content = $this->tpl->execute($this->template_filename . '.tpl', 'fetch'); $this->sendHeaders(); } |
Na sam koniec potrzebujemy metody, która po przetworzeniu danych wypluje nam kod kanału. Właśnie tym zajmuje się powyższa metoda execute.
Przypisujemy w niej zmienne do naszego systemu szablonów, przetwarzamy szablon, pobieramy kod i zapisujemy do wcześniej utworzonej właściwości template_content, po czym wywołujemy metodę odpowiedzialną za przesłanie do przeglądarki odpowiednich nagłówków HTTP, sendHeaders.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * Send Headers * set content type, content length and others **/ protected function sendHeaders() { // send headers to application header('Content-Type: ' . $this->template_filetype . '; charset=UTF-8'); header("Content-Length: " . mb_strlen($this->template_content)); // show content of file echo $this->template_content; exit; } |
Metoda ta jest bardzo prosta – ustawiamy tylko nagłówek Content-Type i Content-Length, przesyłamy do okna przeglądarki, po czym kończymy działanie skryptu PHP.
System szablonów
Jak wcześniej pewnie zauważyłeś, odwołaliśmy się w powyższym kodzie do klasy TemplateFeed. Najwyższy czas zabrać się za napisanie tej klasy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | // require system templates - Smarty require_once 'libs/smarty/Smarty.class.php'; class TemplateFeed extends Smarty { public function __construct() { parent::__construct(); // folders $this->template_dir = 'templates/'; $this->compile_dir = 'templates_c/'; $this->cache_dir = 'cache/'; // enable cache $this->caching = 2; $this->cache_lifetime = 86400; } /** * Execute * display panel page * * @param string page filename * @param string display or fetch template * @return string template after fetch **/ public function execute($page, $mode = 'display') { // set mode switch ($mode) { case 'display': $this->display('feed/' . $page); break; case 'fetch': return $this->fetch('feed/' . $page); break; } } } |
I to już cały kod naszej klasy zajmującej się podziałem warstw w aplikacji. Nie jest zbyt skomplikowany, ponieważ cała praca została zrzucona na Smarty.
My tylko w konstruktorze ustawiamy własne preferencje konfiguracyjne (w tym włączamy cache, by nie budować kanału za każdym razem – kanał zostanie zbudowany tylko jeden raz, a później będzie trzymany w buforze).
Ponadto dodajemy metodę execute, która zwraca przetworzony już szablon.
Obsługa konkretnych kanałów
Pozostało nam tylko dodanie obsługi konkretnych protokołów kanałów, czyli de facto najważniejsza część naszego kodu. Jako iż ktoś może chcieć oferować na swojej stronie kanały RSS w wersji 2.0, inny w wersji 1.x, natomiast ktoś jeszcze inny w zupełnie innym formacie, jak Atom, musimy zaprogramować kilka opcji wyboru.
Niezbędne jest w związku z tym napisanie interfejsu narzucającego określony sposób tworzenia nowych kanałów.
1 2 3 4 5 6 7 8 | interface IFeedChannel { public function getFileName(); public function getFileType(); public function setOtherAssigns(TemplateFeed $tpl); } |
Wiemy więc, że każda klasa będzie musiała mieć powyższe klasy. Łatwo można zgadnąć czym będą one się zajmowały, ale o tym za chwilę.
Warto dodać w tym miejscu, że stworzone zaraz klasy będą przekazywane jako argument do klasy Feed. Dzięki takiemu rozwiązaniu w samym skrypcie będziemy mogli łatwo zadecydować który rodzaj protokołu chcemy wykorzystać.
RSS
Pora zabrać się za utworzenie klasy do obsługi RSS. Na potrzeby przykładu napiszemy tylko szablon dla RSS w wersji 2.0, czyli tej najczęściej wykorzystywanej w dzisiejszym Internecie.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | class RSS implements IFeedChannel { private $version; public function __construct($version = '2.0') { $this->version = $version; } /** * Get Template Name * get filename for template with xml-code * * @return string channel template filename **/ public function getFileName() { return 'rss_' . str_replace(array('.', ','), '', $this->version); } /** * Get Template FileType * get filetype/mimetype for template * * @return string template extension **/ public function getFileType() { return 'application/rss+xml'; } /** * Get Format Date * get format date in XML file * * @see http://php.net/manual/en/function.date.php * @return string format date * **/ public function getFormatDate() { return 'r'; } /** * Set Other Assigns * set some other assigns to rss file **/ public function setOtherAssigns(TemplateFeed $tpl) { $tpl->assign('channel_version', $this->version); } } |
Klasa jest bardzo prosta do zrozumienia, jednak krótko opiszę co jest do czego :-)
- jak zostało wcześniej wspomniane, klasa korzysta z interfejsu IFeedChannel,
- do konstruktora trafia wersja RSS, domyślnie jest to 2.0,
- metoda getFileName zwraca nazwę szablonu,
- metoda getFileType zwraca typ pliku (dla RSS będzie to application/rss+xml),
- getFormatDate zwraca format daty wykorzystywanej w protokole RSS do zapisu daty wpisu (format ten zostanie podstawiony do funkcji date języka PHP)
- metoda setOtherAssigns pozwala na dopisanie do szablonu nowych zmiennych – w tym przypadku do szablonu dodajemy informację o wersji wykorzystanego RSS-a.
Potrzebujemy jeszcze dopisać szablon, który będzie wyświetlał nasz kanał RSS:
templates/rss_20.tpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>{$channel_name}</title> <link>{$channel_link}</link> <description>{$channel_description}</description> {foreach from=$channel_items item=travel} <item> {foreach from=$travel key=element item=value} <{$element}>{$value}</{$element}> {/foreach} </item> {/foreach} </channel> </rss> |
Myślę, że nie trzeba wyjaśniać. Kod po prostu prezentuje typowy, podstawowy dokument RSS, który będzie wypełniany naszymi danymi.
Atom
Idąc za ciosem, możemy od razu dopisać klasę do wyświetlania kanału zgodnego z protokołem Atom:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | class Atom implements IFeedChannel { /** * Get Template Name * get filename for template with xml-code * * @return string channel template filename **/ public function getFileName() { return 'atom'; } /** * Get Template FileType * get filetype/mimetype for template * * @return string template extension **/ public function getFileType() { return 'application/atom+xml'; } /** * Get Format Date * get format date in XML file * * @see http://php.net/manual/en/function.date.php * @return string format date * **/ public function getFormatDate() { return DATE_ATOM; } /** * Set Other Assigns * set some other assigns to channel file **/ public function setOtherAssigns(TemplateFeed $tpl) { $tpl->assign('channel_self_link', 'http://' . $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"]); $tpl->assign('channel_update', date($this->getFormatDate())); } } |
Podobnie jak wcześniej, także i teraz nasza klasa implementuje interfejs IFeedChannel. Poza tym zwraca nazwę szablonu, typ pliku (tym razem jest to application/atom+xml), format daty oraz przypisuje inne zmienne do szablonu – potrzebujemy tutaj adresu do naszego kanału oraz daty ostatniej aktualizacji kanału.
Do tego dochodzi znowu prosty szablon:
templates/atom.tpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?xml version="1.0" encoding="UTF-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> <title>{$channel_name}</title> <subtitle>{$channel_description}</subtitle> <link href="{$channel_self_link}" rel="self" /> <link href="{$channel_link}" /> <updated>{$channel_update}</updated> {foreach from=$channel_items item=entry} <entry> <title>{$entry.title}</title> <link href="{$entry.link}" /> <updated>{$entry.pubDate}</updated> <summary>{$entry.description}</summary> </entry> {/foreach} </feed> |
I zrobione!
Praktyczne wykorzystanie
Nadszedł czas na zastosowanie naszego mechanizmu w praktycznym przykładzie. Utwórzmy więc przykładową tablicę z kilkoma feedami:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | $items = array( array( 'title' => 'Kompendium programisty #2', 'link' => 'http://blog.kamilbrenk.pl/kompendium-programisty-2/', 'pubDate' => 'Thu, 30 Sep 2010 00:41:28 +0000', 'description' => 'Nie ma wątpliwości, że praca programisty polega na ciągłym rozwoju i doskonaleniu swoich umiejętności, szlifowaniu wiedzy, poznawaniu nowych technik i automatyzacji przestarzałych. Także i ja dbam o odwiedzających mój blog, a więc zamieszczam kolejny zbiór ciekawych i pouczających linków :-)' ), array( 'title' => 'Wielowątkowość w JavaScript', 'link' => 'http://blog.kamilbrenk.pl/web-workers-wielowatkowosc-w-javascript/', 'pubDate' => 'Mon, 27 Sep 2010 15:25:11 +0000', 'description' => 'Najprawdopodobniej po przeczytaniu tytułu tego wpisu pierwszą myślą, która przyszła Ci do głowy było "wielowątkowość" i "JavaScript" razem? To musi być jakiś błąd. Pocieszę Cię jednak! To nie jest błąd i w niniejszym wpisie opiszę nową, pojawiającą się dopiero w przeglądarkach technologię - Web Workers. Umożliwia ona tworzenie wielowątkowych aplikacji z wykorzystaniem JavaScript.' ), array( 'title' => 'Jak załadować biblioteki JavaScript?', 'link' => 'http://blog.kamilbrenk.pl/jak-zaladowac-biblioteki-javascript/', 'pubDate' => 'Mon, 20 Sep 2010 17:08:41 +0000', 'description' => 'Większość developerów w ostatnich czasach zaczyna coraz częściej korzystać z CDN Google czy Microsoftu celem załadowania jQuery, Prototype czy innego frameworka do JavaScript. Jest w tym mnóstwo korzyści i sam też tak robię. Co jeśli jakimś cudem nie uda się połączyć z zewnętrznym serwerem? Strona pozostanie bez najważniejszej biblioteki, przez co reszta naszych skryptów nie zadziała.' ) ); |
Tablica ta zawsze musi posiadać takie indeksy, celem ułatwienia właściwego formatowania każdego feeda (konwersja daty i niedozwolonych znaków).
Czas na najważniejszą część wpisu:
1 2 3 4 5 6 7 8 9 10 11 | $feed = new Feed(new Atom()); $feed->setItemsNum(10); $feed->setChannelName('Kamil Brenk Blog'); $feed->setPageLink('http://blog.kamilbrenk.pl/'); $feed->setPageDescription('Blog programisty, w którym znajdziesz przydatne i ciekawe informacje ze świata Web. Poruszane tematyki to przede wszystkim PHP, JavaScript, bazy danych, modelowanie danych i inne. Zapraszam!'); $feed->addItems($items); $feed->execute(); |
W ten sposób na określonej stronie wyrzucamy nasz kanał Atom, przesyłane są też określone nagłówki, a całość zapisywana do buforu do czasu kolejnej modyfikacji naszego kanału.
A może zamiast protokołu Atom chcesz wykorzystać RSS 2.0? Nic prostszego, bowiem trzeba dokonać zmiany tylko w jednym miejscu:
1 | $feed = new Feed(new RSS('2.0')); |
W ten sposób można też dodawać kolejne protokoły i poszczególne ich wersje, co nie jest szczególnie trudnym zadaniem.
Podsumowanie
Tak jak na samym początku napisałem, niniejszy kanał RSS/Atom nie wykorzystuje nawet 10% pełnej funkcjonalności tych protokołów, niemniej na moje potrzeby wystarczy w zupełności. A jeśli ktoś będzie chciał dodać kolejne elementy do dokumentu – nic prostszego, wystarczy edytować plik szablonu oraz dodać nowe zmienne w metodzie setOtherAssigns.
Powyższy kod wykorzystuję w kilku moich projektach i sprawdzają się bez zarzutu. Kod ten co prawda jest częścią większego projektu – mojego autorskiego CMS-a, którego mam w planach stopniowo opisywać na blogu, dodając kolejne funkcjonalności. Być może komuś się przyda.
Być może ktoś też będzie na tyle miły, że wytknie mi błędy lub zaproponuje lepsze rozwiązania, na co czekam :-)
Ok, a dlaczego nie generujesz tego przez klasy, które służą do manipulacji XML-ami, np. SimpleXML?
Też można by było, ale jak napisałem używam u siebie systemu templatek i wolałem zrzucić wszystko na Smarty, w tym od razu cachowanie. Chyba łatwiej niż przez SimpleXML, DOMDocument czy inne :-)
Ale SimpleXML możesz od razu rzutować do stringa, do tego masz bardziej uniwersalne, choćby dlatego, że jeśli uznasz, iż potrzebujesz dodatkowej własności (niektóre czytniki coś takiego obsługują, AFAIR), to nie trzeba przekopywać od razu całego szablonu, tylko dodajesz węzeł do obiektu.
Poza tym, Smarty to prehistoria, tego się jeszcze używa?
Mi tam niczego nie brakuje w Smartach, dają radę :) Mam napisanych sporo własnych pluginów i miałbym teraz rezygnować na koszt innego, praktycznie niczym się nie różniącego systemu? Jakoś nie widzę za wielu zalet takiego rozwiązania… Może kiedyś z braku laku przerzucę się na inny system :)
SimpleXML to mógłby być w sumie lepszy wybór.
Może to nieco heretyczne, ale po co Ci system szablonów przy konstrukcji MVP/MVC?
Mi tam takie Smarty ułatwia pracę – daje od razu cache, trochę ułatwia pracę (przy generowaniu kodu) – poza tym konstrukcja jest bardzo łatwa. No i z przyzwyczajenia :-)
Klasa wyrżnięta żywcem z frameworka (stara wersja korzystała z DOMDocument)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
public $xmlns = 'http://www.w3.org/1999/xhtml';
public $title;
public $link;
public $updated;
public $id;
public $content = array();
/**
* Sets basic parameters for ATOM feed
*/
public function data() {
$this->xmllang = Url::$url['lang_id'];
$this->xmlbase = Url::$url['self'];
}
/**
* Creates ATOM feed data from content
*/
public function __toString() {
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0"></feed>');
$xml->addChild('title', $this->title);
$node = $xml->addChild('link', $this->title);
$node->addAttribute('href', $this->link);
$node->addAttribute('rel', 'self');
$xml->addChild('updated', $this->updated);
$xml->addChild('id', $this->link);
foreach($this->content as &$row) {
$entryNode = $xml->addChild('entry');
$entryNode->addChild('id', $row['url']['self']);
$entryNode->addChild('title', $row['title']);
$node = $entryNode->addChild('link');
$node->addAttribute('href', $row['url']['self']);
$node = $entryNode->addChild('author');
$node->addChild('name', $row['user']['Name']);
$entryNode->addChild('updated', date('c', strtotime($row['datetime'])));
$node = $entryNode->addChild('summary');
$node->addAttribute('type', 'xhtml');
$subNode = $node->addChild('div');
$subNode->addAttribute('xmlns', $this->xmlns);
if(!strstr($row['cover'], 'no_image')) {
$imgNode = $subNode->addChild('img');
$imgNode->addAttribute('src', Thumbinalizer::make($row['cover'], Conf::$ini['Directories']['thumb'], 319, 200, 'file', true));
$imgNode->addAttribute('alt', $row['title']);
}
$subNode->addChild('p', str_replace("\r", '', $row['text']));
}
return $xml->asXML();
}
/**
* Return rows count
*
* @param int &$count
*
* @return int
*/
public function rowCount(&$count = 0) {
return count($this->content);
}
/**
* Does nothing
*
* @return bool false
*/
public function read($limit = NULL, $hop = NULL, $element = NULL) {
return false;
}
/**
* Does nothing
*
* @return bool false
*/
public function write() {
return false;
}
/**
* Does nothing
*
* @return bool false
*/
public function drop() {
return false;
}
}
Michał, widzę że właśnie tworzysz strukturę dokumentu w locie.. chyba też u siebie to przestawię, za dużo roboty nie ma :-) Do czego tylko służą metody – read/write/drop?
A tak nawiasem mówiąc, do właściwości wolę się odwoływać przez metody set/get – niby trochę więcej roboty, ale za to porządek w kodzie. No i zachowana jest enkapsulacja.
Klasa Atom dostała read/write/drop jako spuściznę dziedzictwa po prototypie (jak i kilka innych metod) i aby nie dochodziło do dziwnych wywołań – read/write/drop oryginalnie odpowiadają za obsługę wymiany danych z DB – tu zaś zostały przeciążone by nie robiły nic – Atom dostaje dane od innych modułów.
Możliwe, że w przyszłości do czegoś wykorzystam read/write/drop do czegoś sensownego…
Jak widać na powyższym przykładzie – mam w głębokim poważaniu getery, setery. Bez nich też jakoś udaje się zachować porządek. Do enkapsulacji w zupełności wystarczają mi public/protected/private.
Po to by odseparować warstwę logiki od warstwy danych?
A ja tam wolę przekładać kompozycję nad dziedziczenie, bo później wychodzą takie dziwne kombinacje :-)
Pewnie chodziło o to, że nie trzeba do tego żadnego systemu szablonów :-) W różnych frameworkach jedynym systemem szablonów jest czysty PHP (przykładowo Yii tak robi). Coraz częściej słyszę, że wszelkie systemy templatek dają tylko niepotrzebny narzut oraz konieczność uczenia się nowego języka. Coś w tym prawdy jest, mimo to wolę konstrukcję smarty-podobną…
Zależy od podejścia – w tym przypadku wyszło dziwadło takie trochę…
W pozostałych modułach – sprawa jest o wiele przyjemniejsza – jedynie trzeba przeciążyć jedną metodę odpowiadającą za mapowanie DB i dzięki metodom read/write/drop bez jakiegokolwiek dziergania zapytań mam gotową obsługę select, insert, update, drop np:
2
3
4
$Module->fields('id', 'title');
$Module->order('order', 'asc');
$Module->read(2);
Co nam w efekcie wygeneruje z automatu zapytanie o treści
Co do szablony vs „raw php” – kolejnego języka się nie uczę – po to napisałem sobie system szablonów by był zbliżony do PHP a i by nierozgarnięty koder wklepał szablonik :)
Fajna zabawka do dynamicznego układania zapytań :) u siebie zapytania wklepuję samemu, np. :
To dalej trafia do PDO i zwraca mi dane bezpośrednio do punktu wyjścia. Przy łatwiejszych poleceniach mam mniej przyjazny interfejs, ale przy bardziej to jak jest rozwiązywane przez klasę Prototype?
Jak połączyć kilka tabel z UNION? Albo porobić LEFT JOINY, RIGHT JOINY, HAVING czy coś jeszcze innego? :-) No chyba, że jest tam osobna metoda do samodzielnego pisania SQLi, zaraz zerknę.
Jest, jest metoda do ‚ręcznych’ zapytań. Po automatycznym wygenerowaniu zapytania – string idzie właśnie do tej metody – nic się nie marnuje :D
Automat nie obsługuje żadnych join’ów. Zostawiłem to do napisania później, ale się okazało zbędne po zastosowaniu obserwera, obiekt reagujący na zdarzenie sam sobie sprawdza jakie dane ma pobrać.
Np.:
2
3
4
$Article = new Article();
Observer::attach($Article, $Struct);
$Struct->read(10);
Co wygeneruje takie zapytania
2
SELECT * FROM `tabela_article` WHERE `struct_id` IN (---id z odczytanych struct---);
Przy okazji – tu się uwidacznia kolejne rzeczy dziedziczone:
– każdy konstruktor przyjmuje jako parametr tablicę o strukturze parametr => wartość, które są od razu przypisywane do parametrów obiektu, np.:
2
3
echo $Struct->id; // 1
echo $Struct->dupa; // jest
– destruktor wypisuje obiekt z obserwera
(Nie wiem jaką wersję mego frameworka masz, bo klasa Data przeszła kilka zmian – doszła obsługa kluczy wielokolumnowych – co zmniejszyło potrzebę przeciążania metod)
Mam fw z 30 września 2010 :-) A nie powiem, całkiem przyjemna obsługa bazy danych – spore ułatwienie pracy. Choć takie „ułatwiacze” to też zawsze jakiś narzut czasowy na cały framework. Z chęcią używałbym Doctrine czy Propel w swoich projektach, ale to też muli i jest mniej wydajne niż gołe SQLe – a skoro nie sprawiają wiele trudności to piszę samodzielnie. Jak na razie jakoś jeszcze to znoszę :D
Z tym obciążeniem to bym nie przesadzał – właśnie sprawdziłem jaki nakład narzuca klasa Data i wyszło, że nakład tworzenia zapytania to ~2% czasu wykonania żądania, więc niewiele za taką wygodę :)