Archiwum

Posty oznaczone ‘Symfony’

Walidacja jednego pola formularza w symfony

Marzec 31st, 2009 reinmar 2 comments

Natknąłem się ostatnio na problem. Otóż wszystkie formularze walidujemy przy wykorzystaniu symfoniowych Formsów. Standardowe użycie podczas aktualizacji obiektu Place wygląda następująco:

$this->form = new PlaceForm ($place);
$this->form->bind ($request->getParameter ('place'));

if ($this->form->isValid())
{
	$place = $this->form->updateObject();
	$place->save();
}

Co jednak kiedy nie chcemy walidować całego formularza? Np. w przypadku kiedy używając InPlaceEditora zmieniamy jedno pole. Podpowiem, że próba wysłania tylko tego jednego pola i wykorzystaniu tej samej akcji skończy się wyzerowaniem pozostałych pól.

Dokumentacja symfony niestety milczy na ten temat (choć przyznam się, że nie szukałem jakoś strasznie dokładnie), a wiemy chyba wszyscy co jest jej największą bolączką – opisanie tylko kilku funkcjonalności poszczególnych modułów. Jak się chce czegoś więcej, to droga wolna – szukajcie sobie sami w kodzie.

Tak też postąpiłem. I po koszmarnie długim czasie odkryłem, że do walidowania każdego z pól służy metoda o intuicyjnej nazwie… clean(). Przykład walidacji jednego pola:

$form = new PlaceForm($place);
try
{
	$cleaned = $form->getValidator($field_name)->clean($value); #1

	$place->setByName($field_name, $cleaned, BasePeer::TYPE_FIELDNAME); #2
	$place->save();
}
catch (sfValidatorError $e) #3
{
	#Obsługa błędu
}
  1. Pobranie odpowiedniego walidatora oraz wywołanie na nim metody clean(), która w przypadku, gdy pole ma poprawną wartość zwraca je. A w przypadku błędu wyrzuca wyjątek sfValidatorError,
  2. Zapisanie wyczyszczonej wartości tego pola do obiektu,
  3. Kod obsługujący przypadek niewalidującego się pola.

Mam nadzieję, że ten krótki artykuł pozwoli komuś zaoszczędzić trochę czasu.

Wpis opublikowałem także na prywatnym blogu reinmar.jogger.pl.

Sortowanie obiektów po dacie utworzenia

Luty 23rd, 2009 zergu 1 komentarz

Czasami istnieje potrzeba posortowania obiektów wg własnych kryteriów — dajmy na to w przypadku, gdy w jednej tablicy mamy obiekty różnych typów. Aby jednak sortowanie mogło mieć sens, potrzebne jest jakieś wspólne pole. W tym przykładzie chcemy sortować po dacie utworzenia, więc z założenia wynika, że obiekty będą miały pole created_at.

Do sortowania może wykorzystać funkcję usort:

bool usort ( array &$array , callback $cmp_function )

Jako argumenty przyjmuje ona tablicę i funkcję do porównywania (tzw. callback).

Funkcja porównująca przyjmuje za argumenty wartości do porównania (tutaj: obiekty) i zwraca -1, 0 lub 1 aby określić czy pierwsza wartość jest odpowiednio mniejsza, równa czy większa. W naszym przypadku znaki są zamienione by posortować „od najnowszego” oraz, dla uproszczenia, pomijamy zwracanie zera, ponieważ jego wystąpienie i tak jest mało prawdopodobne, a ponad to nie istnieje kolejne kryterium porównania, więc w przypadku takiej samej daty, kolejność dwóch obiektów jest dowolna:

public static function compare_by_date ($o1, $o2)
{
    return ($o1->getCreatedAt ('U') <= $o2->getCreatedAt ('U')) ? +1 : -1;
}

Metoda korzysta z dość typowego w aplikacjach Symfony (opartych na Propelu) gettera getCreatedAt, który przyjmuje format daty w takiej samej postaci jak funkcja PHP date. W tym przypadku jest to tzw. Unix epoch oznaczający liczbę sekund od początku roku 1970. A sekundy (czytaj: liczby całkowite) porównuje się już bez problemu.

Mając gotową taką funkcję (tutaj jako metoda klasy std) możemy posortować tablicę obiektów poprzez wykonanie:

usort ($images_and_videos, array ('std', 'compare_by_date'))

Kategorie:PHP Tagi:,

Dostosowanie grep-a do SVN i Symfony

Luty 16th, 2009 zergu Brak komentarzy

Jednym z nieocenionych narzędzi podczas programowania jest grep, który (jakby ktoś nie wiedział) służy do wyszukiwania treści w plikach tekstowych.

Jednak, jak każde narzędzie, warto go dostosować do własnych potrzeb. Ja najczęściej stosuję go do przeszukiwania projektów będących pod kontrolą Subversion (SVN), poprzez taką instrukcję:

grep -RIin "szukane" * --exclude-dir=\.svn

Dla wyjaśnienia: -R — szukanie rekursywne w podkatalogach, -I — pomijanie plików binarnych, -i — niewrażliwość na wielkość znaków, -n — wyświetlenie nr linii w wyniku.

Oczywiście wpisywanie tego za każdym razem nie ma nic wspólnego z wygodą, dlatego warto sobie utworzyć funkcję dla powłoki np. w pliku .profile (średniki na końcu są wymagana dla Basha, Zsh ich nie potrzebuje):

function g { grep -RIn $1 * --exclude-dir=\.svn ; }
function gi { grep -RIin $1 * --exclude-dir=\.svn ; }

Dzięki czemu wystarczy wykonać:


[src]> g fuck
arch/i386/kernel/cpu/cpufreq/powernow-k7.c:558: * Some Athlon laptops have really fucked PST tables.
arch/i386/kernel/cpu/mtrr/generic.c:70:/*  Some BIOS's are fucked and don't set all MTRRs the same!  */
…

Z racji tego, że większość projektów piszę w Symfony, stworzyłem sobie dodatkową wersję tej funkcji:

function sfg { grep -RIn $1 lib/model apps/ --exclude-dir=\.svn ; }

Odpalając ją w głównym katalogu projektu, można łatwo przeszukać wszystkie warstwy aplikacji (model, widoki, kontrolery i przy okazji parę innych).

Jest też możliwość włączenia „na stałe” niektórych opcji, poprzez wyeksportowanie zmiennej środowiskowej o nazwie GREP_OPTIONS, np.:

export GREP_OPTIONS="--exclude-dir=\.svn"

Ale ja osobiście tego nie stosuję.

Kategorie:Różne Tagi:, ,

Propel: lazyLoad

Styczeń 31st, 2009 zergu Brak komentarzy

Użycie LazyLoadingu w Propelu jest bardzo proste, jednak słabo udokumentowane. Polega ono na dodaniu lazyLoad: true w schemacie bazy danych (dla wybranych kolumn). Przykładowy fragment schematu:


  images:
    _attributes: { phpName: Image }
    id: { type: serial }
    data_large: { type: blob, lazyLoad: true }
    data_small: { type: blob, required: true, lazyLoad: true }
    …


Czym jest LazyLoad?

Dla nieznających tego mechanizmu wyjaśnienie. LazyLoad to technika stosowana w warstwie modelu dla wybranych kolumn tabeli. Atrybuty oznaczone do LazdyLoadingu pobierane są tylko i wyłączenie, gdy nastąpi jawne zapytanie o nie. Przykładowo (bazując na powyższym kawałku schematu), podczas wyszukiwania zdjęć, chcemy dostać miniaturki wyników — jednak bez tego mechanizmu pobrane byłyby również duże wersje zdjęć, które w aktualnym widoku nie są potrzebne, a to może bardzo spowolnić wygenerowanie strony.

Kategorie:Symfony Tagi:, ,

Optymalizacje: sfPropelPager::getResults()

Styczeń 9th, 2009 zergu Brak komentarzy

Obiekt sfPropelPager nie potrafi „keszować” w sobie wyników zapytania do bazy. Innymi słowy każde wywołanie metody getResults() odpytuje bazę danych. Dlatego też, trzeba uważać czy nie wywołuje się tej metody więcej niż jeden raz podczas jednego żądania (np. przy kilkukrotnym iterowaniu po wynikach).

Kategorie:Symfony Tagi:, ,

Symfony: Paginacja przy własnych/nietypowych warunkach SQL

Styczeń 5th, 2009 zergu Brak komentarzy

Pobieranie obiektów bez wykorzystywania obiektu klasy Criteria jest dość proste. Jednak standardowy paginator w Symfony ma taką właściwość, że jako parametr przyjmuje obiekt tejże klasy. Stwarza to trochę problemów, gdy odpytujemy bazę w niezbyt typowy sposób, a chcemy podzielić wyniki na strony. Przykład: w wyszukiwarce sprawdzamy czy połączone kolumny pasują do podanej frazy.

Prawdę mówiąc nie wiem jak to zrobić w Propelu 1.2 (tym samym Symfony < 1.2), jednak w wersji 1.3 jest już na to sposób dzięki Criteria::CUSTOM. Przykładowo:


$c = new Criteria();
$c->add (CarPeer::BRAND,
         CarPeer::BRAND."||' '||".CarPeer::MODEL.' ILIKE \'%'.$phrase.'%\'',
         Criteria::CUSTOM);
…
$pager->setCriteria ($c);

Rzeczą, na którą koniecznie trzeba zwrócić uwagę, jest podanie dowolnej kolumny (pierwszy parametr), mimo że nie będzie ona wykorzystywana. Jako drugi parametr podajemy już dokładnie to co ciężko było normalnie zbudować wykorzystując propelowe kryteria (może to być fragment czystego SQL występującego po klauzuli WHERE). Ostatni parametr jest narzucony i służy do aktywowania całej magii.

Kategorie:Symfony Tagi:, ,

Migracja MySQL → PostgreSQL (aplikacji w Symfony 1.1)

Grudzień 15th, 2008 zergu Brak komentarzy

Jako, że w MySQL nie dzieje się najlepiej postanowiliśmy zmigrować jeden z naszych projektów na Postgresa. Bazuje on na Symfony 1.1, co oznacza starego Propela 1.2.

Samo przestawienie połączenia nie stwarzało większych problemów, wystarczyło w databases.yml i propel.ini przerobić mysql na pgsql:


all:
  propel:
    class:          sfPropelDatabase
    param:
    persistent:       true
    phptype:          pgsql
…

propel.database            = pgsql
propel.database.createUrl  = pgsql://zdzislaw@localhost/
propel.database.url        = pgsql://zdzislaw@localhost/piwo

Oczywiście o przebudowaniu modelu, utworzeniu bazy i jej struktury nie ma co wspominać :)

Brak SERIAL

Niestety, propelowy generator skyptu SQL tworzącego schemat bazy nie uraczył nas typem SERIAL przy identyfikatorach, jednakże w samej aplikacji nie stwarza to problemów, ponieważ sekwencje i tak są wykorzystywne. Trzeba po prostu uważać podczas zabawy w CLI Postgresa.

Dane binarne pobierane „ręcznie”

Drobny problem pojawił się, w miejscach, gdzie pobieraliśmy dane binarne (BLOB/BYTEA) własnym zapytaniem, omijając nieco wbudowany we framework Active Record. Dane takie trzeba było dodatkowo „od-eskejpować” funkcją stripcslashes.

AKTUALIZACJA:
Po aktualizacji projektu do Symfony 1.2 (chociaż tutaj właściwie chodzi o aktualizację Propela do wersji 1.3) okazało się, że żadne stripcslashes nie będzie potrzebne, ponieważ dane binarne (blob) są pobierane jako… stream. Innymi słowy, przy zapytaniach tworzonych samodzielnie, do akcji musiała wkroczyć funkcja stream_get_contents.

GROUP BY

Najwięcej problemów sprawiło grupowanie, ponieważ Postgres ostro trzyma się standardów i wymusza podanie wszystkich kolumn, które są używane, w klauzuli GROUP BY. MySQL natomiast „po cichu” sam sobie je dołączał. Różnicę w zachowaniu prezentuje poniższy (nie mający większego sensu) przykład:

psql=# select sum(id), id from users;
ERROR: column "users.id" must appear in the GROUP BY clause or be used in an aggregate function

mysql> select sum(id), id from users;
+---------+----+
| sum(id) | id |
+---------+----+
| 45      | 1  |
+---------+----+

CONCAT

Łącznie ciągów znaków odbywa się całkiem inaczej w MySQL i PgSQL. W tym pierwszym wykonuje się to za pomocą funkcji CONCAT(), natomiast w drugim wykorzystuje się operator ||.

FORMAT

W naszym kodzie używaliśmy MySQL-owego FORMAT(), aby ograniczyć liczbę cyfr po przecinku, którego w Postgresie nie ma. Można by tutaj użyć funkcji ROUND(), obecnej i tu i tu, jednak zdecydowaliśmy się przenieść rozwiązanie tego problemu do warstwy języka programowania.

LIKE

LIKE w Postgresie, w przeciwieństwie do MySQL, rozróżnia wielkość znaków. Jednak naprawa tej przypadłości sprowadza się jedynie od zamiany LIKE na ILIKE.

?

Na koniec coś, czego do końca nie udało nam się zrozumieć. Zaraz po przestawieniu konfiguracji na pgsql, strona przestała odpowiadać (a dokładniej odpowiadała gdy połączenie z bazą przekroczyło czas oczekiwania). Niemniej jednak Postgres sam w sobie działał, jak również możliwe było połączenie z „czystego” PHP. Jedynie aplikacja Symfony dziwnie się zachowywała. W logach pojawiało się następujące stwierdzenie:
Dec 4 11:20:22 myhost httpd: [wrapped: Could not connect [Native Error: pg_pconnect() [function.pg-pconnect]: Unable to connect to PostgreSQL server: server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request.] [User Info: host=localhost port=80 dbname='piwo' user='zdzislaw'] [User Info: Array]]

Taka sama sytuacja powtórzyła się na dwóch różnych maszynach (jednak na tym samym systemie operacyjnym). Niezrozumiałe jest to, że w czasie różnych zabaw z konfiguracją wszystko wracało do normy, jednak nie bardzo da się znaleźć konkretną przyczynę. Innymi słowy nie potrafimy zreprodukować tej sytuacji.

Krótko podsumowując — migracja rozwiniętego projektu w Symfony nie jest bardzo trudna, chociaż zawsze mogłaby być mniej problematyczna, gdyby unikać rozwiązań specyficznych dla danego systemu baz danych. Ale czy warto?