GAJDAW


PROGRAMY





MVC w praktyce. Katalog Open Clip Art

Włodzimierz Gajda

Open Clip Art to kolekcja kilku tysięcy wektorowych rysunków wykonanych w programie Inkscape. Całość, choć podzielona na kategorie i otagowana, jest niewygodna w użyciu. Artykuł opisuje, w jaki sposób wykonać katalog, który ułatwi poruszanie się po kliparcie. Jest to doskonałe ćwiczenie w przygotowywaniu witryn internetowych w PHP/MySQL korzystając z wzorca architektonicznego MVC.

1. Dane zawarte w Open Clip Art

Open Clip Art jest dostępny do pobrania w formie spakowanego archiwum openclipart-0.18-full.zip. Każdy rysunek zawarty w kliparcie jest dostępny w dwóch formatach: SVG oraz PNG. Dodatkowy plik tekstowy zawiera informacje o autorze rysunku oraz słowa kluczowe. Nazwy trzech plików .svg, .png i .txt pojedynczego rysunku są identyczne.

Na przykład trzy pliki zawarte w folderze animals\birds:

dotyczą tego samego rysunku orła. Pierwszy z nich, eagle_01.svg, to rysunek wektorowy, który możemy otworzyć w Inkscape, co zostało przedstawione na rysunku 1. Drugi, eagle_01.png, to miniaturka o wymiarach 80x113, którą możemy otworzyć w przeglądarce WWW (patrz rysunek 2). Trzeci plik, eagle_01.txt zawiera następujące informacje:

...brak dostępu...

Na podstawie słów kluczowych zawartych w pliku tekstowym rysunki katalogu możemy poklasyfikować.

Rysunek 1. Plik eagle_01.svg otworzony w Inkscape

Rysunek 2. Plik eagle_01.png otworzony w Firefoksie

Klipart w wersji openclipart-0.18-full.zip zawiera drobne błędy. W kilku przypadkach brakuje plików tekstowych oraz plików .png. Odczyt plików .txt oraz .png należy poprzedzić sprawdzaniem, czy plik o podanej nazwie istnieje.

2. Funkcjonalność katalogu

Wykonanie katalogu rozpoczynamy od ustalenia funkcjonalności. Zasadniczym zadaniem jest otagowanie wszystkich rysunków i umożliwienie łatwego wybierania wszystkich obrazów związanych z wybranym tagiem. Wyświetlanie tagów zawartych w katalogu realizujemy na trzy sposoby:

Ponieważ Open Clip Art zawiera kilka tysięcy różnych słów kluczowych, więc chmurę z rysunku 3 ograniczamy do tagów, które dotyczą ponad dwudziestu rysunków. W przeciwnym razie chmura ta będzie ogromna i przez to nieczytelna.

Rysunek 3. Chmura najważniejszych tagów

Rysunek 4. Lista wszystkich tagów posortowana alfabetycznie

Rysunek 5. Lista wszystkich tagów posortowana malejąco wg liczby obrazów

Tagi występujące na rysunkach 3, 4 oraz 5 są oczywiście odsyłaczami. Umożliwiają one przejście do strony prezentującej wszystkie rysunki związane z wybranym tagiem. Rysunki te przedstawiamy w postaci regularnej tabelki miniatur widocznej na rysunkach 6 oraz 7 (nazwa tagu jest zawarta w tytule strony). Ponieważ jednak niektóre tagi są powiązane z ogromną ilością obrazów (np. słowo kluczowe computer dotyczy 2184 rysunków!), zatem tabele miniatur musimy poddać stronicowaniu. Rysunki 6 oraz 7 przedstawiają pierwszą i czwartą stronę miniatur dla tagu icon. Poruszanie po stronach ułatwiają numeracja oraz strzałki zaznaczone na rysunku 7.

Rysunek 6. Tabelka miniatur dla słowa kluczowego icon — pierwsza strona wyników

Rysunek 7. Tabelka miniatur dla słowa kluczowego icon — czwarta strona wyników

Po wybraniu dowolnej miniatury przechodzimy do przedstawionej na rysunku 8 strony ze szczegółowymi danymi. Zawiera ona wszystkie tagi rysunku oraz oryginalną miniaturę .png, po kliknięciu której ujrzymy plik SVG (rysunek 9).

Rysunek 8. Szczegółowe dane wybranego obrazu

Rysunek 9. Kliknięcie miniaturki z rysunku 8 prowadzi do oryginalnego obrazu SVG

3. Baza danych

Baza danych katalogu będzie zawierała dwie tabele: img oraz tag. Pierwsza z nich jest przeznaczona na obrazy, a druga na słowa kluczowe. Po połączeniu tabel img oraz tag relacją wiele do wielu w bazie danych pojawi się trzecia tabela o nazwie img_has_tag.

Po przygotowaniu modelu widocznego na rysunku 10, generujemy klasy dostępu do bazy danych. Wykorzystujemy do tego skrypt dbframe, który ułatwia połączenie aplikacji DBDesigner i Propel.

Rysunek 10. Struktura bazy danych

3.1 Wyzwalacze

Zliczanie liczby obrazów związanych z wybranym słowem kluczowym wykonujemy wykorzystując wyzwalacze (ang. triggers). Po wstawieniu rekordu do tabeli img_has_tag, czyli w obsłudze zdarzenia AFTER INSERT, zwiększamy liczbę obrazów związanych z tagiem. Kompletny kod wyzwalacza jest przedstawiony na listingu 1. Dzięki wyzwalaczowi, to serwer bazy danych odpowiada za aktualizację pola imgs_count w tabeli tag.

...brak dostępu...

Listing 1. Wyzwalacz zliczający liczbę obrazów związanych ze słowem kluczowym

3.2 Wypełnianie bazy danych

Wypełnianie bazy danych rozpoczynamy od ustalenia nazw wszystkich plików w formacie .svg. Skrypt get-file-list.php rekurencyjne przeszukuje folder z klipartem openclipart-0.18-full/clipart/ i ustala nazwy wszystkich plików o rozszerzeniu SVG. Pełna lista znalezionych plików jest zapisana do pliku files.txt.

Następnie otrzymana lista plików jest w skrypcie wstaw.php przetwarzana element po elemencie. Dla każdego elementu:

Do tabeli img wstawiamy obraz z odpowiednio ustalonymi ścieżkami, zaś do tabeli tag wstawiamy wszystkie słowa kluczowe obrazu. Wstawione słowa łączymy relacją n:m ze wstawionym obrazem.

Zrzut wypełnionej bazy danych, zawierający 42 227 rekordów, jest zapisany w pliku 1-dbframe/input/clipart.sql. Wypełnioną bazę danych możesz odtworzyć wykonując skrypt create-db-filled.bat.

4. Miniaturki

Ostatnim etapem przygotowywania danych do katalogu jest przeskalowanie miniatur. Oryginalne miniatury mają maksymalną szerokość 80 pikseli. Wysokość miniatur jest dobrana proporcjonalnie do szerokości. W przypadku niektórych obrazów wysokość może wynieść nawet kilkaset pikseli. Przygotowanie regularnej tabeli miniatur z tak różnorodnych obrazów byłoby trudne. Dlatego łatwiej najpierw przygotować takie miniaturki, z których każda mieści się w kwadracie o wymiarach 80x80 pikseli.

Tworząc miniatury możemy już wykorzystać zawartość bazy danych. Pobieramy z bazy danych wszystkie obrazy (wywołanie ImgPeer::doSelect()), po czym w pętli dla każdego obrazu sprawdzamy istnienie oryginalnej miniaturki w formacie .png. Jeśli miniaturka istnieje, to w zmiennej $nowa_nazwa ustalamy nazwę dla nowego pliku .png, plik skalujemy (wywołując funkcję skaluj_obraz_xy()). Na koniec przeskalowany obraz zapisujemy do pliku (wywołanie imagepng()). Kompletny skrypt tworz-miniaturki.php jest przedstawiony na listingu 2.

...brak dostępu...

Listing 2. Skrypt generujący miniaturki mieszczące się w kwadracie o wymiarach 80x80 pikseli

Wymiary generowanych miniatur są ustalone w stałych SZEROKOSC oraz WYSOKOSC. Po zmianie podanych wartości należy odpowiednio dostosować aplikację podając liczbę obrazów na stronie. Służy do tego stała ILE_NA_STRONIE w skrypcie index.php. Ponadto w stylach CSS należy podać nową szerokość elementu div przeznaczonego na miniaturkę:

...brak dostępu...

Dodatkowym parametrem aplikacji jest liczba kolumn na stronach z rysunków 4 oraz 5. Jest ona ustalana stałą LICZBA_KOLUMN. Po zmienieniu wartości stałej należy jeszcze zmodyfikować szerokośc odpowiedniego pojemnika w CSS:

...brak dostępu...

5. Aplikacja

Po przygotowaniu kompletu danych przystępujemy do oprogramowania aplikacji. Stosujemy wzorzec architektoniczny MVC (ang. Model View Kontroller), w którym:

5.1 Akcje i ich adresy URL

Analizując katalog od strony funkcjonalnej (rysunki 3, 4, 5, 6, 8), wyznaczamy następujące różne podstrony serwisu:

Podstronom tym przyporządkowujemy numery akcji oraz adresy URL zgodnie z zawartością tabeli 1.

Podstrona Akcja Adres URL Przykład
chmura tagów 2 index.html -
lista alfabetyczna tagów 3 lista-a.html -
lista ilościowa tagów 4 lista-i.html -
pojedyncza strona z tabelą miniatur 5 tag/<nazwa tagu>/p<numer strony>.html tag/animal/p4.html
szczegółowe dane obrazu 6 img/<numer obrazu>.html img/1234.html

Tabela 1. Podstrony, ich akcje i adresy URL

5.2 Model

Baza danych zawiera trzy tabele: img, tag oraz img_has_tag o polach:

...brak dostępu...

Propel wygeneruje więc sześć klas: Img, ImgPeer, Tag, TagPeer, ImgHasTag, ImgHasTagPeer. Klasy Img, Tag, ImgHasTag będą miały metody setX() oraz getX(), zapewniające dostęp do wszystkich pól (np. getImgId(), getPngUrl(), setImgId(), setPngUrl()) zaś klasy Peer pozwolą na wyszukiwanie rekordów. Na przykład wyszukanie wszystkich tagów wykonamy wywołując:

...brak dostępu...

zaś podczas skalowania, przedstawionego na listingu 2, wykorzystaliśmy klasę ImgPeer:

...brak dostępu...

5.3 Kontroler

Kontroler aplikacji jest podzielony na następujące cztery fazy:

Stosujemy przyjazne URL implementowane przy użyciu modułu mod_rewrite. Dyrektywa:

...brak dostępu...

zawarta w pliku .htaccess przekierowuje wszystkie żądania dotyczące plików HTML do skryptu index.php. W pierwszej fazie kontrolera wykorzystując $_SERVER['REQUEST_URI'] w zmiennej $adr ustalamy nazwę pliku, którego dotyczy żądanie.

Następnie, w etapie drugim, przeprowadzamy walidację danych. Tworzymy zmienną $akcja, której wartości będą zgodne z zawartością tabeli 1. Dodatkowa wartość $akcja == 1 służy do obsługi błędów 404. Jest to wartość domyślna.

Po przeprowadzeniu walidacji przechodzimy do etapu trzeciego: wypełnienia szablonu danymi. Instrukcja switch wybiera odpowiedni przypadek na podstawie wartości $akcja. W konkretnym przypadku pobieramy odpowiednie dane z bazy i przekazujemy — jako obiekty Propel-a — do szablonu.

Ostatni etap pracy kontrolera to wyświetlenie szablonu. Zadanie to sprowadza się do wywołania metody display().

Zarys kontrolera jest przedstawiony na listingu 3.

...brak dostępu...

Listing 3. Zarys kontrolera aplikacji

5.4 Chmura tagów

Chmura tagów widoczna na rysunku 3 jest wykonana z wykorzystaniem biblioteki PEAR. Klasa HTML_TagCloud automatyzuje proces tworzenia chmury.

Najpierw pobieramy z bazy danych wszystkie tagi, które dotyczą co najmniej 7 obrazów:

...brak dostępu...

Następnie tworzymy obiekt klasy MyTagsCustomCssTagCloud:

...brak dostępu...

Elementy chmury dodajemy do obiektu $tags_cloud metodą addElement():

...brak dostępu...

Parametrami metody addElement() są:

Gotowa chmura, zwracana przez metody buildHTML() i oraz buildCSS() jest przekazywana do szablonu:

...brak dostępu...

5.5 Widok

Widok aplikacji składa się z szablonów Smarty. Układ witryny jest zawarty w pliku index.tpl przedstawionym w zarysie na listingu 4. W zależności od wartości zmiennej $akcja, wewnątrz kontenera div#wrapper dołączamy inny plik .tpl.

...brak dostępu...

Listing 4. Zarys widoku aplikacji

6. Katalog Offline

Jeśli katalog ma być udostępniany — podobnie jak oryginał openclipart-0.18-full.zip — w postaci spakowanego archiwum plików, to hiperłącza muszą stosować adresy względne, np.:

...brak dostępu...

Ponieważ niektóre z przyjaznych URL-i zawierają znak /:

...brak dostępu...

zatem do tego samego pliku o adresie tag/animals/p34.html będziemy stosowali hiperłącza:

...brak dostępu...

Zmienna $folder widoczna na listingu 4 zawiera prefiks, który należy dodać do hiperłączy. Czasami przedrostek ten jest pusty (np. na stronie index.html), czasami zawiera pojedyncze wyjście w górę ../ (np. na stronie img/1234.html), a czasami — podwójne ../../ (np. na stronie tag/animal/p34.html).

W celu uruchomienia skryptu index.php należy:

  • w folderze 4-app/ umieścić wypakowany klipart openclipart-0.18-full.zip,
  • skryptem 1-dbframe/create-db-filled.bat utworzyć wypełnioną bazę danych,
  • skryptem 3-tworz-miniaturki/tworz-miniaturki.php utworzyć miniaturki obrazów.
lp. Przykład
1. Katalog Open Clip Art — skrypty PHP
2. Katalog Open Clip Art — wersja offline

Tabela 2. Przykłady do pobrania