Connect with us

Cześć, czego szukasz?

Internet Maker

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;

Może cię też zainteresować

Internet Maker

PHP zdobył przed laty popularność jako język skryptowy do tworzenia stron internetowych. Wzięła się ona z pewnością stąd, że jeszcze kilka lat temu nie było alternatywy dla szybkiego, prostego...

Internet Maker

To już trzecie wydanie książki Andrzeja Kierzkowskiego, tłumaczącej nie tylko podstawy języka PHP 5, ale także zawierającej wiele praktycznych ćwiczeń. Autor od lat pisze książki dotyczące programowania w tym języku,...

Internet Maker

Język PHP jest wykorzystywany najczęściej do tworzenia skryptów pracujących na tekście. Jednak dzięki dołączonej do PHP bibliotece GD, możliwa jest łatwa praca na grafice – od prostej obróbki po rysowanie...

Internet Maker

Symfony to jeden z najlepszych dostępnych obecnie frameworków w języku PHP. Dzięki jasnej strukturze oraz generatorom kodu przygotowanie kompletnej aplikacji WWW zajmuje kilku minut. Artykuł opisuje krok po kroku...