AJAX i JSON w Zend Framework 2
Patrząc na fora w Internecie oraz zapytania programistów, mam wrażenie, że obsługa zapytań AJAXowych oraz zwracanie odpowiedzi w formacie JSON w Zend Framework 2 nie jest oczywista. Poniżej postaram się przybliżyć ten temat. 5 lat temu pisałem o tym temacie w kontekście Zend Framework 1. Dzisiaj, kilka miesięcy po premierze ZF2, nadszedł czas na odświeżenie tematu.
Wyświetlanie widoku bez layoutu (w zależności od metody żądania)
Załóżmy, że chcemy, aby nasza aplikacja działała w taki sposób, że gdy przychodzi pełne nowe zapytanie o całą stronę, pokazuje widok akcji razem z pełnym layoutem, a gdy zapytanie przychodzi przez AJAX, wysyła tylko widok akcji, a layout pomija. Jest to dość popularna forma dynamizacji nawigacji po stronie za pomocą schematu hashbang (#!) albo jeszcze lepiej pushState.
Rozpoznanie zapytania Ajaksowego odbywa się na podstawie nagłówka X-Requested-With. Większość frameworków z obsługą AJAX ustawia go na wartość XMLHttpRequest. Zend Framework dostarcza metody łatwego sprawdzenia tego nagłówka i wartości z poziomu kontrolera $this->getRequest()->isXmlHttpRequest() . Jeżeli w takim przypadku chcemy wyłączyć wykorzystanie layoutu i wysłać do przeglądarki sam widok akcji, musimy model widoku (zaleca odpowiedź akcji kontrolera) ustawić jako końcowy („terminal„):
/** * @return \Zend\View\Model\ViewModel */ public function myAction() { // tutaj kod akcji i ustawienie zmiennych, które można przekazać do widoku $vm = new \Zend\View\Model\ViewModel(); if ($this->getRequest()->isXmlHttpRequest()) { $vm->setTerminal(true); } return $vm; }
I to wystarczy aby w ten sposób rozróżnić zwracaną zawartość.
Odpowiedź JSON (w zależności od żądanego formatu)
Budując aplikację w ten sposób, żeby mogła ona od razu działać jako API, możemy np. wyświetlać dane w formacie JSON na żądanie. Co pozwoli wchodząc na przykładowy URL /users zobaczyć listę użytkowników jako normalna strona HTML, która jest częścią serwisu, a wchodząc na /users.json zobaczymy informacje o tych użytkownikach w formacje JSON. Zachowując przy tym przestrzeganie wszystkich parametrów z query, np. stronicowanie albo wyszukiwanie.
Zend Framework w wersji 2 wprowadził nowy sposób wyświetlania. Wyświetlanie składa się z wielu warstw, z których najważniejsze to:
- modele wyświetlania (View Models), które łączą zmienne z plikami widoków (np. phtml) oraz sterują renderowaniem. Domyślnym obiektem tworzonym przez akcję jest obiekt klasy Zend\View\Model\ViewModel. Nawet zwracając tablicę zmiennych, ZF2 tworzy z niej ViewModel z domyślnymi ustawieniami.
- renderery (Renderers, nie przychodzi mi do głowy dobre tłumaczenie), które odpowiadają za wczytanie pliku widoku, przekazanie tam zmiennych i wyrenderowanie wyniku. Domyślnie używane są podobnie jak w ZF1 pliki phtml, które są HTMLem z osadzonymi fragmentami PHP. Jest trochę różnic w stosunku do poprzedniej wersji frameworka (tam podobną rolę spełniał Zend_View), ale ogólna zasada działania jest ta sama.
- strategie renderowania (Rendering Strategies), czyli druga obok modeli wyświetlania największa różnica, gdzie w zależności od różnych warunków (typ modelu, typ żądania z przeglądarki, wersja mobilna/desktop), wynik renderowania będzie różny.
Jest jeszcze kilka innych warstw, ale z doświadczenia wiem, że są one mniej istotne i zazwyczaj domyślne ustawienia wystarczają.
Aby móc prawidłowo wyświetlać dane z akcji jako format JSON, pierwsze co musimy zrobić, to włączyć strategię renderowania JSON obok standardowej strategii wyświetlania plików HTML. Robimy to w konfiguracji jednego z modułów naszej aplikacji w sekcji ’view_manager’:
<?php return array( // to jest tylko fragment pliku konfiguracyjnego 'view_manager' => array( 'strategies' => array( 'ViewJsonStrategy', ), ), );
Następnie w akcji kontrolera zamiast standardowego ViewModel zwracamy JsonModel. Przekazane jako pierwszy argument zmienne w postaci tablicy zostaną automatycznie przekształcone do formatu JSON oraz wysłane do odpowiedzi z typem MIME application/json (dzięki użyciu strategii). Możemy np. zbudować aplikację w taki sposób, aby użytkownik wchodząc przez przeglądarkę na zwykły adres dostawał standardowy widok HTML, a wchodząc na adres z rozszerzeniem .json dostawał odpowiedź w tym formacie.
Aby to zrobić, do ścieżki adresowej musimy dodać parametr, np. w ten sposób:
<?php return array( // to jest tylko fragment pliku konfiguracyjnego 'router' => array( 'routes' => array( 'home' => array( 'type' => 'Literal', 'options' => array( 'route' => '/', 'defaults' => array( '__NAMESPACE__' => 'Application\Controller', 'controller' => 'Index', 'action' => 'index', ), ), 'may_terminate' => true, 'child_routes' => array( 'default' => array( 'type' => 'Segment', 'options' => array( 'route' => '[:controller[/:action[/:id]]][.:format]', 'constraints' => array( 'controller' => '[a-zA-Z][a-zA-Z0-9_-]*', 'action' => '[a-zA-Z][a-zA-Z0-9_-]*', 'id' => '[0-9]*', ), 'defaults' => array( ), ), ), ), ), ), ), );
Przykładowy kontroler, który pozwoli pobrać listę użytkowników pod adresem /users oraz wersję JSON pod adresem /users.json może wyglądać następująco:
<?php namespace Application\Controller; use Application\Model\Users\UserQuery; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\JsonModel; use Zend\View\Model\ViewModel; /** * Class UsersController */ class UsersController extends AbstractActionController { /** * @return JsonModel|ViewModel */ public function indexAction() { $users = UserQuery::create()->findAllByParams($this->getRequest()->getQuery()->toArray()); switch ($this->params('format')) { case 'json': $usersData = array(); foreach ($users as $user) { $usersData[] = $user->toArray(); } return new JsonModel(array( 'success' => true, 'users' => $usersData, )); default: return new ViewModel(array( 'users' => $users, )); } } }
Podsumowując
Wykorzystanie w odpowiedni sposób technik opisanych powyżej pozwoli na zbudowanie dynamicznych, ajaksowych, nowoczesnych aplikacji, dającym użytkownik wygodę poruszania się. Druga metoda pozawala też na zbudowanie w bardzo prosty sposób API w tworzonym oprogramowaniu, tak aby zewnętrzne aplikacje mogły w łatwy sposób pobierać lub tworzyć dane w serwisie.
Twój artykuł bardzo mi pomógł – natrafiłem jednak na spory problem z kodowaniem polskich znaków.
Jeżeli któreś z pól w obiekcie JSON posiada polskie znaki to znika cały widok.
Podobnie zresztą działa w moim ZF3 funkcja json_encode i Zend/Json/Json::encode – cały string JSON zamienia się na pusty string.
…help !
Jeżeli json_encode tak działa, który jest funkcją z podstawowego php (moduł json), to problem jest poza frameworkiem. Może coś poszło źle przy instalacji PHP. Niestety nie umiem pomóc. Jak widzisz w przykładzie na zrzucie ekranu używam danych z polskimi znakami i nigdy z takim problemem się nie spotkałem.