Zajmiemy się dziś praktyczną stroną tworzenia RESTful API. Zbudujemy w Symfony2 działający serwis RESTowy zgodny z dobrymi praktykami. Jak poprzednio, przy tworzeniu OAuth2 będziemy budować aplikację od zera.
Aby skorzystać z REST API w Symfony najwygdniej jest użyć bundla FOSRestBundle. Pominiemy dziś część bazodanową i skupimy się na samej aplikacji.
Dla przykładu stworzymy aplikację pozwalającą na zamawianie i dostarczanie pizzy.
Krok 1 – tworzenie aplikacji.
Zaczynamy od stworzenia samej aplikacji w Symfony:
$ symfony new REST_Example $ cd REST_Example
Instalujemy nasz ulubiony FOSRestBundle oraz dodatkowy bundle, który przyda nam się do obsługi serializacji obiektów:
$ composer require friendsofsymfony/rest-bundle $ composer require jms/serializer-bundle
Dodajemy bundla do Symfony w pliku app/AppKernel.php. Dopisujemy w odpowiednim miejscu linie:
... new FOS\RestBundle\FOSRestBundle(), new JMS\SerializerBundle\JMSSerializerBundle(), ...
Na koniec dodajmy bundla, który będzie odpowiadał za samą obsługę dostarczania pizzy:
$ php app/console generate:bundle --namespace=Example/PizzaBundle --format=yml
Krok 2 – trasy i kontroler
Załóżmy, że chcemy udostępniać nasze api pod adresem /api/. Tu będziemy mieć zasób, który pozwoli na czytanie (GET), zapisywanie (POST), zmianę (PUT) i kasowanie (DELETE) danych. Musimy najpierw zająć się routingiem.
Zajrzyjmy do pliku z routingiem naszego bundla. Dodamy tam taką definicję:
# src/Example/PizzaBundle/Resources/config/routing.yml example_pizza: resource: "@ExamplePizzaBundle/Controller/ApiController.php" prefix: /api/ type: rest
Uwaga na parametr „type”. Nie podajemy tu annotation czy innych standardowych wartości. Chcemy aby ścieżki do restowych metod stworzyły się same.
Utwórzmy teraz kontroler. Zaczniemy od podstawowej metody REST czyli options:
// src/Example/PizzaBundle/Controller/ApiController.php namespace Example\PizzaBundle\Controller; use FOS\RestBundle\Controller\FOSRestController; class PizzaController extends FOSRestController { public function optionsPizzaAction() { } }
Nie dziedziczymy po zwykłym kontrolerze Symfony. FOSRestController jest naszą bazą do tworzenia kontrolerów. Dzięki niemu wystarczy dodać odpowiednie akcje aby routing zadziałał. Sprawdźmy jak wyglądają teraz trasy:
$ php app/console router:debug ... options_pizza OPTIONS ANY ANY /api/pizza.{_format} ...
Mamy teraz naszą metodę. Ale skąd wzięło options i pizza?
Typ routingu „rest” znaczy, że symfony skanuje nasz kontroler szukając akcji. Każdą akcję rozkłada na czynniki pierwsze. Nasza „optionsPizzaAction” stała się metodą HTTP „options„. Z kolei „pizza” – jest nazwą zasobu. REST bundle sam dokleja parametr „_format”, który mówi jak zwrócić dane.
Dopiszmy kolejną metodę aby zobaczyć jak zmienią się nasze trasy. Do pizzy potrzebujemy keczup:
public function getKetchupAction() {}
W naszym routingu pojawi się kolejna trasa:
$ php app/console router:debug ... get_ketchup GET ANY ANY /api/ketchup.{_format} ...
Opcjonalny parametr _format pozwala wybrać, w jakim formacie będą dane zwrócone przez nasze API.
Krok 3 – zwracamy dane
Rozszerzymy naszą metodę zwracającą keczup tak, aby faktycznie podawała jakieś dane. Metoda będzie wyglądać tak:
public function getKetchupAction() { $data = [ 'type' => 'Spicy', 'quantity' => '30ml', ]; $view = $this->view($data,200); return $this->handleView($view); }
Co tu się stało? Tworzymy widok, który będzie odpowiadał za prezentację naszych danych. Nazwa „widok” jest tu trochę myląca bo nie będziemy zwracać html.
Potem wywołujemy metodę handleView(). Sam widok służy do tego aby przechować nasze dane oraz ew. opcje. Metoda handleView() pobiera dane z widoku i zwraca je w odpowiednim formacie. Pozostaje nam zdefiniować obsługiwane formaty. Dodajemy konfigurację:
# app/config/config.yml fos_rest: view: formats: xml: true json: true routing_loader: default_format: json
Obsługujemy XML i JSON. Ten drugi jest naszym ulubionym formatem. Możemy teraz wywołać nasze api:
$ curl -i http://localhost/REST_Example/web/app_dev.php/api/ketchup
Takie wywołanie zwróci nam dane w JSON:
HTTP/1.1 200 OK ... Content-Length: 34 Content-Type: application/json {"type":"Spicy","quantity":"30ml"}
Zmieniając adres na …/ketchup.xml dostaniemy dane w XMLu. Możemy też wywołać …/ketchup.json ale nie musimy bo powiedzieliśmy wcześniej, że JSON jest domyślnym formatem zwracanych danych.
Krok 4 – dobre nagłówki
Znawcy REST od razu zauważą problem. Format danych nie powinien być częścią adresu. Zgodnie ze sztuką powinniśmy zawsze wywoływać sam adres …/ketchup. Za to nagłówek HTTP „Accept” powinien decydować o formacie danych. Poprawmy nasze API.
Szczęśliwie FOSRestBundle przewiduje taką możliwość. Wystarczy dodać w konfiguracji:
# app/config/config.yml fos_rest: ... format_listener: rules: - { path: '^/api/', priorities: ['json', 'xml'], prefer_extension: false }
Co dodaliśmy? Zdefiniowaliśmy regułę dla obiektu, który zajmuje się obsługą formatów. Dla wszystkich ścieżek wewenątrz „api/” Symfony będzie nasłuchiwać nagłówka HTTP „Accept” i ustawiać odpowiedni format. Takich ścieżek można zdefiniować wiele.
Zobaczmy, że działa. Poprosimy o XML:
$ curl -i -H "Accept: application/xml" http://localhost/REST_Example/web/app_dev.php/api/ketchup
W odpowiedzi dostaniemy:
HTTP/1.1 200 OK ... Content-Length: 127 Content-Type: text/xml; charset=UTF-8 <?xml version="1.0" encoding="UTF-8"?> <result> <entry><![CDATA[Spicy]]></entry> <entry><![CDATA[30ml]]></entry> </result>
Jeśli zmienimy nagłówek na Accept: application/json
to pod tym samym adresem dostaniemy dane w JSON. Stary sposób pobierania danych również działa. Możemy dodać format do adresu.
A co gdy spróbujemy wywołać konflikt? W nagłówku zażądamy XMLa ale podamy adres …/ketchup.json. Do tego służy właśnie opcja prefer_extension
. Można nią włączyć lub wyłączyć pierwszeństwo rozszerzenia w adresie nad nagłówkiem HTTP.
Na koniec
W tym artykule zaledwie liznąłem RESTful API w Symfony. Warto przeczytać całą dokumentację do FOSRestBundle. Na sieci znajdziemy też ciekawy artykuł w tym temacie.
FOSRestBundle daje nam o wiele więcej możliwości. Obsługa wszystkich metod HTTP (w tym też LINK i UNLINK). Obsługa formularzy, szablonów w widokach. Zaawansowane trasy dla zasobów i kolekcji. Dużo, dużo więcej. Niebawem wrócimy do tego bundla w jednym z kolejnych postów.
Nareszcie coś „na chłopski rozum”, bo do tutorialu z https://symfony.com/doc/master/bundles/FOSRestBundle/index.html podchodziłem kilkukrotnie :) Wielkie dzięki!.