REST w ZF2 – metody PUT i DELETE w formularzach
Architektura dostępu do danych REST staje się coraz bardziej popularna w Internecie. Coraz częściej trafiam na serwisy działające w ten sposób, coraz więcej frameworków obsługuje ten wzorzec. W Internecie dużo już napisano na temat samego sposobu implementacji, ale w skrócie chodzi w głównym stopniu o sposobie dostępu dodanych przez protokół HTTP wykorzystując 4 typy (metody) zapytań i odpowiednie formatowanie adresów URI. Zazwyczaj standardowe serwisy korzystają z zapytań GET i POST. REST proponuje wykorzystanie też PUT i DELETE (HTTP definiuje jeszcze kilka kolejnych, jeszcze rzadziej wykorzystywanych).
Najcześciej wykorzystywane i obsługiwane przez frameworki zapytania to (podane na przykładzie zasobu users):
- GET /users – pobranie listy zasobów (tutaj użytkowników)
- GET /users/5 – pobranie danych konkretnego zasobu o podanym identifykatorze
- POST /users – utworzenie nowego zasobu
- PUT /users/5 – aktulizacja (edycja) konkretnego zasobu
- DELETE /users/5 – usunięcie konkretnego zasobu
Jak widać zapytania te odpowiadają standardowemu modelowi CRUD (create, read/retrieve, update, delete/destroy) – czynnościom wykonywanym na danych w 99% przypadków działania aplikacji.
Cały czas rozwijany Zend Framework 2 jest tworzony z całkiem niezłą obsługą REST. Przygotowany został specjalny typ kontrolera – RestfulController – w którym znajdziemy abstrakcyjne metody getList, get, create, update i delete odpowiadające wyżej wymienionym RESTowym akcjom. Niestety, problem cały czas stanowią przeglądarki, które niebardzo chcą RESTowe zapytania obsługiwać. A dokładniej wysyłać dane z typem zapytania PUT lub DELETE. Wiele innych frameworków obsługuje takie czy inne sztuczki do pominięcia tego ograniczenia, jednak w ZF2 niczego takiego nie udało mi się znaleźć. Być może zostanie to dodane w przyszłości, ale już teraz poradzenie sobie z tym nie wymaga wiele pracy.
Zapytania PUT i DELETE zmieniają dane w bazie (a PUT dodatkowo przesyła dane do serwera), więc bliżej im do POST niż do GET. Wykorzystamy więc standardowy formularz HTMLowy, ale z dodatkowym ukrytym parametrem _method o wartości metodą, którą chcemy „przemycić”:
<form action="/users/5" method="POST"> <input type="hidden" name="_method" value="PUT"> <!-- tutaj pozostałe elementy formularza --> </form>
Następnie musimy przechwycić te dane i wskazać klasie żądania nowy sposób działania. Robimy to na zdarzeniu bootstrap na wysokim priorytecie (żeby kod został wykonany jak najwcześniej). Akcję definiujemy w dowolnym module, gdzie najlepiej będzie to pasowało do konkretnej aplikacji, może to być na przykład standardowy moduł Application. Edytujemy plik Module.php z katalogu głównego modułu i wpisujemy w nim metodę init:
public function init() { $events = \Zend\EventManager\StaticEventManager::getInstance(); $events->attach('bootstrap', 'bootstrap', array($this, 'overrideMethod'), 2000); }
Jeżeli w konkretnym module metoda już istnieje dopisujemy tylko brakujące linie. Spowoduje to uruchomienie na bardzo wczesnym etapie zdarzenia bootstrap metody overrideMethod z bieżącego obiektu (trzeci parametr metody attach definuje callback). Musimy tę metodę stworzyć (w tej samej klasie Module):
public function overrideMethod(Event $e) { $request = $e->getParam('application')->getRequest(); if($request->isPost()) { switch($request->post()->get('_method')) { case Request::METHOD_PUT: $request->setMethod(Request::METHOD_PUT); $request->setContent(file_get_contents('php://input')); break; case Request::METHOD_DELETE: $request->setMethod(Request::METHOD_DELETE); break; } } }
Metoda kolejno:
- pobiera obiekt Request z Eventu (linia 3),
- sprawdza, czy rządanie jest typu POST (linia 5),
- testuje zawartość pola _method (linia 6),
- nadpisuje odpowiednią metodę zapytania (linie 8 i 12),
- przepisuje dane wejściowe (klasa Request z ZF2 spodziewa się tylko formatu POST, linia 9).
Od tej pory w aplikacji można normalnie używać kontrolerów RestfulControler i ich standardowych akcji łącznie z parametrem $data dla create i update.