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

środa, 3 grudnia 2008

Hosting idealny, ciąg dalszy

W prawdzie nie miałem w planach spamować o tym na blogu, ale pojawił się komentarz w tym wpisie (Hosting idealny (php, postgreSQL, django, shell)?) i spojrzałem na ich stronę nowości i spodobała mi się ilość nowości dodanych w tym hostingu. Oto one:

e-learning :

tu akurat bez szaleństw, ale warte wzmianki(*):
drzewa genealogiczne (jak dla mnie raczej ciekawostka):
Tu z cyklu "pokaż mi inny hosting, który to potrafi":

Dlatego, jeśli szukasz naprawdę elastycznego hostingu, który ciągle się rozwija (i rozwija się w kierunku wskazywanym i sugerowanym przez użytkowników, czasem potencjalnych) to tylko vipserv.org

* Dlaczego lubię autoinstalacje? Tylko dlatego, że oszczędza mój czas. Czasem z chęcią przyjrzałbym się jakiemuś oprogramowaniu, ale nie chce mi się zakładać bazy, i przechodzić instalacji po to, żeby po 20 minutach to skasować. Jeśli instalacja oznacza dla mnie 2 kliknięcia - czasem się skuszę.

wtorek, 2 grudnia 2008

Zarządzanie wersjami oprogramowania

Najtrudniejsze zadanie z jakim się obecnie spotykam w mojej pracy to zarządzanie wersjami produktu.

Od jakiegoś czasu używamy Subversion i pewnie jest o niebo lepiej niż by było bez tego narzędzia, ale dalej jest cholernie ciężko. Szczególnie wtedy, gdy Ciągle wprowadzane są poprawki w bazie. Bywa tak, że mam trzy wersje do synchronizacji: developerską, do testów wewnętrznych i do testów klienta.

Druga zazwyczaj generuje typową ilość znalezionych bugów, a potem sporą ilość 'feature requests' (przegląda ją między innymi manager projektu, który ma dobre pomysły jak usprawnić pracę w systemie; sęk w tym, że poważnie utrudniają one pracę nad systemem).
Trzecia z kolei generuje szum pt. 'na stronie X, link Z jest w nie takim foncie jak potrzeba' i inne problemy ;)

*Razem tworzą żyzne środowisko, z którego co jakiś czas wylęgają się pomysły wymagające zmiany struktury bazy danych.

**Wszystkie trzy wersje są update'owane w różnych momentach, do różnych wersji repozytorium.

Z * i ** wynika, że powinno się zdarzać (i zdarza, a jak!), że na każdej wersji systemu jest inna wersja bazy danych.

Jeszcze nie koniec.

W teamie mam pewną ilość programistów, którzy nie rozwinęli jeszcze umiejętności polegającej na pisaniu kodu w taki sposób, aby był on jak najbardziej elastyczny (dzięki wszystkim bogom za CakePHP, bo przy przenoszeniu systemu na serwer klienta przepisywalibyśmy wszystkie linki... aż mnie ciarki przeszły). Jednocześnie nie mogę ich za to zdyscyplinować - przecież nie dostali żadnych wytycznych jak pisać kod (kto próbował coś takiego napisać, ten wie...)

No i to w sumie wystarczy, żeby przy końcówce projektu mieć poczucie braku kontroli.

Jednak jest pewne światełko w tunelu (nikt nie wyłączył z powodu kryzysu;)), o którym napiszę w następnym poście...
Niektóre wskazówki znajdziesz wponiższych postach:

Największy sklep internetowy w Polsce... agito pe el ...

...nie pozwala dokonać płatności kartą kredytową.

W ciągu sześciu lat istnienia, z dwuosobowej firmy oferującej podzespoły komputerowe i sprzęt fotograficzny, staliśmy się liderem wśród sklepów internetowych z elektroniką użytkową i wypracowaliśmy sobie mocną pozycję na rynku.

 Nie wiem jak Wy, ale ja nie kupię tam prezentów w tym roku ;)

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.