Smarty - Szablony w aplikacjach PHP #1
SPIS TREŚCI: Smarty - Szablony w aplikacjach PHP #1 Smarty - Szablony w aplikacjach PHP #2
Kilka słów wstępu...
Każdy starszy programista wie, że aby projektem swobodnie mogło zarządzać kilka osób należy rozdzielić politykę biznesową od polityki prezentacji. Osoby, które same zajmują się pisaniem w PHPie, czy też innym języku programowania wiedzą, że można od razu przystąpić do pracy - na bieżąco pisać kod i rozmyślać co dodać dalej. Nie jest to najlepsze podejście, chyba że potrzebujemy zrobić małą stronę, która będzie np. wyświetlać rekordy z bazy danych i wpisywać je do tabeli. Do takich zastosowań nie potrzeba wielkich, obszernych, pamięcio i moco-żernych frameworków. Jednak jeżeli chcemy stworzyć stronę o dużo większych wymaganiach, warto na początku zaprojektować i przemyśleć z jakich komponentów będzie składać się nasza strona, oraz jakie opcje musimy zaprogramować, aby osiągnąć dobre rezultaty. Dzięki podzieleniu strony na komponenty możemy później w łatwy sposób wykorzystać je w innym projekcie.
Dzisiejszym tematem jest...
Dzisiaj chciałem przedstawić wam pokrótce nie framework do PHP, a małą klasę pozwalająca na oddzielenie warstwy prezentacji od warstwy reguły biznesu i warstwy dostępu do danych - SMARTY. Podstawowym zadaniem Smartiego, bo tak go dalej będę "przezywał", jest oddzielenie logiki aplikacji od logiki jej prezentacji. Wbrew pozorom Smarty zawiera pewne cechy logiki, która ułatwia odseparowanie PHP'a od szablonu i styczności z kodem HTML.
Co to jest warstwa dostępu do danych?
Są to dane, które chcemy zaprezentować użytkownikom. Dane te mogą być zapisane w postaci plików XML, w bazie danych SQL, w pliku, a nawet na sztywno zapisane w tablicy.
Co to jest warstwa reguł biznesu?
Tutaj prowadzimy cała logikę strony. Programujemy przypadki, które różnymi interakcjami narzuca nam użytkownik. Kliknięcie w button wyślij, a następnie obrobienie danych i wrzucenie do bazy danych, czy nawet proste sortowanie filtrowanie danych z warstwy dostępu do danych.
Co to jest warstwa prezentacji?
To jest wszystko, co widzi użytkownik. Cały układ strony, wyświetlane dane, CSS, obrazki...
Jak to wygląda w praktyce?
Smarty jest specjalną klasą, której obiekt tworzymy w logice aplikacji. Przetwarzamy potrzebne dane i przed wyświetleniem szablonu przekazujemy do logiki prezentacji. Ustawiamy żądany szablon i za resztę odpowiedzialna jest osoba kreująca wizerunek do naszej strony. W praktyce (przykład: firma w której odbywam praktyki) za to odpowiedzialny też jest programista. Jedynie poprawki nanosi projektant, oprócz odwalenia całej roboty stworzenia szablonu i podstawowej integracji. Co dalej? Wywołując metodę display, na klasie Smarty, ustawiamy szablon, który wykorzystywany będzie do wyświetlenia naszej strony. Późniejszym zdaniem jest wyświetlić przekazane informacje. Więcej szczegółów zamieszczę niżej.
Smarty, a szybkość.
Smarty generuje z szablonów TPL, zwykły kod PHP i przetrzymuje go w pamięci cache. Przy kolejnych wywołaniach szablon, na który nie nanieśliśmy żadnych zmian brany jest z pamięci cache. Nie wpływa to znacznie na szybkość naszego kodu.
Smarty, a bezpieczeństwo.
Bezpieczeństwo. Kluczowa rzecz. Nie wiem, czy wcześniej udało mi się choć trochę naszkicować jak działa Smarty, warto tutaj wspomnieć, że nie ma on żadnego wpływu na pogorszenie bezpieczeństwa pisanej aplikacji. Ba, może nawet podnieść jej bezpieczeństwo zabraniając projektantom używać czystego PHP i nakazywać używanie wyłącznie wbudowanych funkcji, modyfikatorów i przesłanych przez programistę danych.
Jak stworzyć pierwszy szablon w Smartim?
Ponieważ nie będę zajmował się kwestią uruchomienia Smartiego, od tego jest dokumentacja, przejdę od razu do meritum sprawy - języka Smartiego! Języka opracowanego specjalnie dla projektantów możemy używać w szablonach z rozszerzeniem 'tpl'. Czym różni się tpl od szablonu w HTML'u? A tym, że oprócz html'a, CSS, JS, może zawierać też tagi języka Smarty. Pisząc dalej zajmę się właśnie zagłębieniem się w ten język.
1. Zmienne - zmienne w Smartim przeplatane są przez kod HTML. Od innych tagów odróżnią je użyte klamry { }. Przykładowo szablon może wyglądać tak:
<html> <body> Witaj świecie! Jestem {$imie}. </body> </html>
Zmienną w tym wypadku jest imię, które zostało przekazane przez programistę w postaci zmiennej typu string zawierające, np. imię zalogowanego użytkownika.
Zmienne można też przypisywać w szablonie. Służy do tego funkcja assign. Przykład użycia:
{assign var="imie" value="Paweł"} Witaj świecie! Jestem {$imie}.
2. Przekazywanie wartości do szablonów - w Smartim całą logikę tworzy się w plikach PHP. Jak przystało na dobry system oddzielenia logiki od treści mamy dostęp do określania, który szablon ma być dołączony pod aktualnie opracowywaną logikę. Smarty to także klasa, która ma zdefiniowane metody, służące do określania ustawień jak i przesyłania danych, oraz wyświetlania wyników (szablonów). Przykładowy kod napisany w języku PHP, do wyświetlenia przykładu z punktu pierwszego, może wyglądać tak:
<?php include("libs/Smarty.class.php"); $smarty = new smarty(); $imie = Paweł; $smarty->assign("imie", $imie); $smarty->display("przyklad.tpl"); ?>
3. Warunki logiczne - tak jak w innych znanych językach programowanie, w Smartim istnieją warunki logiczne. Zasięg instrukcji if oznaczony jest za pomocą nawiasów klamrowych. Używa się tutaj nawiasów {if}{/if}, dostępne są także {else} oraz {elseif}. Smarty obsługuje operatory warunkowe dostępne w języku PHP jak i też dodaje własne operatory np. operator sprawdzający podzielność - $a is [not] div by $b - w PHP $a % $b == 0.
[code]{if $student->ocena >= 4}Dobry uczeń!
{else}Uczeń średni
{/if}[/code]
4. Pętle - w Smartim można tworzyć pętle za pomocą dwóch znaczników. {section} - tablice nieasocjacyjne. {foreach} - tablice asocjacyjne. Dlaczego taki podział? Ponieważ tam gdzie można, należy używać pętli foreach - łatwiejsza. Oczywiście tablice asocjacyjne można przeglądać funkcją section, ponieważ w PHP (nie wiem czy w innych językach także) można do każdej tablicy odwołać się przez jej indeks. Przykład dwóch rodzajów pętli.
[code]{section name=id loop=$titles}Tytuł: {$titles[id]} Cena: {$price[id]}
{/section} {foreach item=book from=$books}Tytuł: {$book.title} Cena: {$book.price}
{/foreach}[/code]
W pierwszym wypadku użyliśmy dwóch tablic jednowymiarowych. Natomiast w drugim przykładzie użyliśmy tablicy asocjacyjnej o nazwie $books, która zawiera kolejne tablice asocjacyjne. Wygląda to mniej więcej tak:
books: ....book0: ........title = Matematyka Dyskretna ........price = 100.99 ....book1: ........title = Wprowadzenie do teorii grafów ........price = 28.00
Dopowiedzenie. Jak by jakiś czytelnik nie wiedział, tablica asocyacyjna jest to tablica, do której możemy odwołać się podając klucz złożony ze znaków. Do tablic nieasocjacyjnych odwołujemy się używając ich kolejnych indeksów zaczynając od indeksu równego zero.
Ponadto używając funkcji section mamy do dyspozycji zmienne, które mogą być przydatne w modyfikowaniu logiki pętli:
index - wyświetla bieżący indeks pętli. Przykład użycia: {section name=id loop=3} # indeksu: {$smarty.section.id.index} {section} first - zwraca true w trakcie pierwszej iteracji iteration - podobna do zmiennej index, ale zwraca numer bieżącej iteracji. Ważne, jeżeli pętla ma indeks startowy np. 15. total - zwraca całkowitą liczbę iteracji. JEDYNA ZMIENNA, KTÓRĄ MOŻNA UŻYĆ PO ZAKOŃCZENIU PĘTLI {section}. loop - zwraca numer ostatniego indeksu.
5. Przydatne funkcje - powiem tylko o części przydatniejszych funkcji. Jest ich naprawdę sporo. W kolejnych wpisach rozpiszę się na ich temat więcej.
5.1. Funkcja html_table - funkcja pobiera parametry w postaci tablicy oraz opcjonalnie liczby kolumn (domyślnie cols=3). Na przykładzie kalendarza:
{html_table loop=$dni_tygodnia cols=7}
5.2. Funkcja cycle - funkcja, która cyklicznie przetwarza zbiór wartości i zwraca je jedną po drugiej. Funkcja cycle może mieć nieskończenie wiele wartości. Przykład dla 2 kolorów:
<tr bgcolor='{cycle values="#EBEBEB, #ACABAB"}'>
5.3. Funkcja include - aby ułatwić nam edycje szablonu, trzeba rozłożyć go na komponenty. Po co pisać wielokrotnie stopkę, bądź menu? Edycja jednego elementu z takiego menu będzie trwała tyle razy dłużej, ile mamy stworzonych podstron. Funkcja include pozwala nam załączyć szablon we wskazanym miejscu. Przykład działania funkcji include. Plik stopka.tpl:
<div id="stopka">Wszystkie prawa zastrzeżone 2009-2020</div>
Plik index.tpl:
<html> <body> <div id="naglowek">Nagłówek</div> <div id="tresc">TREŚĆ TREŚĆ TREŚĆ</div> {include file="stopka.tpl"}
Warto pomyśleć, aby dobrać odpowiednią ilość komponentów. Nie za dużo, oraz nie za mało. W zależności od strony może to być od 4 do 6,10... 5.4. Funkcja literal - funkcja ta przydaje się, kiedy musimy dołączyć kod, w którym zawarliśmy niedozwolone znaki, źle interpretowane przez kompilator Smartiego. Przykładem może być kod JavaScript napisany między znacznikami script, a nie dołączony z oddzielnego pliku. W języku JavaScript do oznaczenia bloków (np. warunków logicznych, funkcji, pętli) używa się klamerek { }, co jest znakiem zastrzeżonym przez Smartiego. Dlatego taki blok najlepiej otoczyć funkcją literal, aby nie była interpretowana przez kompilator. Przykład:
{literal}<a href="#">Click</a>
5.5. Funkcje ldelim i rdelim - funkcje służą do wyświetlania lewej klamerki - {ldelim} { oraz prawej klamerki - {rdelim} }. Dzięki czemu, szablon zostanie prawidłowo prze kompilowany, a wyświetleniem prawidłowych klamerek zajmie się Smarty, dzięki czemu możliwa będzie wewnętrzna (w szablonie) obsługa CSS i JavaScript.
Smarty, używać czy nie?
Nie ma co się zastanawiać! Oczywiście teraz po przeczytaniu takiej porcji informacji możesz nawet w czystym PHP spróbować oddzielić logikę prezentacji od aplikacji. Spójrzcie jak jeszcze miesiąc temu pisałem wyświetlanie prostej listy użytkowników. Widać, że źle nie jest (a może niektórzy powiedzą - tragicznie jest!), ale projekty trzeba pisać tak, aby umożliwić ich łatwiejszą edycję później, dużo później - kiedy już zapomnimy jak działał nasz skomplikowany kod przepleciony fragmentami HTML'a.
$listOfContacts = PrepareContacts::getContactsFT($page); /* Sprawdzam czy zapytanie nie zwróciło false */ if ($listOfContacts !== false) foreach ($listOfContacts as $record) { echo '<div class="wiersz">'; echo '<div class="kolumna"><a rel="nofollow" href="?contact,' . $record->getContactID() . '">' . Config::cutSting($record->getName(), 20) . '</a></div> <div class="kolumna">' . Config::cutSting($record->getSName(), 20) . '</div> <div class="kolumna">' . Config::cutSting($record->getEmail(), 20) . '</div> <div class="kolumna">' . Config::cutSting($record->getPhoneNumer() , 20). '</div> <div class="kolumna">' . Config::cutSting($record->getDate(), 20) . '</div>'; echo '</div>'; }
Moim zdaniem nie wygląda to źle. Ale jak by tak wszyscy pisali, to nie było by problemu, a jednak kody różnych ludzi wyglądają różnie. Smarty to standaryzuje. Po otrzymaniu od programisty tablicy z użytkownikami kod wyglądał by np tak (piszę z palca, przepraszam za wszelkie błędy), przy założeniu otrzymania tablicy obiektów User.
{if $users} {foreach item=user from=$users} <div class="wiersz"> <div class="kolumna"><a rel="nofollow" href="?contact,{$user->getId()}">{$user->getName()|truncate:20:"...":true}</a></div> <div class="kolumna">{$user->getSName()|truncate:20:"...":true}</div> <div class="kolumna">{$user->getEmail()|truncate:20:"...":true}</div> <div class="kolumna">{$user->getPhoneNumer()|truncate:20:"...":true}</div> <div class="kolumna">{$user->getDate()|date_format:"%d %b, %Y"}</div> </div> {/foreach} {else} <div class="wiersz">W bazie nie ma żadnych użytkowników</div> {/if}
To wszystko przepisałem w 4 minuty, zachowując ładne, czytelne formatowanie i prostotę. A przy tym nie napisałem żadnej funkcji (jak np. we wcześniejszym kodzie, obcinanie stringa).
Ponieważ, nie widać wcięć na blogu zamieściłem powyższy kod, wraz z wcięciami, na innej stronie http://ctrlv.it/id/MjQxNTk5.
Podsumowanie.
Wpis jak na mój pierwszy dość obszerny. Pewnie dało by się go napisać ładniej i zgrabniej. Wybaczcie. Mam tylko nadzieje, że ten wpis się komuś przyda. Jeżeli zajdzie taka potrzeba napiszę od postaw jak dodać Smartiego do projektu. Następnych części można spodziewać się w odstępie tygodnia, w których poruszę inne ważne funkcje, metody klasy Smarty, używanie modyfikatorów (użyłem do formatowania daty, ucięcia tekstu) i innych zagadnień związanych ze Smartim. Miejmy nadzieje, że nie utracę weny i podam bardziej praktycznie przykłady zastosowania, bo nie można uczyć się na sucho.
Mile widziane wszelkie poprawki! Przede wszystkim literówki utrudniające czytanie, jak i błędy które wkradły się do kodów i niedomówienia.