Invenzzia » Zasoby / Artykuły / Piszemy galerię zdjęć... / Rozbudowa modeli

5. Rozbudowa modeli

Doctrine domyślnie generuje po dwa pliki PHP dla każdego modelu: NazwaModelu oraz BaseNazwaModelu umieszczony w podkatalogu /models/generated. Drugi z nich jest przebudowywany za każdym razem, gdy wydamy polecenie przebudowania modeli, natomiast pierwszy oddany jest do naszej dyspozycji tak, abyśmy mogli go dowolnie rozszerzać o potrzebne nam opcje, których nie da się wyrazić w pliku YAML.

Modele powinny zajmować się przygotowywaniem i obróbką danych tak, aby widoki (czyli część skryptu podległa OPT) operowały już na gotowych elementach. Dlatego musimy rozbudować domyślne modele tak, aby udostępniały całą gamę dodatkowych informacji. W przypadku naszych tablic polem krytycznym jest data dodania. Wiemy o niej następujące rzeczy:

  1. W bazie danych data przechowywana jest jako liczba sekund od 1.1.1970.

  2. Podczas dodawania zdjęcia/komentarza należy zainicjować pole date aktualną datą.

  3. Podczas wyświetlania model powinien już udostępnić sformatowaną datę w postaci czytelnej dla człowieka, np. 19.05.2009, 13:16.

Pierwszy punkt mamy już zrealizowany, zostały dwa pozostałe. Skorzystamy z dwóch możliwości Doctrine:

  • Obserwatorów zdarzeń.
  • Automatycznych metod dostępowych (auto-accessors).

Doctrine umożliwia modelowi reagowanie na różne zdarzenia, przez co możemy wykonać jakieś dodatkowe operacje np. przed dodaniem nowego wiersza do bazy. Część zdarzeń możemy obsłużyć, przeciążając po prostu w klasie modelu odpowiednie metody, zaś w pozostałych przypadkach musimy posłużyć się dodatkową klasą, obserwatorem zdarzeń (ang. event listener). Sekcja listeners w pliku YAML definiowała właśnie takiego obserwatora dla obu naszych modeli, teraz pozostało nam już tylko go napisać. Otwórzmy plik /models/Photo.php i do (na razie pustej) klasy Photo dodajmy metodę:

    public function preInsert($event)
    {
        $this->date = time();
    } // end preInsert();

W ten oto prosty sposób obsłużyliśmy zdarzenie wstawiania wiersza do bazy danych i mamy pewność, że pole date zawsze zostanie zainicjowane aktualną datą. Oprócz preInsert() jest także postInsert() wykonywana po dodaniu wiersza. Mamy również analogiczne metody dla wielu innych zdarzeń, np. aktualizacji czy usuwania. Warto z nich korzystać szczególnie, kiedy tworzenie wiersza wiąże się z operacjami na systemie plików, abyśmy nie musieli pamiętać o oprogramowywaniu tego w pozostałych miejscach skryptu.

Formatowanie daty musimy zrealizować na dwa sposoby. Pierwszy to użycie metod dostępowych, które umożliwiają deklarowanie fikcyjnych pól w modelu. Od strony użytkowej zachowują się one identycznie, jak prawdziwe pola, tj. możemy do nich coś zapisywać i odczytywać, lecz w rzeczywistości te dwie operacje są obsługiwane przez dwie metody modelu, gdzie definiujemy ich prawdziwą naturę. My wykorzystamy to następująco: pole date będzie przechowywać datę w oryginalnym, systemowym formacie (może się czasem przydać), zaś sformatowana wersja dostępna będzie poprzez fikcyjne pole date_text. W momencie wywołania $model->date_text, Doctrine odpali nam metodę getDateText():

    public function getDateText()
    {
        return date('d.m.Y, H:i', $this->date);
    } // end getDateText();

A ona zwróci nam sformatowaną datę utworzoną w locie z wartości pola date. Ustawianie wartości możemy obsłużyć, tworząc metodę setDateText($value). Zwróćmy uwagę, że Doctrine automatycznie konwertuje styl nazewnictwa – pola mają nazwy w formacie nazwa_pola, zaś tworzone dla nich metody dostępowe – getNazwaPola/setNazwaPola.

Z metod dostępowych należy korzystać bardzo ostrożnie. Doctrine przyznaje im pierwszeństwo przed zwykłymi polami, a sama funkcjonalność nie jest do końca dopracowana, gdyż pierwotnie... miało jej w bibliotece nie być. Została ona wymuszona przez samych użytkowników, lecz domyślnie jest ona wyłączona. Problem polega na tym, że modele Doctrine posiadają kilka metod wewnętrznych w stylu getCostam() i jeśli zajdzie nam konflikt nazw między jakimś rzeczywistym polem, a jedną z nich, nasz skrypt po prostu zgłupieje. Warto przejrzeć dokumentację API, aby zobaczyć, jakich nazw należy unikać w modelu, aby nie wpakować się w kłopoty.

Sama metoda dostępowa to nie wszystko. Zadziała ona tylko wtedy, gdy będziemy odwoływać się do obiektu modelu, tymczasem ze względów wydajnościowych będziemy chcieli, aby zapytania SELECT zwracały nam tablice. Tutaj pomoże nam klasa obserwatora zdarzeń, w którym podepniemy się pod zdarzenie rzutowania zbioru wyników (ang. hydration). Gdy Doctrine będzie budować nam tablicę, wywoła dodatkowo naszą metodę, która dopisze do niej pole date_text. Oto kompletna klasa, którą możemy umieścić w tym samym pliku, co model:

class PhotoListener extends Doctrine_Record_Listener
{
    public function preHydrate(Doctrine_Event $event)
    {
        $data = $event->data;
 
        if(isset($data['date']))
        {
            $data['date_text'] = date('d.m.Y, H:i', $data['date']);
        }
 
        $event->data = $data;
    } // end preHydrate();
} // end PhotoListener;

Tutaj musimy zwrócić uwagę na dwie rzeczy:

  1. Nie możemy operować bezpośrednio na polu $event->data, musimy przepisać jego wartość do zmiennej, a później z powrotem do $event->data tak, jak na przykładzie.

  2. Zbiór wyników nie musi zawierać pola date, dlatego przed wykonaniem na nim jakichkolwiek operacji, musimy upewnić się, czy takie pole zostało zwrócone.

Model komentarzy posiada dokładnie identyczne pole date, które należy obsłużyć dokładnie w ten sam sposób. Spróbuj wykonać to w ramach ćwiczenia, wzorując się na już gotowym modelu Photo.