RESTful api w Symfony2 krok po kroku

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.

Jedna myśl na temat “RESTful api w Symfony2 krok po kroku”

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *