1   2   3   4   5   6   7   8  ...

Propel. Porady

Propel, najbardziej popularny system mapowania relacyjno-obiektowego w PHP, w sposób bardzo istotny skraca cykl produkcyjny aplikacji internetowej. Poradnik zawiera zestawienie popularnych problemów, z jakimi borykają się początkujący użytkownicy Propel-a.

07/01/08

1. 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 runtime-conf.xml ustalamy kodowanie znaków dla połączenia:

<connection>
  ...
  <encoding>utf8</encoding>
</connection>

Wygenerowane klasy będą stosowały podane kodowanie. Rozwiązanie takie ma dwie zalety:

2. 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 propel-gen.bat) w pliku runtime-conf.xml należy wymienić wszystkie bazy danych:

<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.

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 dodasz wpis:

propel.packageObjectModel = true

zaś w plikach -schema.xml umieścisz atrybut package:

<database package="wyrazy" name="wyrazy" ... >

to generowane klasy zostaną umieszczone w osobnych folderach. Atrybut package zawierający kropkę:

package="core.system"

spowoduje dalszy podział generowanych folderów na podfoldery:

core/system

W skrypcie PHP, który korzysta z kilku połączeń należy najpierw wywołać metodę init():

Propel::init('dwiebazy-conf.php');

a następnie utworzyć zmienne umożliwiające korzystanie z połączeń:

$con_osoby = Propel::getConnection('osoby');
$con_wyrazy = Propel::getConnection('wyrazy');

Metody pobierające rekordy z baz danych otrzymają dodatkowy parametr ustalający połączenie:

$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:

echo $osoby[0]->getImie()

$wyrazy[0]->setWyraz('Lorem');
$wyrazy[0]->save();

3. 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:

$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.

4. Porada #4: Konwersje toArray(), fromArray()

Metody toArray() oraz fromArray() pozwalają na konwersje obiektów w tablice i na odwrót.

W celu przekształcenia obiektu $student:

$student = StudentPeer::retrieveByPK(2);

w tablicę wywołujemy metodę toArray():

$t = $student->toArray(BasePeer::TYPE_FIELDNAME);

Jeśli jako parametr podamy stałą TYPE_FIELDNAME, to indeksami tablicy będą nazwy kolumn w bazie danych.

Konwersję odwrotną realizuje metoda fromArray():

$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();

Metody toArray() oraz fromArray() możemy wykorzystać w połączeniu z klasami XML_Serializer oraz XML_Unserializer. W ten sposób możemy:

Oto, jak przebiega konwersja obiektu na XML:

$s = StudentPeer::retrieveByPK(1);
$t = $s->toArray(BasePeer::TYPE_FIELDNAME);

$serializer = new XML_Serializer();
$serializer->serialize($t);

$wynik = $serializer->getSerializedData();

5. Porada #5: Wydawanie zapytań SQL

Niekiedy zachodzi konieczność wykonania konkretnych zapytań SQL. W takiej sytuacji należy wykorzystać statyczną metodę getConnection(). Obiekt zwracany przez tę metodę pozwala na wysyłanie do serwera bazy danych zapytań w języku SQL:

$con = Propel::getConnection('produkty');
$sql = 'SELECT SUM(ilosc * cena) as wartosc FROM produkt';
$rs = $con->executeQuery($sql);
$rs->next();
$wartosc = $rs->getString('wartosc');

Podane wyżej zapytanie wyznacza wartość towaru zapisanego w bazie danych (tj. sumę iloczynów: liczba sztuk * cena jednostki). Wykonanie takiego zadania za pośrednictwem obiektów byłoby znacznie bardziej czasochłonne.

6. 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 doCount().

Oto, w jaki sposób możemy wyznaczyć liczbę wierszy zapisanych w bazie danych:

$ile = WyrazPeer::doCount(new Criteria);

7. Porada #7: Stronicowanie

Stronicowanie wyników wykonujemy stosując metody setLimit() oraz setOffset() klasy Criteria. Pierwsza z nich ustala liczbę zwracanych rekordów, a druga — numer pierwszego zwracanego rekordu:

$c = new Criteria();
$c->setLimit(4);
$c->setOffset(2);

$wyrazy = WyrazPeer::doSelect($c);

8. Porada #8: Pobieranie jednego rekordu

Gdy zachodzi konieczność pobrania dokładnie jednego rekordu, przydatna okazuje się metoda doSelectOne(). 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:

$c = new Criteria();
$c->add(WyrazPeer::WYRAZ, 'lorem');
$wyraz = WyrazPeer::doSelectOne($c);

if (!$wyraz) {
  $wyraz = new Wyraz();
  $wyraz->setWyraz($n);
  $wyraz->save();
}

9. Porada #9: Relacja 1:n, metoda getXs()

Obiekty połączone relacją 1:n są wyposażone w metody, o nazwie getXs() 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:

$poeta = PoetaPeer::retrieveByPK(3);
foreach ($poeta->getWierszs() as $wiersz) {
  echo $wiersz->getTytul();
}

Metoda getWierszs() zwraca wyniki nieuporządkowane. Możemy ją nadpisać w klasie Poeta, by w rezultacie otrzymać metodę getWierszs(), 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:

$wiersz = WierszPeer::retrieveByPK(7);
echo $wiersz->getPoeta()->getImie();

10. Porada #10: Relacja n:m, metody getXHasYsJoinX(), getXHasYsJoinY()

Obiekty stojące w relacji n:m również mają metody dostępu do skorelowanych danych. Metody te nazywają się zgodnie ze schematem

getZsJoinX() (w klasie X)
getZsJoinY() (w klasie Y)

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ę getFilmHasAktorsJoinAktor(), a w klasie Aktor — metodę getFilmHasAktorsJoinFilm(). Metody te będą zwracały obiekty tabeli film_has_aktor.

Oto skrypt drukujący tytuły wszystkich filmów, w których wystąpił aktor o identyfikatorze 1:

$aktor = AktorPeer::retrieveByPK(1);
$c = new Criteria();
$objs = $aktor->getFilmHasAktorsJoinFilm($c);
foreach ($objs as $obj) {
  echo $obj->getFilm()->getTytul();
}

oraz skrypt, który drukuje nazwiska wszystkich aktorów grających w filmie o identyfikatorze 7:

$film = FilmPeer::retrieveByPK(7);
$c = new Criteria();
$objs = $film->getFilmHasAktorsJoinAktor($c);
foreach ($objs as $obj) {
  echo $obj->getAktor()->getNazwisko();
}

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 getFilmsByAktor() (nowa metoda, ręcznie dodana w klasie Aktor), której użycie upraszcza kod skryptu:

$aktor = AktorPeer::retrieveByPK(1);
$c = new Criteria;
$filmy = $aktor->getFilmsByAktor($c);
foreach ($filmy as $film) {
  ...
}

11. Porada #11: Pobieranie zawartości całej bazy danych

Ciekawym efektem ubocznym metod getXs() oraz getZsJoinX() jest to, że jeden obiekt może dać dostęp do całej bazy danych.

Umieśćmy 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:

Ksiazka
  metoda getRozdzials()
  
Rozdzial
  metoda getZadanies()
  
Zadanie

Jeśli w bazie danych znajduje się jeden rekord o identyfikatorze 1 (zbiór zadań z programowania w języku C++), to wywołanie:

$ksiazka = KsiazkaPeer::retrieveByPK(1);

zapewni dostęp do całej bazy danych. Podwójna pętla foreach wydrukuje całą książkę (wszystkie rozdziały i wszystkie zadania):

foreach ($ksiazka->getRozdzials() as $rozdzial) {
  echo $rozdzial->getTytul();
  foreach ($rozdzial->getZadanies() as $zadanie) {
    echo $zadanie->getTekst();
  }
}

12. Porada #12: Smarty i wielokrotne wywoływanie metod

Szablony Smarty domyślnie nie pozwalają na wielokrotne wywoływanie metod. Instrukcje szablonu:

PRZYKŁAD NIEPOPRAWNY
{$wiersz->getAutor()->getImie()}

będą powodowały błąd. W celu ominięcia tego problemu możemy zmodyfikować klasę Smarty_Compiler. Jeśli w pliku Smarty_Compiler.class.php wymienisz wyrażenie regularne zawarte w linijce 155 i w miejsce:

...$this->_dvar_guts_regexp . ')';

wpiszesz:

..$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.

13. Porada #13: Metoda __toString()

Metoda __toString() służy do konwersji obiektu na typ string. Jeśli wygenerowane klasy wzbogacisz o metody __toString(), to będziesz mógł stosować obiekty jako parametry instrukcji echo, np.:

$poeta = PoetaPeer::retrieveByPK(3);
echo $poeta;
foreach ($poeta->getWierszs() as $wiersz) {
    echo $wiersz;
}

Instrukcje:

echo $poeta;
echo $wiersz;

będą działały poprawnie, pod warunkiem, że w klasach Poeta oraz Wiersz dodasz metody __toString():

class Poeta extends BasePoeta {
  function __toString()
  {
    return $this->getImie() . ' ' . 
        $this->getNazwisko();
  }
}

class Wiersz extends BaseWiersz {
  function __toString()
  {
    return $this->getTytul();
  }
}

14. Porada #14: Wyjątki

W przypadku wystąpienia błędów, obiekty Propel-a generują wyjątki. Obsługę wyjątków realizujemy instrukcją try-catch. Metody obiektu $e pozwalają poznać przyczynę błędu:

$wyraz = new Wyraz();
$wyraz->setWyraz('żółw');

try {
  $wyraz->save();
} catch (PropelException $e) {
  $c = $e->getCause();
  $komunikat = $c->getNativeError();
  echo $komunikat;
}

15. Porada #15: Sortowanie wyników wg wybranych kolumn

Porządek sortowania zwracanych wyników ustalamy, korzystając z metod addAscendingOrderByColumn() oraz addDescentingOrderByColumn() klasy Criteria:

$c = new Criteria();
$c->addAscendingOrderByColumn(StudentPeer::NAZWISKO);
$c->addDescendingOrderByColumn(StudentPeer::IMIE);
$studenci = StudentPeer::doSelect($c);

16. 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:

11-cala-zawartosc/
    1-zrzut-db/
        tworz-baze-cpp.bat
        baza-cpp.sql
    2-skrypt/
        skrypt.php

W pliku tworz-baze-cpp.bat, w miejsce napisu AX1BY2CZ3 umieść hasło administratora serwera MySQL:

c:\mysql\bin\mysql -uroot -pAX1BY2CZ3 < baza-cpp.sql

Następnie uruchom skrypt tworz-baze-cpp.bat, po czym — wykorzystując aplikację phpMyAdmin — sprawdź, czy baza danych o nazwie cpp została utworzona. Nazwę tworzonej bazy danych znajdziesz w skrypcie baza-cpp.sql:

...
DROP DATABASE IF EXISTS cpp;
...

Powinieneś otrzymać bazę danych cpp wstępnie wypełnioną pewną liczbą rekordów, co ilustruje rysunek 1.

Rysunek 1. Sprawdzanie poprawności tworzenia bazy danych o nazwie cpp programem phpMyAdmin

Następnie przeglądarką WWW odwiedź plik skrypt.php. Ujrzysz stronę przedstawioną na rysunku 2.

Rysunek 2. Przykład 11: zbiór zadań z podstaw programowania w języku C++

Szczegóły rozwiązania poznasz analizując plik skrypt.php.

lp. Przykład
1. Propel. Porady. Przykładowe skrypty

Tabela 1. Przykłady do pobrania

lp. Adres
1. Propel — strona domowa
2. „Propel. Tutorial” — artykuł wprowadzający w świat Propel-a
3. „DBDesigner i Propel” — artykuł opisujący jak połączyć dwie wymienione aplikacje
4. „Mistrzostwa świata w piłce nożnej, czyli Propel i DBDesigner w akcji”
5. „Instalacja aplikacji wykorzystującej biblioteki Propel i Creole na serwerze hostingowym”
6. Wikipedia, hasło ORM
7. Doctrine — alternatywny ORM w PHP

Tabela 2. Adresy

 1   2   3   4   5   6   7   8  ...