Uwaga, blog przeniesiony

Posty na tym blogu już nie będą się pojawiać. Zapraszam gorąco pod nowy adres: blog.grzegorzpawlik.com



Subskrybuj ten blog...

środa, 10 grudnia 2008

Dobre praktyki programowania w CakePHP #1

Postanowiłem zbierać gdzieś drobne usprawnienia, przy programowaniu w cake'u, żeby móc później zebrać to w jakąś sensowną listę do zapoznania się "cake'owcom" w naszej firmie.
Padło na blog - zawsze to coś czym można się podzielić z całym światem.

Problem #1:
Mamy taką strukturę (przedstawię za pomocą modeli):
model Photo (id, name) i Tag (id, name),
Photo hasAndBelongsToMany Tag.

W jednym miejscu potrzebne było znalezienie Photos takich, które są powiązane z Tagiem, którego name = 'abc'.

Zostało to wykonane w sposób następujący:
$photos = $this->Photo->Tag->find(" Tag.name ='abc' " );
W widoku z kolei ktoś zrobił
foreach($photos['Photo'] as $photo); gdyż z relacji założył, że wyszukując tag, dostanie powiązane z nim Photos.

Jak widać, dobrał się do tych danych "od tyłu"...
Na pierwszy rzut oka wygląda na to, że jest ok. Ale po jakimś czasie okazało się, że zdjęcia powinny być w kategorii, czyli doszedł nam
Category
i Photo belongsTo Category (photo wzbogacił się o pole category_id).

I poprzednie zadanie musiało zostać zmodyfikowane. Otóż zamiast 'abc' ktoś mógł podać id dla Category w którym jest Photo. Nie można zmienić kodu po prostu dodając warunek, gdyż wyszukujemy tagi, a one nie mają żadnego cateogry_id dla Photos

Nie można zrobić drugiego szukania
$photos2 = $this->Photo->findAll("Photo.category_id = 50") i zrobić zwykłego array_merge, gdyż strulktura zwracanych tablic jest inna
1: Tag.Photo.{n}.id
vs
2: {n}.Photo.id

Więc trzeba robić pętle, która drugą tablicę zmieli i przerobi tak, żeby wyglądała jak pierwsza (i z automatu działała w widoku).
Nawet w tym momencie wydaje nam się, że wszystko gra (jeśli przymknęliśmy oko na nieeleganckie rozwiązanie ze zmianą struktury drugiej tablicy) tak naprawdę brniemy coraz głębiej.

Wyobraźmy sobie, że za dwa miesiące klient ma tak dużo Photos pasujących do kryterium, ze potrzebuje pagination + sortowanie (po nazwie zdjęcia). Jesteśmy w kropce, bo nie załatwimy pagination w zapytaniu sql za pomocą limit - gdyż mamy dwa zapytania. Z sortowaniem to samo. Jak dalej się uprzemy, że brniemy coraz dalej w grząskie tereny - skończymy implementując sortowanie na zcalonych dwóch tablicach - koszmar. Nie od teog są kontrolery!

Odpowiedź (nie idealna, ale też całkiem sensowna) :
Długo się głowiłem jak ten problem ugryźć.

Po pierwsze trzeba przyjąć, że nie próbuję exploitować cake'owych funkcjonalności i dobierać się do dany "wstecz" relacji. W naszym przykładzie - potrzebuję Photos, więc wywołuję metodę z modelu Photo. Takim exploitem jest wykorzystanie modelu (Tag), który jest w relacji z Photo (relacja w przód) i założenie, że Tag też będzie w symetrycznej relacji z Photo i wykorzystam ją do moich celów (tu właśnie jest to działanie wstecz).

Po drugie trzeba wbić sobie do głowy zasadę "Skinny Controller, Fat Model". Jeśli z zasady grzebię tylko w kontrolerach, a boję się ruszyć model - polegnę. Skończę tak, że modele potrafią tylko to co zaimplementowane zostało przez team Cake'a. A cała logika będzie się odbywać w kontrolerze, mimo, że nie cała do niego należy (choćby sortowanie, kryteria wyszukiwania).
Dlatego od samego początku, gdy poczujesz leniwca na ramieniu, który sugeruje, żeby dobrać się do danych od tyłu - przypomnij sobie, że to nie ładne rozwiązanie.

Należałoby raczej na samym początku zdefiniować metodę modelu
Photo::customFind($conditions = null)
W niej wysmarzyć prostego sql'a (napiszę go "na sztywno", w w następnych wskazówkach podam jak poprawnie budować query w modelach)
"Select * from photos as Photo
   Inner Join  photos_tags as pt on (pt.photo_id = Photo.id)
   Inner Join tags as Tag on (pt.tag_id = Tag.id)
WHERE $conditions";
Wtedy na samym początku w conditions bez problemu możemy podać Tag.name = 'abc'.

Po przyjściu nowego problemu wystarczy, ze conditions będzie:
Tag.name = 'abc' or Photo.category_id = 'abc'

Jak potrzebuję sortowanie - dodaję sobie parametr $orderBy i doklejam do mojego query.
Tak samo z pagination etc.

Zauważ, że w pierwszym wypadku Controler puchł, a Model był pusty (oprócz relacji). W przypadku drugim Controler powiększa się co najwyżej o dodatkowe parametry w wywołaniu metody $this->Photo->cunstomFind
Struktura zwracanych wyników jest pod kontrolą i nie trzeba zmieniać widoku.

Zamiast siedzieć 5 godzin nad nowymi problemami, wstukujemy kilka sprytnych linijek i idziemy na piwko.

Podsumowując, jedna z dobrych praktych programowaniu w cakePHP brzmi:
Nigdy nie dobieraj się do swoich danych z d*** strony

Brak komentarzy:

Uwaga! blog przeniesiony

Posty na tym blogu już nie będą się pojawiać. Zapraszam gorąco pod nowy adres: blog.grzegorzpawlik.com
Komentowanie artykułów możliwe jest pod nowym adresem.