REST API в Symfony (без FosRestBundle) с использованием JWT аутентификации. Часть 1

от автора

Перевод статьи подготовлен в преддверии старта курса «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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *