Symfony Framework
Framework Symfony został stworzony przez znanego pewnie wielu programistom Fabiena Potenciera. Jest on jednym z popularniejszych frameworków w naszym kraju, ale na świecie jednak nie zdołał zdobyć sobie takiego uznania, plasując się daleko w tyle za ZF-em, CakePHP oraz... CodeIgniterem, czego chyba nigdy nie zrozumiem. Reakcje, z jakimi do tej pory się na jego temat spotykałem, są różne. Część programistów chwali go za dużą liczbę generatorów, inni zaś wyklinają wydajność, a właściwie jej brak.
W przeszłości miałem już jedno podejście do Symfony, ale odrzuciła mnie od tego frameworka rzecz prozaiczna, mianowicie szablony PHP. Teraz od tego czy go opanuję, zależały losy świata, więc trzeba było zakasać rękawy, wziąć się w garść i zacząć pisać. Co z tego wynikło?
W dalszej części tekstu zamiast słowa framework będę używać słowa platforma. Takie tłumaczenie odkryłem w najnowszym polskim wydaniu książki Gangu Czworga i muszę przyznać, że jest warte uwagi.
Poziom trudności
Dla kogoś, kto pracował już z kilkoma platformami, nauczenie się kolejnej nie stanowi specjalnej trudności. Wszędzie mamy praktycznie te same elementy, wszędzie mamy niemal identyczną architekturę MVP+pasywny widok z tylko drobnymi różnicami, zatem jedyne, co nam potrzeba, to nauczenie się nowych konwencji oraz API. Dla osoby z doświadczeniem opanowanie Symfony na satysfakcjonującym poziomie to kwestia góra kilku dni aktywniejszej pracy z kodem. Jeśli nie znasz Doctrine, musisz też wziąć poprawkę na naukę korzystania z tego ORM-a oraz nauczenie się DQL-a.
Architektura
Symfony to niemal klasyczna implementacja wzorca MVP z pasywnym widokiem, czyli tego samego, który spotykamy w 95% platform dla aplikacji internetowych, jakie powstają. Mamy sobie warstwę prezentera dzielącą się na moduły i akcje, która zajmuje się większością rzeczy, mamy szablony pełniące rolę widoku, do którego pchamy dane, zaś za warstwę modelu robi kod biblioteki Doctrine. Dodatkowo istnieje mechanizm komponentów, czyli trójek akcja+szablon+model, które możemy kaskadowo wywoływać np. z poziomu szablonu. Pozwala to na wydzielenie pewnych wspólnych fragmentów aplikacji i ich opakowanie w celu wygodniejszego użycia w innych miejscach.
Stosunkowo ciekawą rzeczą jest sposób opakowania tego wszystkiego. Kod akcji, szablony oraz konfiguracja jest zapakowany w pojedynczym bycie zwanym modułem. Rozwiązanie to ma swoje plusy i minusy. Do niewątpliwych plusów należy fakt, że dany kod nie jest rozrzucony po całym projekcie. Jeśli piszemy obsługę profilu użytkownika, wszystko mamy w jednym miejscu. Niestety, w module nie są przechowywane pliki modelu oraz formularze, których szkielety są generowane domyślnie. W większych projektach oznacza to dla nas dalej dużo skakania po drzewie katalogowym.
Sam projekt możemy podzielić sobie na aplikacje, które współdzielą ten sam model oraz mają wspólną konfigurację bazową. Najczęściej wykorzystuje się to do podzielenia systemu na część publiczną oraz panel administracyjny. Dodatkowo, w projekcie możemy korzystać z różnych wtyczek. Zasadniczo nie ma specjalnych ograniczeń na to, co one dostarczają. Jedne wtyczki mogą dodawać np. dodatkowe kontrolki formularzy, inne całe moduły i akcje realizujące pewną całość. Tutaj dla twórców należy się duży plus, bowiem bardzo ułatwia to rozbudowę aplikacji. Osobnym tematem jest jakość części dostępnych w sieci wtyczek, ale tu ciężko za to winić twórców platformy.
Z wtyczkami nieodłącznie wiąże się temat konfiguracji. W Symfony postawiono na kaskadowość. Mamy globalną konfigurację, mamy konfigurację aplikacji, mamy konfigurację modułu, mamy konfigurację konkretnej wtyczki. Wszystko to jest miksowane razem i łączone w jedną całość i również ma to zarówno wady, jak i zalety. Do zalet na pewno warto zaliczyć możliwość przykrywania globalnych opcji lokalnymi ustawieniami. Jeśli w jakimś module potrzebujemy specyficznych ustawień, po prostu tworzymy odpowiedni plik konfiguracyjny w jego katalogu i po sprawie. W dużych projektach prowadzi to jednak do efektu zwanego konfiguracyjnym piekłem, gdzie mamy w ekstremalnych przypadkach kilkaset ustawień porozrzucanych po kilkuset miejscach tak, że ciężko jest nad tym zapanować. Ja osobiście preferuję sytuacje, gdy cała konfiguracja siedzi sobie w jednym miejscu. Kaskadowości to nie wyklucza, a bardzo ułatwia życie.
ORM zwany modelem
Symfony stoi ORM-ami. W początkowej fazie rozwoju główną biblioteką mapowania obiektowego podszywającą się pod model był Propel, który później musiał ustąpić miejsca Doctrine 1.x. Twórcy frameworka zrobili niewątpliwie dobry użytek z reprezentowania tabel bazodanowych jako obiektów. Wykonywanie typowych operacji jest bardzo proste, a także bardzo ułatwia integrację wielu mniejszych komponentów. W projekcie stworzyliśmy różne komponenty np. do komentowania czy oceniania różnych rzeczy. Wystarczyło dopisać sobie ich obecność do schematu, a następnie przekazać ich obiekt do magicznego pudełeczka, które zajmowało się już resztą. Taka architektura dobrze się też sprawdzała przy prostych formularzach oraz typowych CRUD-ach.
Problemy niestety zaczynały się przy próbie wejścia na wyższy poziom abstrakcji. Realizowany projekt jest dla mnie kolejnym dowodem, że w złożonych aplikacjach automatyczne ORM-y tak naprawdę mogą wyrządzić więcej szkody niż pożytku z powodu zbyt dużego rozczłonkowania poszczególnych obiektów. Spójrzmy sobie na ten blog. Logika jego bazy danych jest prosta, jak budowa cepa, podobnie jak hipotetyczny model obiektowy. Jednak co w sytuacji, gdy pojedynczy byt rzeczywisty jest opisywany przez np. 30 tabel? I nie mówimy tu o zwykłych informacyjnych tabelach, ale rozbiciu informacji, która pod wszystkimi względami jest częścią jednego i tego samego, na kilka tabel. Aż się prosi, by mieć do tego jeden obiekt. ORM tymczasem da nam tyle obiektów, ile tabel i musimy się z tym zwyczajnie pogodzić. Wewnętrznej budowy nie jesteśmy w stanie za bardzo ukryć, co w przyszłości może stwarzać trochę trudności przy rozbudowie.
Taki tryb pracy nie pozostaje też bez wpływu na wydajność. Symfony generalnie wymaga, aby większość rzeczy była rzutowana właśnie na obiekty nawet, jeśli specjalnie nie będziemy wykorzystywać faktu ich obiektowości. Tymczasem w dokumentacji Doctrine czytamy, że rzutowanie obiektowe nie powinno być nadużywane ze względu na duży narzut wydajnościowy. Tam, gdzie to możliwe, powinniśmy korzystać ze zwykłych tablic. W przypadku Symfony próba zastosowania się do tej rady napotyka wiele trudności, jako że nagle zostajemy bez 80-90% narzędzi, jakie platforma nam oferowała.
Bliskie przywiązanie do Doctrine ma też jedną, poważną wadę. Wystarczy spróbować oprzeć swój projekt na bazie innej, niż MySQL, by się o tym przekonać. Niestety w ostatnich miesiącach przekonałem się boleśnie, że twórcy tej biblioteki poza MySQL-em za bardzo świata nie widzą i silniki dla innych systemów bazodanowych są strasznie niedopracowane. Jeśli nie korzystamy z jakichś bardziej zaawansowanych funkcji, od biedy będzie to działać, tylko co z tego, kiedy nawet sobie indeksów porządnie nie możemy zdefiniować, bo w dokumentacji "zapomniano" wspomnieć, że pełne wsparcie dla konfigurowania porządku, długości itd. ma tylko MySQL? Skoro już korzystamy z takiego PostgreSQL-a, to najczęściej po to, by wykorzystać jego możliwości. Aby to zrobić, musimy przeprosić się z językiem SQL i PDO i klepać zapytania ręcznie. I tu zaczynają się największe jaja, jeśli chodzi o Symfony, bowiem nagle okazuje się, że w tej platformie poza Doctrinem/Propelem najzwyczajniej w świecie nic nie ma! Aby było śmieszniej, nawet ciężko jest sprawić, by coś było. Głupie stronicowanie trzeba klepać od zera, bowiem klasa sfPager jest... klasą abstrakcyjną dla stronicowania opartego o Propel/Doctrine i o wykorzystaniu jej do innych celów możemy spokojnie zapomnieć.
Podsumowując: albo używasz Doctrine, które w bardziej złożonych projektach potrafi narobić więcej problemów niż pożytku i które słabo radzi sobie z bazamy innymi niż MySQL, albo nie masz nic. Jeśli Symfony 2 będzie tak silnie oparte na Doctrine 2, to już z góry współczuję programistom, bowiem tam ograniczeń możliwości baz danych jest jeszcze więcej.
Szablony zwane widokiem
Co tu dużo mówić. Symfony to kolejna platforma, która potwierdziła, że używanie PHP do klepania szablonów to najbardziej idiotyczny pomysł na świecie i masochizm. Inne języki szablonów są skomplikowane i ograniczają? Hipokryzja, zważywszy co trzeba zrobić w przypadku PHP:
- Gołe PHP nie nadaje się do niczego, bowiem nic w nim nie ma.
- Zatem trzeba napisać mnóstwo funkcji wspomagających, helperów itd.
- Aby wiedzieć, jak z tych funkcji korzystać, trzeba się ich też nauczyć, udokumentować je itd.
- Co więcej, aby się biedni programiści nie zmęczyli i nie powiedzieli, że dana platforma jest głupia, trzeba napakować tam tyle magii, ile wlezie, żeby stworzyć u nich iluzję, że szablony PHP są proste.
- Magia sprawia, że nic nie jest tym, na co wygląda, a efekt jest taki, że połowa struktur kontrolnych PHP wariuje przy próbie zrobienia czegoś, co domyślnie powinno działać bez zająknięcia.
- Ergo, musimy się od nowa nauczyć, jak działają zmienne, pętle, na co uważać, czego nie robić, co nie zadziała, gdzie kryją się kruczki...
- I dowiadujemy się o tym najczęściej w praniu, bo twórcom Symfony niespecjalnie chciało się udokumentować dobre 3/4 rzeczy związanych z szablonami.
I wy ludzie śmiecie twierdzić, że szablony PHP są proste i czytelne w użyciu?! Buahahahahahahahahahahahahaha!!!!! Wybaczcie, ale musiałem :). Nie znam innej reakcji na fakt, ile czasu zmarnowałem, próbując obchodzić idiotyzmy helperów, magicznego escape'owania kodu HTML w zmiennych, a zwłaszcza obsługi formularzy, gdzie po prostu dzieją się cuda. Kod HTML do połowy widgetów jest zakodowany na sztywno, w ogóle nie współgra z tym, co robią graficy i praktycznie nie ma możliwości jego modyfikacji. Dlaczego? Bowiem aby to zrobić, potrzebna jest obiektówka, a dokumentacja do tego, jak pisać tzw. formattery zwyczajnie nie istnieje. Tę wiedzę tajemną posiadają tylko twórcy i wybrane grono osób, które przekazuje ją sobie z pokolenia na pokolenie. Dołączyć do niego można tylko trafiając przez przypadek na jakiś blog, gdzie któryś z wtajemniczonych nieopatrznie jakiś fragment takiego formattera opublikował.
Margia... arghhh...
Symfony 1.x to platforma starej daty i magia jest tu wszechobecna. Mamy ją w Doctrine, mamy ją w szablonach, mamy w normalnym kodzie. I mamy problem, bo to wszystko jest fajne, dopóki działa. Gdy nie działa, nagle okazuje się, że nie bardzo wiemy, gdzie zacząć szukać przyczyny. Zresztą, o negatywnych aspektach magii wypowiadałem się już szerzej w innym wpisie. Tutaj ograniczę się jedynie do adnotacji, że na obchodzenie tych różnych magicznych duperszmitów straciłem naprawdę mnóstwo czasu tylko przez to, że komuś zamiast get() zachciało się napisać __get().
Dokumentacja, wsparcie itd.
Jakość dokumentacji to jedna ze słabszych rzeczy tej platformy. Cała wiedza jest porozrzucana po kilku niezależnych od siebie podręcznikach tak, że do końca nawet nie wiadomo, czego gdzie szukać, a nawet i wtedy dany rozdział nie omawia wszystkiego, koncentrując się bardziej na skończeniu Jobeet. Fajnie się to może i czyta do poduszki, tylko co z tego, kiedy poszukujemy konkretnej informacji o tym, jak używać konkretnego elementu i dosłownie cholera człowieka trafia. Dramatyzm podnosi dodatkowo fakt, że na stronie internetowej mamy dokumentacje do wszystkich pięciu głównych wydań Symfony oraz że pomiędzy wydaniami 1.2 i 1.3 zmieniły się nazwy części podręczników. Wyszukiwarka oczywiście zaindeksowała każdą z nich, dzięki czemu po wpisaniu danej frazy w Google najczęściej wyskoczy Ci wszystko, tylko nie Twoja wersja platformy.
Równie tragicznie prezentuje się dokumentacja API. Jest to klasyczny przykład tego, jak nie powinno się dokumentować kodu. Większość opisów to zwykłe masło maślane: setUpSomething() - sets up something. A jakieś szczegóły? Gdzie to jest wykorzystywane? Czy tego potrzebuję? Jeśli tak, to w jaki sposób? Ponadto jeszcze pół biedy, jak jest jakiś opis, bowiem niespecjalnie zadano sobie trud, aby ten opis w niektórych przypadkach w ogóle był. Taka kuriozalna sytuacja występuje m.in. ze wspomnianymi już formatterami. W podręczniku czytamy, że aby dowiedzieć się, jak zrobić własny formatter, musimy zajrzeć do API. Zaglądamy do API, a tam nie ma nic o formatterach. Formaty daty do helperów? Trzeba poszukać po blogach, albo w kodzie źródłowym. Czym się różni configure() od setup() w sfForm i dlaczego część kodu wykorzystuje jedno, a inna drugie? Zostaje tylko analiza kodu źródłowego.
Wydajność
Niestety, Symfony do demonów szybkości nie należy. W trybie debug praca z tą platformą przy większym projekcie to koszmar, a pojedyncze żądanie może wykonywać się nawet 10 sekund. Nie muszę dodawać, że czekanie, aż biedny PHP policzy wszystko strasznie spowalnia prace. W trybie produkcyjnym Symfony ratuje ekstremalny cache, ale wtedy pojawiają się problemy z jego aktualizacją. Swoje pięć groszy dokłada Doctrine, gdzie najczęściej musimy korzystać z powolnego rzutowania do obiektów. Może jedynie cieszyć, że wreszcie twórcy połapali się, że to jednak jest nienajlepsza droga rozwoju i zaczęli dbać o to, by robić rzeczy szybciej.
Podsumowanie
Symfony mnie nie zachwycił. Platforma ta powinna raczej nazywać się Magic albo Hogwart, bo symfonie charakteryzują się raczej elegancją i wewnętrzną spójnością, a tego o Symfony powiedzieć nie mogę. Mnóstwo magii, niespójności i braków, zwłaszcza jeśli chcemy wyjść poza jakieś podstawowe ramy. Nie ma się co czarować, przy większych projektach na pewno będziemy musieli z konieczności wyjść poza nie, a tu jest z tym poważny problem. Swoje pięć groszy dokłada też kiepska dokumentacja.
Oczywiście nauka Symfony 1.x i próba wiązania z nim przyszłości to teraz średni pomysł z racji zbliżającego się końca prac nad dwójką. Poprawiono tam przynajmniej część z moich uwag - wszystko wskazuje na to, że sami twórcy doszli do podobnych wniosków, co ja i postanowili na bazie tego stworzyć coś lepszego. Wersja 1.x dobrze nadaje się do stworzenia zaplecza dla typowych stron internetowych; jakiś nieduży CMS, może blog, strona domowa czy galeria internetowa. Ja realizowałem większy projekt, doskonale pamiętam wszystkie problemy, jakie napotkałem przy próbie zgrania tego wszystkiego do kupy i widzę potencjalne problemy wydajnościowe oraz związane ze skalowalnością, jakie mogą pojawić się w przyszłości. Jak ktoś się uprze, to napisze wszystko, co chce, ale jeśli można to zrobić łatwiej i szybciej?
Naturalnie nie wszystko w Symfony jest złe - wspominam o tym, by ktoś nie wyciągnął pochopnych wniosków. Po prostu trzeba mieć na uwadze to, że platforma ta jest ukierunkowana na specyficzne potrzeby. Jeśli się w nie wstrzelimy, dzięki choćby admin generatorowi praca idzie dużo szybciej. Znalazłem w niej kilka ciekawych koncepcji, które z pewnością przydadzą się w dalszych pracach nad Trinity...






Napisał destroyer w poniedziałek, 28 lutego 2011 o 19:54
Nie dowierzam, że postanowiłeś napisać na koniec coś pozytywnego o symfony. Wpis zacząłeś od wpadki na temat widoków, ponieważ symfony je posiada.
Jest frameworkiem z architekturą MVC, to że korzystałeś z niego jak z MVP, to już co innego. Model w postaci ORM oczywiście sprawdza się w CRUD, dla bardziej skomplikowanych rzeczy tworzysz własny model np. w DDD.
Spora część to subiektywne odczucia i do nich nie ma sensu się odnosić.