Po niezbyt owocnym początku mojej „przyjaźni” z systemem zarządzania treścią Drupal nadszedł czas zmierzenia się z rozszerzeniami dla CMS’a. W niniejszym artykule przedstawię, jak krok po kroku stworzyć rozszerzenie pełniące rolę REST API.
Szczerze praca na kodzie Drupala, operowanie na własnych szablonach i wszelkie funkcje opierające się na bardziej zaawansowanych zmianach w bazie danych, nie należą do przyjemności. Aby przynajmniej na poziomie podstawowym zrozumieć mechanizm (nie tylko w teorii) należy poświęcić mnóstwo cennego czasu. W moim mniemaniu dużo za dużo czasu.
Ale wróćmy do tematu. Zapraszam na mały tutorial tworzenia REST API.
Teoria, a praktyka
Niezbędne informacje można (i należy) odszukać na stronie Drupala – creating custom modules. Znajdziecie tam mnóstwo informacji. Nie mam pojęcia, czy wszystkie, ale na pewno jest to lektura obowiązkowa.
Niezbędne elementy, aby skonstruować moduł REST API:
- Katalog rozszerzenia
- Metadane
- Skórka
- Routing
- Kontroler
Na wyżej wskazanej stronie, jako źródle informacji można znaleźć zdecydowanie więcej informacji nt. rozszerzeń. Zauważcie, że nasz REST API nie będzie mieć żadnego bloku zarządzania ustawieniami itp., a także nie zajmiemy się w nim szeroko pojętą obsługą prawami dostępu do API – pozostawimy je publiczne. Te i pochodne tematy pozostawiam Wam. Sam jedynie nakieruję, gdzie można zajrzeć, by dowiedzieć się więcej.
REST API
Z katalogiem rozszerzenia nie powinno być najmniejszego problemu. Moduł należy utworzyć w jednej z poniższych lokalizacji:
- /modules/<your-module> (najczęstsza lokalizacja)
- /sites/all/modules/<your-module>
Nazwa modułu dla zachowania logiki najlepiej, aby była tożsama z nazwą maszynową, wykorzystywaną przez Drupala. Nazwa maszynowa to nic innego, jak nazwa plików konfiguracyjnych (*.info.yml, *.routing.yml, *.module). Drupal nakłada pewne restrykcje na nazwy maszynowe:
- musi się rozpoczynać od litery
- musi zawierać wyłącznie małe litery i podkreślnik (nie może zawierać nic innego, w tym spacji)
- musi być unikalna względem innych modułów, skórek i profili używanych w CMS’ie
- musi być inna niż następujące słowa: src, lib, vendor, assets, css, files, images, js, misc, templates, includes, fixtures, Drupal.
Metadane to nic innego, jak plik <nazwa_maszynowa>.info.yml. Stanowi on dane informacyjne, identyfikujące nasze rozszerzenie. Na potrzeby REST API stworzymy: rest_api.info.yml
name: Publiczne API type: module description: Pierwsze publiczne API. package: Api author: Damian Kosiorowski core: 8.x version: '8.x-1.0-alpha'
Nazwa i opis to przyjazne informacje nt. naszego modułu wyświetlane na stronie pozwalającej zarządzać modułami w Drupalu. Pakiet pozwala pozycjonować nasz moduł w określonym bloku rozszerzeń. Więcej informacji tutaj.
Skórka, czyli plik <nazwa_maszynowa>.module jest to plik analogiczny do plików *.theme. Pozwalają one nadzorować haki (ang. hooks) w obrębie rozszerzenia. Na nasze potrzeby plik pozostawiamy pusty. Zainteresowanych szczegółami odsyłam tutaj.
Plik routingu jest już niezbędny w następującej formie (<nazwa_maszynowa>.routing.yml):
rest_api.add_like: path: '/rest-api/item/{node}/addLike' defaults: { _controller: '\Drupal\rest_api\Controller\RestAPIController::addLike' } methods: [POST] requirements: _access: 'TRUE' options: no_cache: 'TRUE'
Myślę, że nic tutaj nie trzeba tłumaczyć. Klucze właściwości są bardzo intuicyjne. Jest jednak ich dużo więcej. Wszystkie znajdziecie opisane bardzo szczegółowo na stronie: https://www.drupal.org/docs/8/api/routing-system/structure-of-routes.
W powyższym przykładzie zwróćcie uwagę, że path oznacza ścieżkę w adresie url, której wywołanie spowoduje uruchomienie kodu – funkcji addLike we wskazanym kontrolerze. Oczywiście w tym przypadku wyłącznie poprzez metodę POST.
Takim sposobem dotarliśmy do kluczowego momentu, mianowicie logiki kontrolera – /modules/rest_api/src/Controller/RestAPIController.php:
<?php /** * @file * Contains \Drupal\rest_api\Controller\RestAPIController. */ namespace Drupal\rest_api\Controller; use Drupal\Core\Controller\ControllerBase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\JsonResponse; use Drupal\node\Entity\Node; /** * Controller routines for rest_api routes. */ class RestAPIController extends ControllerBase { /** * Callback for `/rest-api/item/{node}/addLike` API method. */ public function addLike( Request $request, $node ) { $response['result'] = true; if(!($item = Node::load($node))) { $response['message'] = t('Node is not exist.'); $response['result'] = false; } if($response['result']) { $item->field_clicks_counter->value++; try { if($item->save() !== SAVED_UPDATED) { throw new \Exception('Increment unsuccessful'); } $response['counter'] = $item->field_likes->value; } catch(\Exception $e) { $response['message'] = $e->getMessage(); $response['node'] = $item->toArray(); $response['result'] = false; } } return new JsonResponse( $response ); } }
To wszystko. Teraz wystarczy zainstalować rozszerzenie i korzystać.
Podsumowując chciałbym nadmienić, że akurat pisanie i korzystanie z rozszerzeń w Drupalu, nie jest aż tak kłopotliwe i można je tolerować o ile dotyczy aplikacji-systemu blogowego. Jeśli aplikacja jest na tyle złożona, że powiązanie jej z typowym blogiem jest trudne do zidentyfikowania to wystarczy mieć doświadczenie w tworzeniu systemów w natywnych frameworkach PHP, by w dużo krótszym czasie ją stworzyć. Ba i dużo lżejszą – piszę o tym dlatego, że to co dzieje się „z tyłu” systemu (tzw. backend) jest w mojej ocenie mocno nieoptymalne dla złożonych systemów. Liczba zapytań do bazy danych, ich sekwencje dla pojedynczego żądania są często bardzo wysokie, nieoptymalne, aby nie powiedzieć, że nie do zaakceptowania … a przecież każdy backendowiec wie, że podstawą jest optymalizacja liczby zapytań i samych zapytań do bazy.
Pozdrawiam!