Czego nauczę się w tym poradniku?
- Jak przechwycić parametry żądania GET podczas interakcji z RESTful API
Wymagania
- System z rodziny UNIX/Linux
- Serwer Apache2 z PHP w wersji 7.1
- Baza danych MySQL
- Projekt Symfony pobrany stąd
- Dowolny edytor tekstowy
- Istniejący projekt w Firebase
- Narzędzie composer
Poziom trudności
- Średni
Treść poradnika
Poradnik jest kolejnym z serii Web Development, gdzie opisywane są procesy budowy aplikacji serwerowej w Symfony, zdolnej do wysyłki powiadomień push na urządzenia mobilne. W poprzednim artykule rozszerzony został mechanizm głosujący, tak aby umożliwić weryfikację uprawnień do interakcji z obiektem urządzenia na podstawie ról przypisanych do użytkownika wykonującego żądanie.
Przykładowy projekt wykorzystywany na potrzeby tego poradnika znajduje się tutaj. Jest to w zasadzie kod, który otrzymamy po ukończeniu poprzedniego kompendium. Wskazanym jest pobranie wspomnianego projektu z repozytorium github.
Jakie aspekty Web Development z Symfony zostaną przedstawione w tym poradniku?
- Proces implementacji funkcjonalności Param Fetcher dostarczonej z modułem
FOSRestBundle
, pozwalającej na przechwytywanie parametrów GET z żądania. Przechwycone parametry mogą zostać wykorzystane np. do stronicowania wyników odnalezionych w bazie danych oraz do zmiany typu ich sortowania.
Przykłady zastosowania funkcjonalności utworzonych w tym poradniku znajdują się na jego końcu.
Jak przechwycić parametry żądania GET podczas interakcji z RESTful API?
Param fetcher będący częścią modułu FOSRestBundle
pozwala na przechwytywanie parametrów GET żądania, jednocześnie umożliwiając weryfikację ich zawartości. Przechwytywane parametry oraz odpowiadające im reguły definiowane są za pomocą adnotacji umieszczonych w blokach phpdoc
.
W dzisiejszym artykule, param fetcher zostanie wykorzystany do utworzenia funkcjonalności umożliwiającej stronicowanie wyników wiadomości wysłanych na konkretne urządzenie oraz pozwalającej na definiowanie typu sortowania.
Klasa repozytorium wiadomości
W tym etapie zostanie utworzona klasa repozytorium wiadomości, która posiadać będzie metodę odpowiedzialną za odnalezienie wiadomości powiązanych z konkretnym urządzeniem. Dodatkowo, wspomniana metoda pozwoli na ustawienie sortowania dla zwracanych wyników oraz definicję wartości offset
oraz limit
.
Wartość offset
oznacza pozycję, która będzie pierwszym elementem zwracanej tablicy. Z kolei wartość limit
pozwala zdefiniować ilość rekordów, licząc od pozycji wskazanej w wartości offset
, które mają zostać zawarte w zwracanej tablicy.
Warstwa abstrakcyjna
Nim przejdziemy do definicji klasy właściwej repozytorium wiadomości, wskazane jest utworzenie interfejsu MessageRepositoryInterface
, zawierającego deklarację dla metody findMessagesByDevice(DeviceInterface $device, string $sort, int $offset, int $limit)
odpowiedzialnej za zwrócenie listy wiadomości powiązanych z konkretnym urządzeniem.
<?php
// src/Ptrio/MessageBundle/Repository/MessageRepositoryInterface.php
namespace App\Ptrio\MessageBundle\Repository;
use App\Ptrio\MessageBundle\Model\DeviceInterface;
use Doctrine\Common\Persistence\ObjectRepository;
interface MessageRepositoryInterface extends ObjectRepository
{
/**
* @param DeviceInterface $device
* @param string $sort
* @param int $offset
* @param int $limit
* @return array
*/
public function findMessagesByDevice(DeviceInterface $device, string $sort, int $offset, int $limit): array;
}
Jak widać na powyższym przykładzie, metoda findMessagesByDevice(DeviceInterface $device, string $sort, int $offset, int $limit)
przyjmować będzie cztery argumenty. Pierwszy to obiekt urządzenia, drugi to typ sortowania, trzeci to przesunięcie, czyli numer wiersza, od którego należy rozpocząć definiowanie wyników. Ostatnim argumentem jest jest limit, czyli maksymalna ilość wyników zwracanych w tablicy.
Klasa właściwa
Klasa właściwa repozytorium wiadomości zawierać będzie logikę określającą w jaki sposób metoda MessageRepository::findMessagesByDevice(DeviceInterface $device, string $sort = 'DESC', int $offset, int $limit)
będzie obsługiwana.
<?php
// src/Ptrio/MessageBundle/Repository/MessageRepository.php
namespace App\Ptrio\MessageBundle\Repository;
use App\Ptrio\MessageBundle\Model\DeviceInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
class MessageRepository extends EntityRepository implements MessageRepositoryInterface
{
public function __construct(EntityManagerInterface $em, string $class)
{
parent::__construct($em, $em->getClassMetadata($class));
}
public function findMessagesByDevice(DeviceInterface $device, string $sort = 'DESC', int $offset, int $limit): array
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb
->select('m')
->from($this->getEntityName(), 'm')
->andWhere($qb->expr()->eq('m.device', $device->getId()))
->orderBy('m.id', $sort)
->setFirstResult($offset)
->setMaxResults($limit)
;
return $qb->getQuery()->getResult();
}
}
Metoda MessageRepository::findMessagesByDevice(DeviceInterface $device, string $sort = 'DESC', int $offset, int $limit)
korzysta z obiektu typu QueryBuilder
w celu przygotowania zapytania w DQL, które w pierwszej kolejności zwróci wiadomości wysłane do konkretnego urządzenia, następnie posortuje je rosnąco lub malejąco po identyfikatorze wiadomości. Zwrócona zostanie liczba elementów zdefiniowana w argumencie $limit
, począwszy od pozycji wskazanej w argumencie $offset
.
Konfiguracja usług
Konieczne jest dodanie definicji dla usługi repozytorium ptrio_message.message_repository
do pliku services.yaml
, aby odpowiednie zależności zostały wstrzyknięte.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
ptrio_message.message_repository:
class: 'App\Ptrio\MessageBundle\Repository\MessageRepository'
arguments:
- '@doctrine.orm.entity_manager'
- '%ptrio_message.model.message.class%'
Aktualizacja klasy managera wiadomości
Na tę chwilę metoda MessageManager::findMessagesByDevice(DeviceInterface $device)
umożliwia jedynie odnalezienie wiadomości wysłanych na konkretne urządzenie. Aby funkcjonalność sortowania wyników oraz stronicowania mogła zostać wykorzystana przez usługę managera wiadomości, należy ją zmodyfikować, tak by korzystała z obiektu typu MessageRepository
.
Warstwa abstrakcyjna
W pliku interfejsu MessageManagerInterface.php
należy zaktualizować deklarację dla metody odpowiedzialnej za wyszukiwanie wiadomości wysłanych na dane urządzenie.
// src/Ptrio/MessageBundle/Model/MessageManagerInterface.php
// other method declarations
public function findMessagesByDevice(DeviceInterface $device, string $sort, int $offset, int $limit): array;
Klasa właściwa
W klasie właściwej managera wiadomości należy zaktualizować blok phpdoc
dla własności MessageManager::$repository
, aby powiązać z nią typ abstrakcyjny MessageRepositoryInterface
.
// src/Ptrio/MessageBundle/Doctrine/MessageManager.php
/**
* @var MessageRepositoryInterface
*/
private $repository;
W kolejnym kroku konieczne jest uzupełnienie konstruktora klasy MessageManager
o argument zawierający instancję klasy implementującej interfejs MessageRepositoryInterface
.
/**
* MessageManager constructor.
* @param ObjectManager $objectManager
* @param string $class
* @param MessageRepositoryInterface $repository
*/
public function __construct(
ObjectManager $objectManager,
string $class,
MessageRepositoryInterface $repository // add this line
)
{
$this->objectManager = $objectManager;
$this->repository = $repository; // modify this line
$metadata = $objectManager->getClassMetadata($class);
$this->class = $metadata->getName();
}
Od tej chwili własność MessageManager::$repository
przechowywać będzie obiekt repozytorium wiadomości.
Następnie konieczne będzie zastąpienie istniejącej definicji metody MessageManager::findMessagesByDevice(DeviceInterface $device)
na tę, która znajduje się poniżej.
/**
* {@inheritdoc}
*/
public function findMessagesByDevice(DeviceInterface $device, string $sort, int $offset, int $limit): array
{
return $this->repository->findMessagesByDevice($device, $sort, $offset, $limit);
}
Konfiguracja usług
W ostatnim kroku należy zaktualizować definicję usługi ptrio_message.message_manager
, dodając usługę ptrio_message.message_repository
jako trzeci argument.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
ptrio_message.message_manager:
class: 'App\Ptrio\MessageBundle\Doctrine\MessageManager'
arguments:
- '@doctrine.orm.entity_manager'
- '%ptrio_message.model.message.class%'
- '@ptrio_message.message_repository'
Klasa kontrolera wiadomości
Aby lista wiadomości dla konkretnego urządzenia mogła zostać zwrócona jako odpowiedź HTTP, konieczne będzie utworzenie klasy kontrolera. Będzie ona zawierać definicję dla jednej metody MessageController::getMessagesAction(string $deviceName, ParamFetcher $paramFetcher)
. Nad wspomnianą metodą zostaną umieszczone odpowiednie adnotacje, umożliwiające zdefiniowanie parametrów GET przechwytywanych przez obiekt typu ParamFetcherListener
. W ten sposób możliwe będzie przekazanie typu sortowania, numeru strony oraz ilości wyników na stronie, poprzez dodanie wartości ?sort=sort_type&page=page_no&results_per_page=limit
do ścieżki żądania.
Informacja: Ze względu na fakt, że klasa kontrolera rozszerzać będzie klasę bazową
FOSRestController
, tworzenie warstwy abstrakcyjnej nie będzie konieczne.
Klasa właściwa
Klasa właściwa kontrolera wiadomości korzystać będzie z usługi managera urządzeń w celu odszukania urządzenia po nazwie oraz z usługi managera wiadomości, aby przygotować listę wiadomości wysłanych na to urządzenie. Obie wspomniane usługi zostaną wstrzyknięte jako argumenty konstruktora klasy MessageController
.
<?php
// src/Ptrio/MessageBundle/Controller/MessageController.php
namespace App\Ptrio\MessageBundle\Controller;
use App\Ptrio\MessageBundle\Model\DeviceManagerInterface;
use App\Ptrio\MessageBundle\Model\MessageManagerInterface;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Request\ParamFetcher;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\Controller\Annotations\QueryParam;
class MessageController extends FOSRestController
{
/**
* @var DeviceManagerInterface
*/
private $deviceManager;
/**
* @var MessageManagerInterface
*/
private $messageManager;
/**
* MessageController constructor.
* @param DeviceManagerInterface $deviceManager
* @param MessageManagerInterface $messageManager
*/
public function __construct(
DeviceManagerInterface $deviceManager,
MessageManagerInterface $messageManager
)
{
$this->deviceManager = $deviceManager;
$this->messageManager = $messageManager;
}
/**
* @param string $deviceName
* @param ParamFetcher $paramFetcher
* @return Response
*
* @QueryParam(name="sort", requirements="(asc|desc)", default="asc")
* @QueryParam(name="page", requirements="\d+", default="0")
* @QueryParam(name="results_per_page", requirements="\d+", default="25")
*/
public function getMessagesAction(string $deviceName, ParamFetcher $paramFetcher): Response
{
if ($device = $this->deviceManager->findDeviceByName($deviceName)) {
$this->denyAccessUnlessGranted(null, $device);
$sort = $paramFetcher->get('sort');
$page = $paramFetcher->get('page');
$resultsPerPage = $paramFetcher->get('results_per_page');
list($offset, $limit) = [($page * $resultsPerPage), $resultsPerPage];
$messages = $this->messageManager->findMessagesByDevice($device, $sort, $offset, $limit);
$view = $this->view($messages, Response::HTTP_OK);
} else {
$view = $this->view(null, Response::HTTP_NOT_FOUND);
}
return $this->handleView($view);
}
}
Po krótce omówmy ważniejsze aspekty klasy MessageController
znajdującej się powyżej.
Parametry przechwytywane przez obiekt typu ParamFetcherListener
definiowane są za pomocą adnotacji @QueryParam
. Jak widać w przedstawionym przykładzie, tymi parametrami będą sort
, page
oraz results_per_page
.
Parametr sort
będzie mógł przechowywać dwie wartości: asc
oraz desc
. Adekwatna reguła weryfikacyjna zdefiniowana została w argumencie requirements
. W przypadku gdy parametr sort
nie został przekazany, zostanie wykorzystana domyślna wartość, przechowywana w argumencie default
.
Parametr page
przechowywał będzie wartość numeryczną, reprezentującą numer strony listy wiadomości. Domyślną wartością będzie 0
.
Informacja: Ze względu na fakt, że numeracja stron zaczyna się od wartości
0
, numer pierwszej strony to 0, drugiej to 1, trzeciej 2 i tak dalej.
Z kolei parametr results_per_page
pozwalał będzie na przekazanie ilości wyników prezentowanych na danej stronie. W tym przypadku domyślną wartością jest 25
.
Metoda MessageControllergetMessagesAction(string $deviceName, ParamFetcher $paramFetcher)
przyjmuje dwa argumenty. Pierwszy to nazwa urządzenia, dla którego chcemy wyszukać wiadomości, natomiast drugi to obiekt typu ParamFetcher
, który umożliwia m.in. pobranie wartości parametrów zdefiniowanych za pomocą adnotacji @QueryParam
.
Informacja: Aplikacja korzysta z prześwietlenia klas i typów, dlatego też kolejność argumentów dla metod kontrolera nie ma znaczenia.
Dodatkowo, usługa mechanizmu głosującego wykorzystywana jest do weryfikacji czy dany użytkownik posiada odpowiednie uprawnienia do wyświetlenia listy wiadomości wysłanych na konkretne urządzenie.
Wartości przechwytywanych parametrów mogą zostać pobrane poprzez odwołanie się do metody ParamFetcher::get($name, $strict = null)
, gdzie argument $name
to nazwa parametru, którego wartość chcemy pobrać.
Wartość zmiennej $offset
tworzona jest poprzez pomnożenie numeru żądanej strony przez liczbę wyników na stronie.
Konfiguracja usług
Aby zależności dla kontrolera wiadomości zostały wstrzyknięte, konieczne będzie dodanie definicji usługi ptrio_message.message_controller
oraz oznaczenie jej jako controller.service_arguments
.
# src/Ptrio/MessageBundle/Resources/config/services.yaml
ptrio_message.message_controller:
class: 'App\Ptrio\MessageBundle\Controller\MessageController'
arguments:
- '@ptrio_message.device_manager'
- '@ptrio_message.message_manager'
tags:
- { name: controller.service_arguments }
Konfiguracja ścieżek
W następnym kroku wymagane jest dodanie konfiguracji dla ścieżek związanych z kontrolerem wiadomości.
# src/Ptrio/MessageBundle/Resources/config/routes.yaml
message:
type: rest
parent: device
resource: 'ptrio_message.message_controller'
Parametr parent
pozwala na zdefiniowanie ścieżek nadrzędnych. W tym przypadku, dla ścieżek związanych z kontrolerem wiadomości, nadrzędnymi ścieżkami będą te, powiązane z kontrolerem urządzeń.
Istniejące ścieżki można wyświetlić za pomocą polecenia php bin/console debug:router
.
--------------------- -------- -------- ------ -------------------------------------------------
Name Method Scheme Host Path
--------------------- -------- -------- ------ -------------------------------------------------
get_device GET ANY ANY /api/v1/devices/{deviceName}.{_format}
post_device POST ANY ANY /api/v1/devices.{_format}
delete_device DELETE ANY ANY /api/v1/devices/{deviceName}.{_format}
get_device_messages GET ANY ANY /api/v1/devices/{deviceName}/messages.{_format}
--------------------- -------- -------- ------ -------------------------------------------------
Jak widać na powyższym przykładzie, listę wiadomości dla konkretnego urządzenia, można wyświetlić odwołując się do ścieżki /api/v1/devices/{deviceName}/messages
.
Konfiguracja FOSRestBundle
W ostatnim kroku, należy zmodyfikować konfigurację modułu FOSRestBundle
znajdującą się w pliku config/packages/fos_rest.yaml
.
# config/packages/fos_rest.yaml
param_fetcher_listener: true
Poprzez ustawienie wartości true
dla parametru param_fetcher_listener
informujemy moduł FOSRestBundle
, że parametry GET zdefiniowane w adnotacjach @QueryParam
powinny być przechwytywane.
Przykłady
Listę wiadomości dla urządzenia iphone-piotr
można wyświetlić odwołując się do adresu http://localhost/api/v1/devices/iphone-piotr/messages
.
curl -H 'Accept: application/json' -H 'X-AUTH-TOKEN: MMYKUE63gCyFc9blOwcrdr0EP9ghb7yiWZ2lr4fRauM' http://localhost/api/v1/devices/iphone-piotr/messages
W powyższym przypadku, zostaną wykorzystane domyślne wartości dla parametrów sort
, page
oraz results_per_page
, zostanie więc wyświetlone maksymalnie pierwsze dwadzieścia pięć wiadomości, posortowane od najnowszej do najstarszej.
W celu wyświetlenia wiadomości z drugiej strony, przy założeniu, że pięć to lista wyników na stronę, należy przesłać żądanie do ścieżki api/v1/devices/iphone-piotr/messages?page=2&results_per_page=5
.
curl -H 'Accept: appllOwcrdr0EP9ghb7yiWZ2lr4fRauM' 'http://localhost/api/v1/devices/iphone-piotr/messages?page=1&results_per_page=5'
Przykładowa lista zwróconych wiadomości znajduje się poniżej.
[
{
"id": 16,
"body": "Hi, how is it going?",
"device": {
"id": 1,
"name": "iphone-piotr",
"token": "d1KeQHgkIoo:APA91b...",
"user": {
"id": 1,
"username": "piotr42",
"apiKey": "MMYKUE63gCyFc9blOwcrdr0EP9ghb7yiWZ2lr4fRauM",
"roles": [
"ROLE_ADMIN"
],
"password": null,
"salt": null
}
},
"sentAt": "2018-03-14T00:00:00+00:00"
},
{
"id": 18,
"body": "Hi all, team meeting in 15 minutes!",
"device": {
"id": 1,
"name": "iphone-piotr",
"token": "d1KeQHgkIoo:APA91b...",
"user": {
"id": 1,
"username": "piotr42",
"apiKey": "MMYKUE63gCyFc9blOwcrdr0EP9ghb7yiWZ2lr4fRauM",
"roles": [
"ROLE_ADMIN"
],
"password": null,
"salt": null
}
},
"sentAt": "2018-03-14T00:00:00+00:00"
},
{
"id": 20,
"body": "Aren't you forgetting something?",
"device": {
"id": 1,
"name": "iphone-piotr",
"token": "d1KeQHgkIoo:APA91bGBG7vg9rfX0PFbtnrY5yX68x63qGrYm7tWjNbPStOnbK8FbE14DfmWPgzd9a_ucgQRDJqGYFJ-0xG8Cg8KG-ZGyOxpzzP9KavOYq7Kpof7beNHE0vfIuFTI-P3OJfshR3s7O-k",
"user": {
"id": 1,
"username": "piotr42",
"apiKey": "MMYKUE63gCyFc9blOwcrdr0EP9ghb7yiWZ2lr4fRauM",
"roles": [
"ROLE_ADMIN"
],
"password": null,
"salt": null
}
},
"sentAt": "2018-03-15T00:00:00+00:00"
},
{
"id": 21,
"body": "Please check your mail!",
"device": {
"id": 1,
"name": "iphone-piotr",
"token": "d1KeQHgkIoo:APA91bGBG7vg9rfX0PFbtnrY5yX68x63qGrYm7tWjNbPStOnbK8FbE14DfmWPgzd9a_ucgQRDJqGYFJ-0xG8Cg8KG-ZGyOxpzzP9KavOYq7Kpof7beNHE0vfIuFTI-P3OJfshR3s7O-k",
"user": {
"id": 1,
"username": "piotr42",
"apiKey": "MMYKUE63gCyFc9blOwcrdr0EP9ghb7yiWZ2lr4fRauM",
"roles": [
"ROLE_ADMIN"
],
"password": null,
"salt": null
}
},
"sentAt": "2018-03-15T00:00:00+00:00"
},
{
"id": 22,
"body": "test",
"device": {
"id": 1,
"name": "iphone-piotr",
"token": "d1KeQHgkIoo:APA91bGBG7vg9rfX0PFbtnrY5yX68x63qGrYm7tWjNbPStOnbK8FbE14DfmWPgzd9a_ucgQRDJqGYFJ-0xG8Cg8KG-ZGyOxpzzP9KavOYq7Kpof7beNHE0vfIuFTI-P3OJfshR3s7O-k",
"user": {
"id": 1,
"username": "piotr42",
"apiKey": "MMYKUE63gCyFc9blOwcrdr0EP9ghb7yiWZ2lr4fRauM",
"roles": [
"ROLE_ADMIN"
],
"password": null,
"salt": null
}
},
"sentAt": "2018-03-20T00:00:00+00:00"
}
]
By posortować wiadomości od najstarszej do najnowszej, do żądania należy dodać parametr sort
, o wartości asc
.
curl -H 'Accept: appllOwcrdr0EP9ghb7yiWZ2lr4fRauM' 'http://localhost/api/v1/devices/iphone-piotr/messages?page=1&results_per_page=5&sort=asc'
Curriculum
- Web Development z Symfony: Aplikacja serwerowa do wysyłki powiadomień push z FCM [część 7]
- Web Development z Symfony: Aplikacja serwerowa do wysyłki powiadomień push z FCM [część 6]
- Web Development z Symfony: Aplikacja serwerowa do wysyłki powiadomień push z FCM [część 5]
- Web Development z Symfony: Aplikacja serwerowa do wysyłki powiadomień push z FCM [część 4]
- Web Development z Symfony: Aplikacja serwerowa do wysyłki powiadomień push z FCM [część 3]
- Web Development z Symfony: Aplikacja serwerowa do wysyłki powiadomień push z FCM [część 2]
- Web Development z Symfony: Aplikacja serwerowa do wysyłki powiadomień push z FCM [część 1]
Congratulations! This post has been upvoted from the communal account, @minnowsupport, by piotr42 from the Minnow Support Project. It's a witness project run by aggroed, ausbitbank, teamsteem, theprophet0, someguy123, neoxian, followbtcnews, and netuoso. The goal is to help Steemit grow by supporting Minnows. Please find us at the Peace, Abundance, and Liberty Network (PALnet) Discord Channel. It's a completely public and open space to all members of the Steemit community who voluntarily choose to be there.
If you would like to delegate to the Minnow Support Project you can do so by clicking on the following links: 50SP, 100SP, 250SP, 500SP, 1000SP, 5000SP.
Be sure to leave at least 50SP undelegated on your account.
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
Congratulations @piotr42! You have completed some achievement on Steemit and have been rewarded with new badge(s) :
Award for the number of posts published
Award for the total payout received
Click on any badge to view your own Board of Honor on SteemitBoard.
To support your work, I also upvoted your post!
For more information about SteemitBoard, click here
If you no longer want to receive notifications, reply to this comment with the word
STOP
Do not miss the last announcement from @steemitboard!
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit