Na codzień pracuję w Zend Framework. Praca jest bardzo przyjemna, framework jest fajnie napisany, obiektowo, elastycznie (wiele klas można rozszerzyć, jest wiele miejsc, w których domyślne klasy są gotowe do uruchomienia naszego kodu, itp.). Ale ma też wiele wad i braków, które często są rozwiązane w innych frameworkach. Twórcy ZF wiedzą o wielu z nich i obiecują, że szykowana wersja 2.0 będzie lepiej przemyślana i napisana. Ale póki nie mamy wersji 2.0 (a nawet jak będzie, nie wiadomo, czy wszystkie problemy zostaną weliminowane) trzeba sobie jakoś radzić. Jednym z problemów, które spotykam w niemal każdym projekcie, jest mechanizm zapisywania danych wysłanych przez użytkownika.
Generalnie zapisywanie informacji w większości przypadków wygląda bardzo podobnie: po wejściu na stronę użytkownik otrzymuje formularz, który wypełnia i zatwierdza; jeśli dane przesłasne są poprawne są zapisywane do bazy (tworzony jest nowy obiekt lub aktualizowany istniejący); jesli niepoprawne - użytkownik otrzymuje ten sam formularz z wpisanymi już wcześniej danymi i komunikatami o błędnych danych. Bardzo często powtarzający się schemat. Czemu więc nie ułatwić sobie życia?
Co jest potrzebne? Formularz, widok, kontroler i model.
Formularz powinien zapewniać (poza wyświetlaniem formularza) pełną walidację i filtrowanie danych. Np. edycja danych kotantaktowych, która zawiera między innymi email i telefon powinna sprawdzać oba pola. Do emaila jest klasa w Zendzie, numer telefonu może być np. wyrażeniami regularnymi. Pola obowiązkowe, listy (np. państw czy województw) itp. - to wszystko sprawa formularza i wiele narzędzi przydatnych w tym temacie jest już w Zendzie. Klasę formularza do kontrolera można tworzyć osobno albo korzystać z plików konfiguracyjnych (ini, xml) na podstawie których Zend potrafi taki formularz przygotować (niżej opisany kontroler korzysta z pierwszego sposobu).
Widok - tu wystarczy
<?php echo $this->form; ?>
jeśli pod form przypisany jest obiekt klasy Zend_Form. To co przed i po formularzu można dopisać. Za wyświetlanie/renderowanie formularza powinnien odpowiadać obiekt i jego dekoratory. W ostateczności można uciec się do dekoratora ViewScript, ale nadal w widoku akcji kodu formularza nie ma.
Jeżeli w całej aplikacji będziemy stosowali podobne nazewnictwo akcji i parametrów, to każdy kontroler może dziedziczyć wszystko po jednym - wspólnym. Ja polecam napisanie takiego kontrolera abstrakcyjnego, który będzie działał z domyślnymi akcjami create i update, a w kluczowych miejscach będzie wykonywał opcjonalne metody, które w konkretnych kontrolerach można rozszerzać: (1) przed edycją, (2) po edycji z sukcesem, (3) po edycji z błędami.
Przykład kontrolera:
<?php
abstract class Application_Controller_Action extends Zend_Controller_Action
{
/**
* @var string
*/
protected $_suffixNameByController;
/**
* @return string
*/
protected function _getSuffixNameByController()
{
if(null === $this->_suffixNameByController) {
$controllerName = $this->getRequest()->getControllerName();
$filter = new Zend_Filter_Word_UnderscoreToCamelCase();
$this->_suffixNameByController = $filter->filter($controllerName);
}
return $this->_suffixNameByController;
}
/**
* @param array $nameParts
* @return string
*/
protected function _getPrefixedNameByController($nameParts)
{
$nameParts = (array)$nameParts;
$nameParts[] = $this->_getSuffixNameByController();
return implode('_', $nameParts);
}
/**
* @return string
*/
protected function _getDbTableNameByController()
{
return $this->_getPrefixedNameByController(array('Application', 'Model', 'DbTable'));
}
/**
* @return Application_Model_DbTable
*/
protected function _getDbTableByController()
{
$dbTableClass = $this->_getDbTableNameByController();
return new $dbTableClass;
}
/**
* @return string
*/
protected function _getFormNameByController()
{
return $this->_getPrefixedNameByController(array('Application', 'Form'));
}
/**
* @return Zend_Form
*/
protected function _getFormByController()
{
$formClass = $this->_getFormNameByController();
return new $formClass;
}
/**
* @param $row Application_Model
* @return void
*/
protected function _afterSave($row = null)
{
if(null === $row) {
$url = $this->view->url(array(
'action' => 'index',
'id' => null
));
}
else {
$url = $this->view->url(array(
'action' => 'read',
'id' => $row->getId()
));
}
$this->_redirect($url, array('code' => 303));
}
/**
* @return Application_Model
*/
protected function _loadObjectFromId()
{
$id = (int)$this->_getParam('id');
if($id < 1)
throw new Zend_Exception('No valid ID found');
$table = $this->_getDbTableByController();
return $table->find($id)->current();
}
/**
* @return void
*/
public function createAction()
{
$form = $this->_getFormByController();
if($this->getRequest()->isPost()) {
if($form->isValid($this->getRequest()->getPost())) {
$dbTable = $this->_getDbTableByController();
$row = $dbTable->createRow();
$row->setFromArray($form->getValues());
$row->save();
$this->_afterSave($row);
}
}
$this->view->assign(array(
'form' => $form
));
}
/**
* @return void
*/
public function updateAction()
{
$row = $this->_loadObjectFromId();
$form = $this->_getFormByController();
if($this->getRequest()->isPost())
{
if($form->isValid($this->getRequest()->getPost())) {
$row->setFromArray($form->getValues());
$row->save();
$this->_afterSave($row);
}
} else {
$form->populate($row->toValues());
}
$this->view->assign(array(
'row' => $row,
'form' => $form
));
}
}
Klasa "zgaduje" nazwę formularza i modelu tabeli na podstawie nazwy kontrolera w zapytaniu, więc używając standardowych nazw i operacji, akcje create i update można w końcowych kontrolerach pominąć. Służą do tego pierwsze metody w klasie. Są rozbite na mniejsze, ale zawsze uważam, że dobrze być przygotowanym - sama nazwa klasy formularza może też być potrzebna.
Przy edycji obiektu, ID należy przekazać w parametrze id, np. http://www.example.com/article/update/id/563.
Na koniec zostaje jeszcze model.Powyższy kontroler wykorzystuje standardowe metody ZF - createRow() i setFromArray(array()). W klasie wiersza można nadpisać metodę setFromArray() gdyby np. było potrzeba zapisać dane z innych tabel (połączenie z inną tabelą, itp.) - rzeczy, które wyświetla i sprawdza formularz, a kontroler nie musi nie powinien się już tym zajmować.
Co po zapisaniu danych? Uruchamiana jest metoda _afterSave(), która domyślnie przekierowuje do akcji read z ID ustawionym na ID zapisanego właśnie wiersza (wymaga w nim metody (getId()), ale jest przygotowana na bycie nadpisaną i wykonanie cokolwiek będzie potrzebne w danym projekcie/kontrolerze.
Jak tego użyć? Jeżeli będziemy trzymali się w projekcie kilku zasad (dziedziczenie odpowiednich elementów po odpowiednich klasach) w naszym kontrolerze do edycji danego elementu (typu PostsController, UsersController, BooksController) możemy całkiem pominąć akcje create i update używając generycznych. W razie potrzeby poprawki - zmiany dokonujemy w jednym miejscu. Magia programowania obiektowego. Miłego używania!




Nie ma jeszcze żadnych komentarzy.
Komentarze do wpisu „Modele i formularze Zend Framework - automatyzacja zapisu”