<h3>Porada #1: Kodowanie znaków</h3>

<p>
Klasy dostępu do bazy danych mogą stosować dowolne
kodowanie znaków, niezależne od ustawień serwera.
</p>

<p>
Przed uruchomieniem generatora <span class="program">Propel</span>
(tj. skryptu <span class="filename">propel-gen.bat</span>)
w pliku 
<span class="filename">runtime-conf.xml</span>
ustalamy kodowanie znaków dla połączenia:
</p>

<pre>
&lt;connection&gt;
  ...
  &lt;encoding&gt;utf8&lt;/encoding&gt;
&lt;/connection&gt;
</pre>

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

<ul>
<li>skrypty PHP korzystające z klas nie muszą zawierać żadnych 
instrukcji ustalających kodowanie,</li>
<li>konfiguracja kodowania znaków serwera <span class="program">MySQL</span> 
nie ma wpływu na skrypty.</li>
</ul>

<h3>Porada #2: Korzystanie z kilku baz danych</h3>

<p>
Skrypt PHP może &mdash; za pośrednictwem propelowych obiektów &mdash;
łączyć się z wieloma bazami danych.
</p>

<p>
Przed uruchomieniem generatora klas
(tj. skryptu <span class="filename">propel-gen.bat</span>)
w pliku <span class="filename">runtime-conf.xml</span>
należy wymienić wszystkie bazy danych:
</p>

<pre>
&lt;datasources default="osoby"&gt;
  &lt;datasource id="osoby"&gt;
    &lt;adapter&gt;mysql&lt;/adapter&gt;
    &lt;connection&gt;
      &lt;phptype&gt;mysqli&lt;/phptype&gt;
      &lt;hostspec&gt;localhost&lt;/hostspec&gt;
      &lt;database&gt;osoby&lt;/database&gt;
      &lt;username&gt;osobyadm&lt;/username&gt;
      &lt;password&gt;osobypass&lt;/password&gt;
      &lt;encoding&gt;utf8&lt;/encoding&gt;                    
    &lt;/connection&gt;
  &lt;/datasource&gt;
  &lt;datasource id="wyrazy"&gt;
    &lt;adapter&gt;mysql&lt;/adapter&gt;
    &lt;connection&gt;
      &lt;phptype&gt;mysqli&lt;/phptype&gt;
      &lt;hostspec&gt;localhost&lt;/hostspec&gt;
      &lt;database&gt;wyrazy&lt;/database&gt;
      &lt;username&gt;wyrazyadm&lt;/username&gt;
      &lt;password&gt;wyrazypass&lt;/password&gt;
      &lt;encoding&gt;utf8&lt;/encoding&gt;                    
    &lt;/connection&gt;
  &lt;/datasource&gt;            
&lt;/datasources&gt;
</pre>

<p>
Dla każdego wymienionego źródła danych
przygotowujemy osobny plik XML z opisem
struktury bazy, np.
</p>

<pre>
wyrazy-schema.xml
osoby-schema.xml
</pre>

<p>
Tak skonfigurowany <span class="program">Propel</span> wygeneruje
plik konfiguracyjny <span class="filename">-conf.php</span>,
umożliwiający łączenie się obiektów z wieloma bazami,
przy czym wszystkie wygenerowane klasy trafią do jednego folderu.
</p>

<p>
Jeśli w pliku <span class="filename">build.properties</span> dodasz wpis:
</p>

<pre>
propel.packageObjectModel = true
</pre>

<p>
zaś w plikach <span class="filename">-schema.xml</span> umieścisz atrybut
<span class="filename">package</span>:
</p>

<pre>
&lt;database package="wyrazy" name="wyrazy" ... &gt;
</pre>

<p>
to generowane klasy zostaną umieszczone w osobnych folderach.
Atrybut <span class="variable">package</span>
zawierający kropkę:
</p>

<pre>
package="core.system"
</pre>

<p>
spowoduje dalszy podział generowanych folderów
na podfoldery:
</p>

<pre>
core/system
</pre>



<p>
W skrypcie PHP, który korzysta z kilku połączeń 
należy najpierw wywołać metodę <span class="variable">init()</span>:
</p>



<pre>
Propel::init('dwiebazy-conf.php');
</pre>



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


<pre>
$con_osoby = Propel::getConnection('osoby');
$con_wyrazy = Propel::getConnection('wyrazy');
</pre>



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



<pre>
$wyrazy = WyrazPeer::doSelect(new Criteria, $con_wyrazy);
$osoby = OsobaPeer::doSelect(new Criteria, $con_osoby);
</pre>



<p>
Pierwsza z powyższych instrukcji pobiera dane z bazy 
o nazwie <span class="variable">wyrazy</span>, 
a druga &mdash; z bazy o nazwie <span class="variable">osoby</span>.
</p>

<p>
Utworzone obiekty nie wymagają podawania 
połączenia. Korzystamy z nich identycznie jak w skryptach,
które stosowały jedną bazę danych:
</p>


<pre>
echo $osoby[0]-&gt;getImie()

$wyrazy[0]-&gt;setWyraz('Lorem');
$wyrazy[0]-&gt;save();
</pre>





<h3>Porada #3: Pobieranie tylko wybranych kolumn</h3>

<p>
Obiekty tworzone na podstawie informacji
zapisanych w bazie, 
np. metodą <span class="variable">retrieveByPK()</span>,
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.
</p>

<p>
Kolumny, które powinny być pobrane,
możemy wskazać korzystając z metod 
klasy <span class="variable">Criteria()</span>.
Utworzona poniżej tablica <span class="variable">$studenci</span>
zawiera tylko imiona studentów:
</p>

<pre>
$c = new Criteria();
$c-&gt;clearSelectColumns();
$c-&gt;addSelectColumn(StudentPeer::IMIE);
$rs = StudentPeer::doSelectRS($c);
$studenci = array();
while($rs-&gt;next()) {
  $tmp = array(
    'imie' =&gt; $rs-&gt;get(1),
  );
  $studenci[] = $tmp;
}
</pre>

<p>
Tak wygenerowane wyniki
nie mogą być wykorzystane do 
operacji <span class="variable">save()</span> czy 
<span class="variable">delete()</span>, gdyż nie są 
obiektami, a stringami.
</p>

<p>
Kod zwracający wyłącznie wybrane kolumny 
dodajemy w postaci nowych metod do wygenerowanych klas.
Metody te mogą przyjmować parametr klasy <span class="variable">Criteria</span>,
który pozwoli na wskazywanie wybranych wierszy i sortowanie wyników.
</p>

<h3>Porada #4: Konwersje toArray(), fromArray()</h3>

<p>
Metody <span class="filename">toArray()</span> oraz 
<span class="filename">fromArray()</span>
pozwalają na konwersje obiektów w tablice i na odwrót.
</p>


<p>
W celu przekształcenia obiektu <span class="variable">$student</span>:
</p>


<pre>
$student = StudentPeer::retrieveByPK(2);
</pre>


<p>
w tablicę wywołujemy metodę <span class="variable">toArray()</span>:
</p>




<pre>
$t = $student-&gt;toArray(BasePeer::TYPE_FIELDNAME);
</pre>

<p>
Jeśli jako parametr podamy stałą <span class="variable">TYPE_FIELDNAME</span>,
to indeksami tablicy będą nazwy kolumn w bazie danych.
</p>



<p>
Konwersję odwrotną realizuje metoda <span class="variable">fromArray()</span>:
</p>


<pre>
$t = array(
  'imie' =&gt; 'Tomasz',
  'nazwisko' =&gt; 'Nijaki',    
  'plec' =&gt; 'M',    
  'wiek' =&gt; '33',    
  'numerindeksu' =&gt; '00000000001',    
  'kierunek' =&gt; 'marketing',    
);

$s2 = new Student();
$s2-&gt;fromArray($t, BasePeer::TYPE_FIELDNAME);
$s2-&gt;save();
</pre>



<p>
Metody 
<span class="variable">toArray()</span>
oraz 
<span class="variable">fromArray()</span>
możemy wykorzystać w połączeniu z klasami
<span class="variable">XML_Serializer</span>
oraz 
<span class="variable">XML_Unserializer</span>.
W ten sposób możemy:
</p>

<ul>
<li>obiekty konwertować do XML (<span class="variable">toArray()</span>, <span class="variable">XML_Serializer</span>),</li>
<li>na podstawie danych XML tworzyć obiekty (<span class="variable">XML_Unserializer</span>, <span class="variable">fromArray()</span>).</li>
</ul>



<p>
Oto, jak przebiega konwersja obiektu na XML:
</p>


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

$serializer = new XML_Serializer();
$serializer-&gt;serialize($t);

$wynik = $serializer-&gt;getSerializedData();
</pre>





<h3>Porada #5: Wydawanie zapytań SQL</h3>

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


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

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

<h3>Porada #6: Zliczanie</h3>

<p>
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 <span class="variable">doCount()</span>.
</p>

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

<pre>
$ile = WyrazPeer::doCount(new Criteria);
</pre>

<h3>Porada #7: Stronicowanie</h3>


<p>
Stronicowanie wyników wykonujemy stosując metody 
<span class="variable">setLimit()</span> oraz 
<span class="variable">setOffset()</span> klasy <span class="variable">Criteria</span>.
Pierwsza z nich ustala liczbę zwracanych rekordów,
a druga &mdash; numer pierwszego zwracanego rekordu:
</p>



<pre>
$c = new Criteria();
$c-&gt;setLimit(4);
$c-&gt;setOffset(2);

$wyrazy = WyrazPeer::doSelect($c);
</pre>
 
<h3>Porada #8: Pobieranie jednego rekordu</h3>

<p>
Gdy zachodzi konieczność pobrania dokładnie jednego rekordu,
przydatna okazuje się metoda <span class="variable">doSelectOne()</span>.
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 <span class="variable">if</span> do zbadania, 
czy obiekt został utworzony:
</p>

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

if (!$wyraz) {
  $wyraz = new Wyraz();
  $wyraz-&gt;setWyraz($n);
  $wyraz-&gt;save();
}
</pre>

<h3>Porada #9: Relacja 1:n, metoda getXs()</h3>

<p>
Obiekty połączone relacją 1:n są wyposażone w metody,
o nazwie <span class="variable">getXs()</span> udostępniające dane stojące w relacji.
Litera X w nazwie metody jest zastępowana nazwą odpowiedniej tabeli.
</p>

<p>
Jeśli tabele <span class="variable">poeta</span> i 
<span class="variable">wiersz</span> połączymy relacją 1:n
(każdy poeta może być autorem wielu wierszy),
to w klasie <span class="variable">Poeta</span> 
pojawi się metoda <span class="variable">getWierszs()</span>.
Metoda ta będzie zwracała obiekty klasy <span class="variable">Wiersz</span>:
</p>

<p>
Oto skrypt drukujący tytuły wierszy poety o identyfikatorze 3:</p>

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


<p>
Metoda <span class="variable">getWierszs()</span> zwraca wyniki nieuporządkowane.
Możemy ją nadpisać w klasie <span class="variable">Poeta</span>,
by w rezultacie otrzymać metodę <span class="variable">getWierszs()</span>,
która zachowując pełną funkcjonalność domyślnie zwraca wyniki
posortowane alfabetycznie.
</p>

<p>
W drugą stronę korzystamy z odwołań kaskadowych.
Oto jak ustalić nazwisko poety,
który napisał wiersz o identyfikatorze 7:
</p>

<pre>
$wiersz = WierszPeer::retrieveByPK(7);
echo $wiersz-&gt;getPoeta()-&gt;getImie();
</pre>


<h3>Porada #10: Relacja n:m, metody getXHasYsJoinX(), getXHasYsJoinY()</h3>

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



<pre>
getZsJoinX() (w klasie X)
getZsJoinY() (w klasie Y)
</pre>

<p>
gdzie <span class="variable">Z</span> 
jest nazwą tabeli haszującej, a 
<span class="variable">X</span> oraz 
<span class="variable">Y</span> &mdash; nazwami tabel
połączonych relacją.
</p>

<p>
Jeśli tabele <span class="variable">film</span> oraz 
<span class="variable">aktor</span> połączymy relacją 
<span class="variable">n:m</span>
i tabelę haszującą nazwiemy 
<span class="variable">film_has_aktor</span>, to 
<span class="program">Propel</span> wygeneruje 
trzy klasy:
<span class="variable">Film</span>, 
<span class="variable">Aktor</span> oraz 
<span class="variable">FilmHasAktor</span>.
W klasie <span class="variable">Film</span> znajdziemy metodę 
<span class="variable">getFilmHasAktorsJoinAktor()</span>,
a w klasie <span class="variable">Aktor</span> &mdash; metodę 
<span class="variable">getFilmHasAktorsJoinFilm()</span>.
Metody te będą zwracały obiekty 
tabeli <span class="variable">film_has_aktor</span>.
</p>



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


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



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



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



<p>
Korzystając z powyższych metod możemy 
opracować własne metody, których wyniki, 
będą obiektami klas <span class="variable">Film</span> lub 
<span class="variable">Aktor</span>.
Przykładem takiej metody jest <span class="variable">getFilmsByAktor()</span>
(nowa metoda, ręcznie dodana w klasie <span class="variable">Aktor</span>),
której użycie upraszcza kod skryptu:
</p>


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

    



<h3>Porada #11: Pobieranie zawartości całej bazy danych</h3>


<p>
Ciekawym efektem ubocznym metod <span class="variable">getXs()</span>
oraz <span class="variable">getZsJoinX()</span>
jest to, że jeden obiekt może dać dostęp 
do całej bazy danych.
</p>

<p>
Umieśćmy w bazie danych książki
podzielone na rozdziały, które z kolei zawierają zadania.
Otrzymamy trzy tabele:
<span class="variable">ksiazka</span>, 
<span class="variable">rozdzial</span> oraz 
<span class="variable">zadanie</span>. Relacją 1:n łączymy tabele
<span class="variable">ksiazka</span> i <span class="variable">rozdzial</span>
(każda książka zawiera wiele rozdziałów)
oraz <span class="variable">rozdzial</span> i 
<span class="variable">zadanie</span> 
(każdy rozdział zawiera wiele zadań).
<span class="program">Propel</span> wygeneruje klasy:
</p>


<pre>
Ksiazka
  metoda getRozdzials()
  
Rozdzial
  metoda getZadanies()
  
Zadanie
</pre>



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


<pre>
$ksiazka = KsiazkaPeer::retrieveByPK(1);
</pre>

<p>
zapewni dostęp do całej bazy danych.
Podwójna pętla <span class="variable">foreach</span> wydrukuje całą książkę 
(wszystkie rozdziały i wszystkie zadania):
</p>


<pre>
foreach ($ksiazka-&gt;getRozdzials() as $rozdzial) {
  echo $rozdzial-&gt;getTytul();
  foreach ($rozdzial-&gt;getZadanies() as $zadanie) {
    echo $zadanie-&gt;getTekst();
  }
}
</pre>

<h3>Porada #12: Smarty i wielokrotne wywoływanie metod</h3>


<p>
Szablony <span class="program">Smarty</span> 
domyślnie nie pozwalają na wielokrotne wywoływanie metod.
Instrukcje szablonu:
</p>

<pre>
<strong>PRZYKŁAD NIEPOPRAWNY</strong>
{$wiersz-&gt;getAutor()-&gt;getImie()}
</pre>

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


<pre>
...$this-&gt;_dvar_guts_regexp . ')';
</pre>


<p>
wpiszesz:
</p>


<pre>
..$this-&gt;_dvar_guts_regexp . '(?:\(\))?)';        
</pre>

<p>
wielokrotne wywołanie metod będzie działało poprawnie.
</p>


<p>
Powyższa niedogodność sytemu <span class="program">Smarty</span> 
jest na tyle dokuczliwa,
że rozsądnym wydaje się rezygnacja z szablonów <span class="program">Smarty</span> 
na rzecz surowych szablonów PHP.
</p>



<h3>Porada #13: Metoda __toString()</h3>

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



<pre>
$poeta = PoetaPeer::retrieveByPK(3);
echo $poeta;
foreach ($poeta-&gt;getWierszs() as $wiersz) {
    echo $wiersz;
}
</pre>



<p>
Instrukcje:
</p>


<pre>
echo $poeta;
echo $wiersz;
</pre>





<p>
będą działały poprawnie, pod warunkiem, że w klasach 
<span class="variable">Poeta</span>
oraz 
<span class="variable">Wiersz</span> dodasz 
metody <span class="variable">__toString()</span>:
</p>

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

class Wiersz extends BaseWiersz {
  function __toString()
  {
    return $this-&gt;getTytul();
  }
}
</pre>



<h3>Porada #14: Wyjątki</h3>

<p>
W przypadku wystąpienia błędów, 
obiekty <span class="program">Propel-a</span> generują wyjątki.
Obsługę wyjątków realizujemy 
instrukcją <span class="variable">try-catch</span>.
Metody obiektu <span class="variable">$e</span> pozwalają poznać przyczynę błędu:
</p>

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

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


<h3>Porada #15: Sortowanie wyników wg wybranych kolumn</h3>

<p>
Porządek sortowania zwracanych wyników
ustalamy, korzystając z metod
<span class="variable">addAscendingOrderByColumn()</span>
oraz 
<span class="variable">addDescentingOrderByColumn()</span>
klasy <span class="variable">Criteria</span>:
</p>


<pre>
$c = new Criteria();
$c-&gt;addAscendingOrderByColumn(StudentPeer::NAZWISKO);
$c-&gt;addDescendingOrderByColumn(StudentPeer::IMIE);
$studenci = StudentPeer::doSelect($c);
</pre>




<h3>Uruchamianie przykładowych rozwiązań</h3>

<p>
Każda z powyższych porad jest zilustrowana przykładami.
Pojedynczy przykład składa się ze skryptów tworzących 
bazę danych (pliki <span class="filename">.sql</span> i <span class="filename">.bat</span>)
oraz ze skryptów PHP, które pobierają dane z bazy
i prezentują je w postaci strony WWW.
</p>

<p>
Uruchamianie każdego przykładu należy rozpocząć od utworzenia bazy danych.
Następnie przeglądarką odwiedzamy stronę <span class="filename">skrypt.php</span>.
</p>


<p>
Oto jak przebiega uruchomienie przykładu jedenastego.
W folderze <span class="filename">11-cala-zawartosc/</span>
znajdziemy dwa podfoldery,
a w nich pliki:
</p>

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


<p>
W pliku <span class="filename">tworz-baze-cpp.bat</span>,
w miejsce napisu <span class="variable">AX1BY2CZ3</span> umieść hasło administratora
serwera <span class="program">MySQL</span>:
</p>


<pre>
c:\mysql\bin\mysql -uroot -pAX1BY2CZ3 &lt; baza-cpp.sql
</pre>



<p>
Następnie uruchom skrypt <span class="filename">tworz-baze-cpp.bat</span>,
po czym &mdash; wykorzystując aplikację 
<span class="program">phpMyAdmin</span> 
&mdash; sprawdź, czy baza danych o nazwie 
<span class="variable">cpp</span> została utworzona.
Nazwę tworzonej bazy danych znajdziesz 
w skrypcie <span class="filename">baza-cpp.sql</span>:
</p>

<pre>
...
DROP DATABASE IF EXISTS cpp;
...
</pre>


<p>
Powinieneś otrzymać bazę danych <span class="variable">cpp</span> 
wstępnie wypełnioną pewną liczbą rekordów, co ilustruje rysunek 1.
</p>

#####{$figures[1]}#####

<p>
Następnie przeglądarką WWW odwiedź plik <span class="filename">skrypt.php</span>.
Ujrzysz stronę przedstawioną na rysunku 2.
</p>

#####{$figures[2]}#####


<p>
Szczegóły rozwiązania poznasz analizując
plik <span class="filename">skrypt.php</span>.
</p>







