Propel, najbardziej popularny system mapowania relacyjno-obiektowego w PHP skraca cykl produkcyjny aplikacji internetowej. Poradnik zawiera zestawienie popularnych problemów, z którymi borykają się początkujący użytkownicy Propela.
Porada 1: Kodowanie znaków
Klasy dostępu do bazy danych mogą stosować dowolne kodowanie znaków, niezależne od ustawień serwera.
Przed uruchomieniem generatora Propel (tj. skryptu propel-gen.bat) w pliku {stala}runtime-conf.xml{/stala} ustalamy kodowanie znaków dla połączenia:
1 2 3 4 |
<connection> ... <encoding>utf8</encoding> </connection> |
Wygenerowane klasy będą stosowały podane kodowanie. Rozwiązanie takie ma dwie zalety:
- skrypty PHP korzystające z klas nie muszą zawierać żadnych instrukcji ustalających kodowanie,
- konfiguracja kodowania znaków serwera MySQL nie ma wpływu na skrypty.
Porada 2: Korzystanie z kilku baz danych
Skrypt PHP może – za pośrednictwem propelowych obiektów – łączyć się z wieloma bazami danych.
Przed uruchomieniem generatora klas (tj. skryptu {stala}propel-gen.bat{/stala}) w pliku {stala}runtime-conf.xml{/stala} należy wymienić wszystkie bazy danych:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<datasources default=\"osoby\"> <datasource id=\"osoby\"> <adapter>mysql</adapter> <connection> <phptype>mysqli</phptype> <hostspec>localhost</hostspec> <database>osoby</database> <username>osobyadm</username> <password>osobypass</password> <encoding>utf8</encoding> </connection> </datasource> <datasource id=\"wyrazy\"> <adapter>mysql</adapter> <connection> <phptype>mysqli</phptype> <hostspec>localhost</hostspec> <database>wyrazy</database> <username>wyrazyadm</username> <password>wyrazypass</password> <encoding>utf8</encoding> </connection> </datasource> </datasources> |
Dla każdego wymienionego źródła danych przygotowujemy osobny plik XML z opisem struktury bazy, np.:
1 2 |
wyrazy-schema.xml osoby-schema.xml |
Tak skonfigurowany Propel wygeneruje plik konfiguracyjny -conf.php, umożliwiający łączenie się obiektów z wieloma bazami, przy czym wszystkie wygenerowane klasy trafią do jednego folderu.
Jeśli w pliku build.properties dodamy wpis:
1 |
propel.packageObjectModel = true |
zaś w plikach {stala}-schema.xml{/stala} umieścimy atrybut package:
1 |
<database package=\"wyrazy\" name=\"wyrazy\" ... > |
to generowane klasy zostaną umieszczone w osobnych folderach. Atrybut package zawierający kropkę:
1 |
package=\"core.system\" |
spowoduje dalszy podział generowanych folderów na podfoldery:
1 |
core/system |
W skrypcie PHP, który korzysta z kilku połączeń, należy najpierw wywołać metodę init():
1 |
Propel::init(\'dwiebazy-conf.php\'); |
a następnie utworzyć zmienne umożliwiające korzystanie z połączeń:
1 2 |
$con_osoby = Propel::getConnection(\'osoby\'); $con_wyrazy = Propel::getConnection(\'wyrazy\'); |
Metody pobierające rekordy z baz danych otrzymają dodatkowy parametr ustalający połączenie:
1 2 |
$wyrazy = WyrazPeer::doSelect(new Criteria, $con_wyrazy); $osoby = OsobaPeer::doSelect(new Criteria, $con_osoby); |
Pierwsza z powyższych instrukcji pobiera dane z bazy o nazwie wyrazy, a druga z bazy o nazwie osoby.
Utworzone obiekty nie wymagają podawania połączenia. Korzystamy z nich identycznie jak w skryptach, które stosowały jedną bazę danych:
1 2 3 4 |
echo $osoby[0]->getImie() $wyrazy[0]->setWyraz(\'Lorem\'); $wyrazy[0]->save(); |
Porada 3: Pobieranie tylko wybranych kolumn
Obiekty tworzone na podstawie informacji zapisanych w bazie, np. metodą retrieveByPK(), pobierają z bazy danych wszystkie kolumny. To prowadzi do dużych nieoptymalności. Wyświetlenie tytułów artykułów (np. lista nowości na stronie) będzie powodowało pobieranie kompletnych artykułów.
Kolumny, które powinny być pobrane, możemy wskazać korzystając z metod klasy Criteria(). Utworzona poniżej tablica $studenci zawiera tylko imiona studentów:
1 2 3 4 5 6 7 8 9 10 11 |
$c = new Criteria(); $c->clearSelectColumns(); $c->addSelectColumn(StudentPeer::IMIE); $rs = StudentPeer::doSelectRS($c); $studenci = array(); while($rs->next()) { $tmp = array( \'imie\' => $rs->get(1), ); $studenci[] = $tmp; } |
Tak wygenerowane wyniki nie mogą być wykorzystane do operacji save() czy delete(), gdyż nie są obiektami, a stringami.
Kod zwracający wyłącznie wybrane kolumny dodajemy w postaci nowych metod do wygenerowanych klas. Metody te mogą przyjmować parametr klasy Criteria, który pozwoli na wskazywanie wybranych wierszy i sortowanie wyników.
Porada 4: Konwersje toArray(), fromArray()
Metody {stala}toArray(){/stala} oraz {stala}fromArray(){/stala} pozwalają na konwersje obiektów w tablice i na odwrót.
W celu przekształcenia obiektu {stala}$student{/stala}:
1 2 3 4 5 6 7 8 9 10 11 |
$c = new Criteria(); $c->clearSelectColumns(); $c->addSelectColumn(StudentPeer::IMIE); $rs = StudentPeer::doSelectRS($c); $studenci = array(); while($rs->next()) { $tmp = array( \'imie\' => $rs->get(1), ); $studenci[] = $tmp; } |
w tablicę wywołujemy metodę {stala}toArray(){/stala}:
1 |
$student = StudentPeer::retrieveByPK(2); |
Jeśli jako parametr podamy stałą TYPE_FIELDNAME, to indeksami tablicy będą nazwy kolumn w bazie danych.
Konwersję odwrotną realizuje metoda {stala}fromArray(){/stala}:
1 |
$t = $student->toArray(BasePeer::TYPE_FIELDNAME); |
Metody {stala}toArray(){/stala} oraz {stala}fromArray(){/stala} możemy wykorzystać w połączeniu z klasami XML_Serializer oraz XML_Unserializer. W ten sposób możemy:
- obiekty konwertować do XML-a ({stala}toArray(){/stala}, {stala}XML_Serializer{/stala}),
- na podstawie danych XML tworzyć obiekty ({stala}XML_Unserializer, fromArray(){/stala}).
Oto, jak przebiega konwersja obiektu na XML:
1 2 3 4 5 6 7 8 9 10 11 12 |
$t = array( \'imie\' => \'Tomasz\', \'nazwisko\' => \'Nijaki\', \'plec\' => \'M\', \'wiek\' => \'33\', \'numerindeksu\' => \'00000000001\', \'kierunek\' => \'marketing\', ); $s2 = new Student(); $s2->fromArray($t, BasePeer::TYPE_FIELDNAME); $s2->save(); |
Porada 5: Wydawanie zapytań SQL
Niekiedy zachodzi konieczność wykonania konkretnych zapytań SQL. W takiej sytuacji należy wykorzystać statyczną metodę {stala}getConnection(){/stala}. Obiekt zwracany przez tę metodę pozwala na wysyłanie do serwera bazy danych zapytań w języku SQL:
1 2 3 4 5 6 7 |
$s = StudentPeer::retrieveByPK(1); $t = $s->toArray(BasePeer::TYPE_FIELDNAME); $serializer = new XML_Serializer(); $serializer->serialize($t); $wynik = $serializer->getSerializedData(); |
Podane wyżej zapytanie wyznacza wartość towaru zapisanego w bazie danych (tj. sumę iloczynów: liczba sztuk x cena jednostki). Wykonanie takiego zadania za pośrednictwem obiektów byłoby znacznie bardziej czasochłonne.
Porada 6: Zliczanie
Szczególnym przypadkiem zapytań SQL jest zliczanie rekordów. W tym celu nie musimy jednak uciekać się do języka SQL, gdyż generowane klasy zawierają metody {stala}doCount(){/stala}.
Oto w jaki sposób możemy wyznaczyć liczbę wierszy zapisanych w bazie danych:
1 2 3 4 5 |
$con = Propel::getConnection(\'produkty\'); $sql = \'SELECT SUM(ilosc * cena) as wartosc FROM produkt\'; $rs = $con->executeQuery($sql); $rs->next(); $wartosc = $rs->getString(\'wartosc\'); |
Porada 7: Stronicowanie
Stronicowanie wyników wykonujemy stosując metody {stala}setLimit(){/stala} oraz {stala}setOffset(){/stala} klasy {stala}Criteria{/stala}. Pierwsza z nich ustala liczbę zwracanych rekordów, a druga numer pierwszego zwracanego rekordu:
1 |
$ile = WyrazPeer::doCount(new Criteria); |
Porada 8: Pobieranie jednego rekordu
Gdy zachodzi konieczność pobrania dokładnie jednego rekordu, przydatna okazuje się metoda {stala}doSelectOne(){/stala}. Z sytuacją taką mamy do czynienia np. wtedy, gdy chcemy do bazy danych wstawić rekord, pod warunkiem że takiego rekordu nie było. Należy najpierw wyszukać rekord, a następnie użyć instrukcji if do zbadania, czy obiekt został utworzony:
1 2 3 4 5 |
$c = new Criteria(); $c->setLimit(4); $c->setOffset(2); $wyrazy = WyrazPeer::doSelect($c); |
Porada 9: Relacja 1:n, metoda getXs()
Obiekty połączone relacją 1:n są wyposażone w metody o nazwie {stala}getXs(){/stala}, udostępniające dane stojące w relacji. Litera X w nazwie metody jest zastępowana nazwą odpowiedniej tabeli.
Jeśli tabele poeta i wiersz połączymy relacją 1:n (każdy poeta może być autorem wielu wierszy), to w klasie Poeta pojawi się metoda getWierszs(). Metoda ta będzie zwracała obiekty klasy Wiersz:
Oto skrypt drukujący tytuły wierszy poety o identyfikatorze 3:
1 2 3 4 5 6 7 8 9 |
$c = new Criteria(); $c->add(WyrazPeer::WYRAZ, \'lorem\'); $wyraz = WyrazPeer::doSelectOne($c); if (!$wyraz) { $wyraz = new Wyraz(); $wyraz->setWyraz($n); $wyraz->save(); } |
Metoda {stala}getWierszs(){/stala} zwraca wyniki nieuporządkowane. Możemy ją nadpisać w klasie Poeta, by w rezultacie otrzymać metodę {stala}getWierszs(){/stala}, która zachowując pełną funkcjonalność, domyślnie zwraca wyniki posortowane alfabetycznie.
W drugą stronę korzystamy z odwołań kaskadowych. Oto jak ustalić nazwisko poety, który napisał wiersz o identyfikatorze 7:
1 2 3 4 |
$poeta = PoetaPeer::retrieveByPK(3); foreach ($poeta->getWierszs() as $wiersz) { echo $wiersz->getTytul(); } |
Porada 10: Relacja n:m, metody {stala}getXHasYsJoinX(){/stala}, {stala}getXHasYsJoinY(){/stala}
Obiekty stojące w relacji n:m również mają metody dostępu do skorelowanych danych. Metody te nazywają się zgodnie ze schematem
1 2 |
$wiersz = WierszPeer::retrieveByPK(7); echo $wiersz->getPoeta()->getImie(); |
gdzie Z jest nazwą tabeli haszującej, a X oraz Y nazwami tabel połączonych relacją.
Jeśli tabele film oraz aktor połączymy relacją n:m i tabelę haszującą nazwiemy film_has_aktor, to Propel wygeneruje trzy klasy: Film, Aktor oraz FilmHasAktor. W klasie Film znajdziemy metodę {stala}getFilmHasAktorsJoinAktor(){/stala}, a w klasie Aktor metodę {stala}getFilmHasAktorsJoinFilm(){/stala}. Metody te będą zwracały obiekty tabeli {stala}film_has_aktor{/stala}.
Oto skrypt drukujący tytuły wszystkich filmów, w których wystąpił aktor o identyfikatorze 1:
1 2 |
getZsJoinX() (w klasie X) getZsJoinY() (w klasie Y) |
oraz skrypt, który drukuje nazwiska wszystkich aktorów grających w filmie o identyfikatorze 7:
1 2 3 4 5 6 |
$aktor = AktorPeer::retrieveByPK(1); $c = new Criteria(); $objs = $aktor->getFilmHasAktorsJoinFilm($c); foreach ($objs as $obj) { echo $obj->getFilm()->getTytul(); } |
Korzystając z powyższych metod możemy opracować własne metody, których wyniki będą obiektami klas Film lub Aktor. Przykładem takiej metody jest {stala}getFilmsByAktor(){/stala} (nowa metoda, ręcznie dodana w klasie Aktor), której użycie upraszcza kod skryptu:
1 2 3 4 5 6 |
$film = FilmPeer::retrieveByPK(7); $c = new Criteria(); $objs = $film->getFilmHasAktorsJoinAktor($c); foreach ($objs as $obj) { echo $obj->getAktor()->getNazwisko(); } |
Porada 14: Wyjątki
W przypadku wystąpienia błędów obiekty Propela generują wyjątki. Obsługę wyjątków realizujemy instrukcją try-catch. Metody obiektu {stala}$e{/stala} pozwalają poznać przyczynę błędu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Poeta extends BasePoeta { function __toString() { return $this->getImie() . \' \' . $this->getNazwisko(); } } class Wiersz extends BaseWiersz { function __toString() { return $this->getTytul(); } } |
Porada 15: Sortowanie wyników wg wybranych kolumn
Porządek sortowania zwracanych wyników ustalamy korzystając z metod {stala}addAscendingOrderByColumn(){/stala} oraz {stala}addDescentingOrderByColumn(){/stala} klasy {stala}Criteria{/stala}:
1 2 3 4 5 6 7 8 9 10 |
$wyraz = new Wyraz(); $wyraz->setWyraz(\'żółw\'); try { $wyraz->save(); } catch (PropelException $e) { $c = $e->getCause(); $komunikat = $c->getNativeError(); echo $komunikat; } |
Uruchamianie przykładowych rozwiązań
Każda z powyższych porad jest zilustrowana przykładami. Pojedynczy przykład składa się ze skryptów tworzących bazę danych (pliki .sql i .bat) oraz ze skryptów PHP, które pobierają dane z bazy i prezentują je w postaci strony WWW.
Uruchamianie każdego przykładu należy rozpocząć od utworzenia bazy danych. Następnie przeglądarką odwiedzamy stronę skrypt.php.
Oto jak przebiega uruchomienie przykładu jedenastego. W folderze 11-cala-zawartosc/ znajdziemy dwa podfoldery, a w nich pliki:
1 2 3 4 |
$c = new Criteria(); $c->addAscendingOrderByColumn(StudentPeer::NAZWISKO); $c->addDescendingOrderByColumn(StudentPeer::IMIE); $studenci = StudentPeer::doSelect($c); |
W pliku {stala}tworz-baze-cpp.bat{/stala}, w miejsce napisu AX1BY2CZ3 umieszczamy hasło administratora serwera MySQL:
1 2 3 4 5 6 |
11-cala-zawartosc/
1-zrzut-db/
tworz-baze-cpp.bat
baza-cpp.sql
2-skrypt/
skrypt.php
|
Następnie uruchamiamy skrypt tworz-baze-cpp.bat, po czym – wykorzystując aplikację phpMyAdmin – sprawdzamy, czy baza danych o nazwie cpp została utworzona. Nazwę tworzonej bazy danych znajdziemy w skrypcie {stala}baza-cpp.sql{/stala}:
1 |
c:\mysql\bin\mysql -uroot -pAX1BY2CZ3 < baza-cpp.sql |
Porada 11: Pobieranie zawartości całej bazy danych
Ciekawym efektem ubocznym metod {stala}getXs(){/stala} oraz {stala}getZsJoinX(){/stala} jest to, że jeden obiekt może dać dostęp do całej bazy danych.
Umieścimy w bazie danych książki podzielone na rozdziały, które z kolei zawierają zadania. Otrzymamy trzy tabele: ksiazka, rozdzial oraz zadanie. Relacją 1:n łączymy tabele ksiazka i rozdzial (każda książka zawiera wiele rozdziałów) oraz rozdzial i zadanie (każdy rozdział zawiera wiele zadań). Propel wygeneruje klasy:
1 2 3 4 5 6 |
$aktor = AktorPeer::retrieveByPK(1); $c = new Criteria; $filmy = $aktor->getFilmsByAktor($c); foreach ($filmy as $film) { ... } |
Jeśli w bazie danych znajduje się jeden rekord o identyfikatorze 1 (zbiór zadań z programowania w języku C++), to wywołanie:
1 2 3 4 5 6 7 |
Ksiazka metoda getRozdzials() Rozdzial metoda getZadanies() Zadanie |
zapewni dostęp do całej bazy danych. Podwójna pętla foreach wydrukuje całą książkę (wszystkie rozdziały i wszystkie zadania):
1 |
$ksiazka = KsiazkaPeer::retrieveByPK(1); |
Porada 12: Smarty i wielokrotne wywoływanie metod
Szablony Smarty domyślnie nie pozwalają na wielokrotne wywoływanie metod. Instrukcje szablonu:
PRZYKŁAD NIEPOPRAWNY
1 2 3 4 5 6 |
foreach ($ksiazka->getRozdzials() as $rozdzial) { echo $rozdzial->getTytul(); foreach ($rozdzial->getZadanies() as $zadanie) { echo $zadanie->getTekst(); } } |
będą powodowały błąd. W celu ominięcia tego problemu możemy zmodyfikować klasę Smarty_Compiler. Jeśli w pliku {stala}Smarty_Compiler.class.php{/stala} wymienimy wyrażenie regularne zawarte w linijce 155 i w miejsce:
1 |
{$wiersz->getAutor()->getImie()} |
wpiszemy:
1 |
...$this->_dvar_guts_regexp . \')\'; |
wielokrotne wywołanie metod będzie działało poprawnie.
Powyższa niedogodność sytemu Smarty jest na tyle dokuczliwa, że rozsądnym wydaje się rezygnacja z szablonów Smarty na rzecz surowych szablonów PHP.
Porada 13: Metoda __toString()
Metoda {stala}__toString(){/stala} służy do konwersji obiektu na typ string. Jeśli wygenerowane klasy wzbogacimy o metody {stala}__toString(){/stala}, to będzie można stosować obiekty jako parametry instrukcji echo, np.:
1 |
..$this->_dvar_guts_regexp . \'(?:\(\))?)\'; |
Instrukcje:
1 2 3 4 5 |
$poeta = PoetaPeer::retrieveByPK(3); echo $poeta; foreach ($poeta->getWierszs() as $wiersz) { echo $wiersz; } |
będą działały poprawnie, pod warunkiem że w klasach Poeta oraz Wiersz dodamy metody {stala}__toString(){/stala}:
1 2 |
echo $poeta; echo $wiersz; |
Może Cię zainteresować:




