Перевод статьи подготовлен в преддверии старта курса «Symfony Framework».

В первой части статьи мы рассмотрим самый простой способ реализации REST API в проекте Symfony без использования FosRestBundle. Во второй части, которую я опубликую следом, мы рассмотрим JWT аутентификацию. Прежде чем мы начнем, сперва мы должны понять, что на самом деле означает REST.
Что означает Rest?
REST (Representational State Transfer — передача состояния представления) — это архитектурный стиль разработки веб-сервисов, который невозможно игнорировать, потому что в современной экосистеме существует большая потребность в создании Restful-приложений. Это может быть связано с уверенным подъемом позиций JavaScript и связанных фреймворков.
REST API использует протокол HTTP. Это означает, что когда клиент делает какой-либо запрос к такому веб-сервису, он может использовать любой из стандартных HTTP-глаголов: GET, POST, DELETE и PUT. Ниже описано, что произойдет, если клиент укажет соответствующий глагол.
- GET: будет использоваться для получения списка ресурсов или сведений о них.
- POST: будет использоваться для создания нового ресурса.
- PUT: будет использоваться для обновления существующего ресурса.
- DELETE: будет использоваться для удаления существующего ресурса.
REST не имеет состояний (state), и это означает, что на стороне сервера тоже нет никаких состояний запроса. Состояния остаются на стороне клиента (пример — использование JWT для аутентификации, с помощью которого мы собираемся засекьюрить наш REST API). Таким образом, при использовании аутентификации в REST API нам нужно отправить аутентификационный заголовок, чтобы получить правильный ответ без хранения состояния.
Создание проекта Symfony:
Во-первых, мы предполагаем, что вы уже установили PHP и менеджер пакетов Сomposer для создания нового проекта Symfony. С этим всем в наличии создайте новый проект с помощью следующей команды в терминале:
composer create-project symfony/skeleton demo_rest_api

Создание проекта Symfony
Мы используем базовый скелет Symfony, который рекомендуется для микросервисов и API. Вот как выглядит структура каталогов:

Структура проекта
Config: содержит все настройки бандла и список бандлов в bundle.php.
Public: предоставляет доступ к приложению через index.php.
Src: содержит все контроллеры, модели и сервисы
Var: содержит системные логи и файлы кэша.
Vendor: содержит все внешние пакеты.
Теперь давайте установим некоторые необходимые пакеты с помощью Сomposer:
composer require symfony/orm-pack composer require sensio/framework-extra-bundle
Мы установили sensio/framework-extra-bundle, который поможет нам упростить код, используя аннотации для определения наших маршрутов.
Нам также необходимо установить symphony/orm-pack для интеграции с Doctrine ORM, чтобы соединиться с базой данных. Ниже приведена конфигурация созданной мной базы данных, которая может быть задана в файле .env.

.env файл конфигурации
Теперь давайте создадим нашу первую сущность. Создайте новый файл с именем Post.php в папке src/Entity.
<?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity * @ORM\Table(name="post") * @ORM\HasLifecycleCallbacks() */ class Post implements \JsonSerializable { /** * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(type="string", length=100 * */ private $name; /** * @ORM\Column(type="text") */ private $description; /** * @ORM\Column(type="datetime") */ private $create_date; /** * @return mixed */ public function getId() { return $this->id; } /** * @param mixed $id */ public function setId($id) { $this->id = $id; } /** * @return mixed */ public function getName() { return $this->name; } /** * @param mixed $name */ public function setName($name) { $this->name = $name; } /** * @return mixed */ public function getDescription() { return $this->description; } /** * @param mixed $description */ public function setDescription($description) { $this->description = $description; } /** * @return mixed */ public function getCreateDate(): ?\DateTime { return $this->create_date; } /** * @param \DateTime $create_date * @return Post */ public function setCreateDate(\DateTime $create_date): self { $this->create_date = $create_date; return $this; } /** * @throws \Exception * @ORM\PrePersist() */ public function beforeSave(){ $this->create_date = new \DateTime('now', new \DateTimeZone('Africa/Casablanca')); } /** * Specify data which should be serialized to JSON * @link https://php.net/manual/en/jsonserializable.jsonserialize.php * @return mixed data which can be serialized by <b>json_encode</b>, * which is a value of any type other than a resource. * @since 5.4.0 */ public function jsonSerialize() { return [ "name" => $this->getName(), "description" => $this->getDescription() ]; } }
И после этого выполните команду: php bin/console doctrine:schema:create для создания таблицы базы данных в соответствии с нашей сущностью Post.
Теперь давайте создадим PostController.php, куда мы добавим все методы, взаимодействующие с API. Он должен быть помещен в папку src/Controller.
<?php /** * Created by PhpStorm. * User: hicham benkachoud * Date: 02/01/2020 * Time: 22:44 */ namespace App\Controller; use App\Entity\Post; use App\Repository\PostRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; /** * Class PostController * @package App\Controller * @Route("/api", name="post_api") */ class PostController extends AbstractController { /** * @param PostRepository $postRepository * @return JsonResponse * @Route("/posts", name="posts", methods={"GET"}) */ public function getPosts(PostRepository $postRepository){ $data = $postRepository->findAll(); return $this->response($data); } /** * @param Request $request * @param EntityManagerInterface $entityManager * @param PostRepository $postRepository * @return JsonResponse * @throws \Exception * @Route("/posts", name="posts_add", methods={"POST"}) */ public function addPost(Request $request, EntityManagerInterface $entityManager, PostRepository $postRepository){ try{ $request = $this->transformJsonBody($request); if (!$request || !$request->get('name') || !$request->request->get('description')){ throw new \Exception(); } $post = new Post(); $post->setName($request->get('name')); $post->setDescription($request->get('description')); $entityManager->persist($post); $entityManager->flush(); $data = [ 'status' => 200, 'success' => "Post added successfully", ]; return $this->response($data); }catch (\Exception $e){ $data = [ 'status' => 422, 'errors' => "Data no valid", ]; return $this->response($data, 422); } } /** * @param PostRepository $postRepository * @param $id * @return JsonResponse * @Route("/posts/{id}", name="posts_get", methods={"GET"}) */ public function getPost(PostRepository $postRepository, $id){ $post = $postRepository->find($id); if (!$post){ $data = [ 'status' => 404, 'errors' => "Post not found", ]; return $this->response($data, 404); } return $this->response($post); } /** * @param Request $request * @param EntityManagerInterface $entityManager * @param PostRepository $postRepository * @param $id * @return JsonResponse * @Route("/posts/{id}", name="posts_put", methods={"PUT"}) */ public function updatePost(Request $request, EntityManagerInterface $entityManager, PostRepository $postRepository, $id){ try{ $post = $postRepository->find($id); if (!$post){ $data = [ 'status' => 404, 'errors' => "Post not found", ]; return $this->response($data, 404); } $request = $this->transformJsonBody($request); if (!$request || !$request->get('name') || !$request->request->get('description')){ throw new \Exception(); } $post->setName($request->get('name')); $post->setDescription($request->get('description')); $entityManager->flush(); $data = [ 'status' => 200, 'errors' => "Post updated successfully", ]; return $this->response($data); }catch (\Exception $e){ $data = [ 'status' => 422, 'errors' => "Data no valid", ]; return $this->response($data, 422); } } /** * @param PostRepository $postRepository * @param $id * @return JsonResponse * @Route("/posts/{id}", name="posts_delete", methods={"DELETE"}) */ public function deletePost(EntityManagerInterface $entityManager, PostRepository $postRepository, $id){ $post = $postRepository->find($id); if (!$post){ $data = [ 'status' => 404, 'errors' => "Post not found", ]; return $this->response($data, 404); } $entityManager->remove($post); $entityManager->flush(); $data = [ 'status' => 200, 'errors' => "Post deleted successfully", ]; return $this->response($data); } /** * Returns a JSON response * * @param array $data * @param $status * @param array $headers * @return JsonResponse */ public function response($data, $status = 200, $headers = []) { return new JsonResponse($data, $status, $headers); } protected function transformJsonBody(\Symfony\Component\HttpFoundation\Request $request) { $data = json_decode($request->getContent(), true); if ($data === null) { return $request; } $request->request->replace($data); return $request; } }
Здесь мы определили пять маршрутов:
● GET /api/posts: вернет список постов.

api получения всех постов
● POST /api/posts: создаст новый пост.

api добавления нового поста
● GET /api/posts/id: вернет пост, соответствующий определенному идентификатору,

получение конкретного поста
● PUT /api/posts/id: обновит пост.

обновление поста
Это результат после обновления:

пост после обновления
● DELETE /api/posts/id: удалит пост.

удаление поста
Это результат получения всех постов после удаления поста с идентификатором 3:

все посты после удаления
Исходный код можно найти здесь
Заключение
Итак, теперь мы понимаем, что такое REST и Restful. Restful API должен быть без состояний. Мы знаем, как создать Restful-приложение, используя HTTP-глаголы. В общем, теперь мы хорошо понимаем REST и готовы профессионально создавать Restful-приложения.
В следующей статье мы рассмотрим, как обеспечить секьюрность API с помощью JWT аутентификации.
Узнать подробнее о курсе «Symfony Framework»
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/509842/
Добавить комментарий