SOCJAL
Do góry

Propel – porady jak rozwiązać podstawowe problemy

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:


  ...
  utf8

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:


  
    mysql
    
      mysqli
      localhost
      osoby
      osobyadm
      osobypass
      utf8                    
    
  
  
    mysql
    
      mysqli
      localhost
      wyrazy
      wyrazyadm
      wyrazypass
      utf8                    
    
              

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

propel.packageObjectModel = true

zaś w plikach {stala}-schema.xml{/stala} umieścimy atrybut package:

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

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.

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

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

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

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

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

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

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

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

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

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

$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

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

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

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

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

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

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

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

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

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

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:

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

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

$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

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:

{$wiersz->getAutor()->getImie()}

wpiszemy:

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

..$this->_dvar_guts_regexp . \'(?:\(\))?)\';

Instrukcje:

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

echo $poeta;
echo $wiersz;

Zostaw komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Więcej w Internet Maker