Trochę czasu minęło od ostatniej notki. Niestety najpierw obrona projektów na uczelni I jakieś kolokwia, a później sporo projektów. Straszne… Niemniej jednak wita was nowy wpis rozszerzający ostatnie refleksje na temat Kohany i świetnego modułu jakim jest Formo.

Kilka słów wstępu do Formo ORM

W dużym skrócie: co robi omawiany plugin? Ano, robi całkiem dużo; na domyślnej konfiguracji generuje funkcjonalne formularze, opierając się tylko I wyłącznie na strukturze wybranej tabelki. Dzięki temu możemy się bardziej poświęcić doborowi pól w bazie I generalnie całą logiką, nie przejmując się zbytnio przeprowadzaniem propagacyjnych poprawek w kontrolerze żeby całość w ogóle działała.

Jedyne co robimy, to poprawiamy wyświetlane tytuły pól, ich typy, czy wyświetlane pola, oraz – kolejność. Całość w bardzo sprytny I przemyślany sposób :) Jakie są wady? Trochę ogranicza nam to pole do popisu w kwestii np. wyglądu całego formularza. Wszystkie pola są mocno generyczne, co przekłada się także na generyczność całego formularza. Na tym etapie chęć zrobienia czegoś niestandardowego należy pogodzić ze zrobieniem specjalnego drivera w formo, który by obsługiwał nasze widzimisię.

Dodatkowo dochodzą kolejne problemy, gdybyśmy chcieli dorzucić kolejność wyświetlania pól. Przykład: jeśli do modelu dorzucimy informację o kolejności to formularz straci wszelkie dorzucone do niego htmlowe wstawki. Być może da się to jakoś swobodnie ominąć, ale w moim przypadku zmiana podejścia okazała się nieco korzystniejsza.

Plugin w akcji

Chcąc w nowych formularzach używać plugina do ORM należy go załadować. Najlepiej w pliku konfiguracyjnym formo.php w katalogu config. Wystarczy dodać linijkę:

$config['plugins'] = array('orm');

Rzecz kolejna to przerobienie swoich modeli tak by dziedziczyły po kohanowej klasie ORM. Klasa ta jest rozszerzeniem standardowego Modelu – wnosi trochę wyższy stopień abstrakcji. Do tak przerobionego modelu dodajemy kilka zmiennych, które będą opisywać to jak formo orm ma wyświetlić wygenerowany formularz. W dokumentacji formo orm znajduje się opis bodaj 5 różnych. Nam wystarczą 2 najważniejsze zmienne:

public $formo_ignores = array ( 'NAZWA_POLA', [...] );  
public $formo_defaults = array (  
    'NAZWA_POLA' => array ( 
        'type' => 'textarea', 
        'label' => 'Tytuł pola', 
        'rows' => 15, 
        'required' => '0', 
        'class' => 'textarea'
    ), [...] );

$formo_ignores, jak sama nazwa wskazuje ignoruje niektóre pola I ukrywa je z formularza. Wypisujemy tu tylko te pola, których w ogóle nie uzupełniamy, a nie te, które chcemy ukryć przed użytkownikiem, ale czymś I tak uzupełniamy sami. Najlepiej wypisywać tu tylko pola z identyfiatorami. Ukrywanie pól przed użytkownikiem będzie niżej.

$formo_default, opisuje zachowanie poszczególnych pól na formularzu, ich wygląd, itd. itp. Tutaj uzupełniamy właściwości tak jakbyśmy opisywali to pole klasycznie, bez użycia ORM-owego dobrodziejstwa. W naszym kontrolerze wykorzystanie i użycie orma wyglądać będzie mniej więcej tak:

$model = new Your_Model($id); 
$form = Formo::factory()->orm($model)->add('Submit'); 
if($form -> validate())  
{ 
    $form->save(); 
    $model->save(); 
    url::redirect(url::site()); 
} 
$this->template->content = $form;

I już! Mamy w pełni funkcjonalny formularz w kilkanaście raptem linii kodu. Ok, prawie. Brakuje w nim kilku rzeczy, ale o tym poniżej ;)

Formo ORM Issues

Ukrywanie niektórych pól. Czasem możemy chcieć zrobić tak by formularz dodawania nowego elementu nie wyświetlał niektórych pól (np. daty dodania i daty edycji – co w momencie dodawania jest logiczne). Nie możemy tych pól dorzucić do ignorowanych, bo ani ich nie ustawimy (nie będzie ich w modelu), ani nie zmusimy modelu do zajęcia się nimi. Generalnie nie widziałem nigdzie jakiegoś czystego rozwiązania tej kwestii, dlatego moje rozwiązanie może wyglądać na trochę ‘hack-owe’, ale przynajmniej działa :D W formularzu dodawania wystarczy w chainie tworzącym form dodać coś takiego:

->set('date_created', 'open', '<p style="display: none; ">');

Dzięki temu pole będzie, ale niewidoczne. Mało przejrzyste i czyste rozwiązanie… ale cóż Akcje automatyczne W przykładzie powyżej mamy pole date_created, byłoby fajnie, gdyby dało się zrobić tak, by to pole automatycznie się uzupełniało w momencie dodawania rekordu do bazy, a pole date_edited uzupełniało się przy każdej edycji, tak by nie trzeba było pisać tej logiki w kontrolerze. Można to zrobić na bazie bezpośrednio przez jakieś triggery – dla miłośników tego typu rozwiązań; lub w nieco bardziej humanitarny sposób – bezpośrednio na Modelu. Możemy bowiem nadpisać funkcję settera by dostosować ją do naszych potrzeb.

public function __set($key, $value)  
{ 
    if($key == 'date_created' && $value == null) 
        $value = date ("Y-m-d H:i:s"); 
    if($key == 'date_edited') 
        $value = date ("Y-m-d H:i:s");

       parent::__set($key, $value); 
}

Funkcja __set wykonywana jest dla każdego nieukrytego pola z formularza w momencie zapisu danych w modelu przez $model->save(). Podobnie możemy zrobić z pobieraniem danych z bazy (używając __get) aby niektóre dane przed wyświetleniem troszkę zmodyfikować. Kwestia obrazków i innych plików Można było podejrzewać, że kwestia wgrywania plików i przypisywania ich do danego rekordu w bazie może być problematyczna, jednak paradoksalnie, ten mechanizm działa bez zarzutów (jeśli zastosowaliśmy niektóre poprawki zaproponowane przez Bełdzia). Jedyny problem jaki możemy zaobserwować to to, że jak już wgramy plik dla danego rekordu, a potem ten sam rekord znów wyedytujemy (bez nadpisywania wgranego pliku) i zapiszemy, to powiązanie z naszym plikiem przepadnie. Jest to poniekąd logiczne, gdyż dane są zapisywane bezwarunkowo, a brak wgranego pliku jest po prostu pustą wartością, która nadpisze link do naszego, poprzednio wgranego pliku. Problem rozwiązujemy w sposób analogiczny jak poprzedni issue, poprzez dopisanie reguły na __set. A, I jeszcze jedna uwaga. Jeśli wgrywamy jakiś plik to musimy w kontrolerze dopisać 4 linijki po ukończonej walidacji:

$imgField = 'fileDbField'; 
$vals = $form->get_values(); 
$model->__set($imgField, $vals[$imgField]); 
$model->save();

Czemu tak? Na etapie form->validate(), formularz zawiera wskazanie na plik, będący jeszcze w lokalizacji tymczasowej i wtedy też model uzupełniany jest danymi z formularza. Dopiero po uzupełnieniu wgrany plik jest przenoszony do miejsca docelowego i jego nazwa ulega zmianie. Dlatego też wymagana jest aktualizacja jego danych w modelu. Jest to jedno z możliwych rozwiązań; takie mniej inwazyjne rzekłbym. Problem być może leży w samej logice wgrywania plików przez moduł Formo, ale to już podchodzi pod dość radykalną zmianę w logice modułu.

Krótkie podsumowanie

Przedstawiłem dziś krótki opis tego co udało mi się odkryć podczas mojej przygody z tym modułem w projektach. Mam nadzieję, że wpis ten będzie pomocą, dla wszystkich tych, którzy mają jeden z napotkanych przeze mnie problemów lub inspiracją dla tych, którzy nie znają dobrodziejstwa Kohanowego ORMa. Pozdrawiam