Archiwum

Archiwum dla ‘Symfony’ Kategoria

Symfony+Propel: domyślne sortowanie

Lipiec 9th, 2010 zergu Brak komentarzy

W przypadku gdy mamy wiele różnych metod pobierających kolekcje obiektów z bazy danych i chcemy, żeby były posortowane w podobny sposób — można dodać sortowanie w metodzie doSelect.

public static function doSelect (Criteria $criteria, PropelPDO $con = null)
{
  if (!$criteria->getOrderByColumns())
  {
     $criteria->addAscendingOrderByColumn (self::YEAR);
     $criteria->addAscendingOrderByColumn (self::MONTH);
     $criteria->addAscendingOrderByColumn (self::DAY);
  }
  return parent::doSelect ($criteria, $con);
}

Warunek if umożliwia nam zdefiniowanie dowolnego innego sortowania wcześniej. W przypadku, gdy jakaś metoda (np. getAll) nie definiowała sortowania za pomocą obiektu Criteria — zostanie użyte domyślne (tutaj: po dacie).

Formularze Symfony: select ograniczony przez wartość innego pola

Maj 12th, 2010 zergu Brak komentarzy

Opis na przykładzie Propela.

Problem
Jak ograniczyć liczbę opcji w sfWidgetFormChoice na podstawie wartości z innego pola formularza. Mowa o ograniczeniu stałym, zdefiniowanym w klasie formularza.

Rozwiązanie
Będąc w klasie formularza mamy dostęp do wartości odpowiadającego obiektu, tylko gdy dany formularz zostanie zainicjalizowany poprzez podanie obiektu do konstruktora (czyli przykładowo $this->form = new SampleForm ($sample_object)). Wtedy możemy pobrać jakąś informację z obiektu i na jej podstawie stworzyć Criteria ograniczające:

$c = new Criteria();
$c->add (APeer::XYZ, $this->getObject()->getAsd());
$this->widgetSchema['asd'] = new sfWidgetFormPropelChoice (array (
    'model' => 'B', 'criteria' => $c));
Kategorie:Symfony Tagi:

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.

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:, ,

Aktualizacja oprogramowania a wydajność

Grudzień 19th, 2008 zergu 2 comments

Krótkie spostrzeżenie na temat wydajności aplikacji (praktycznie nie wypełnionej danymi) po następujących aktualizacjach:

  • Symfony 1.1 → Symfony 1.2
  • Propel 1.2 → Propel 1.3
  • PHP 5.2.0 → PHP 5.2.6
  • PostgreSQL 8.1 → PostgreSQL 8.3

Prosty test został wykonany za pomocą ApacheBenchmarka:
ab -c 5 -n 300 -H 'Connection: close'. Jak widać symulacja bazowała na 300 użytkownikach, przy czym do 5 na raz wchodziło na stronę.

Przed

Próba 1:

Requests per second:    4.34 [#/sec] (mean)
Time per request:       1152.770 [ms] (mean)
Time per request:       230.554 [ms] (mean, across all concurrent requests)

Próba 2:

Requests per second:    4.79 [#/sec] (mean)
Time per request:       1043.581 [ms] (mean)
Time per request:       208.716 [ms] (mean, across all concurrent requests)

Po

Próba 1:

Requests per second:    5.33 [#/sec] (mean)
Time per request:       937.318 [ms] (mean)
Time per request:       187.464 [ms] (mean, across all concurrent requests)

Próba 2:

Requests per second:    5.39 [#/sec] (mean)
Time per request:       927.979 [ms] (mean)
Time per request:       185.596 [ms] (mean, across all concurrent requests)

Oczywiście z uwagi na brak testów pomiędzy poszczególnymi zmianiami ciężko jest powiedzieć coś więcej, niż tyle że warto aktualizować, choćby o tego jednego requesta na sekundę ;). Ot, taka ciekawostka.

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?