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

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
07-Июл-2020 07:31

Перевод статьи подготовлен в преддверии старта курса «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»
===========
Источник:
habr.com
===========

===========
Автор оригинала: Hicham Ben Kachoud
===========
Похожие новости: Теги для поиска: #_php, #_symfony, #_api, #_php, #_symfony_framework, #_rest_api, #_otus, #_fosrestbundle, #_jwt, #_blog_kompanii_otus._onlajnobrazovanie (
Блог компании OTUS. Онлайн-образование
)
, #_php, #_symfony, #_api
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 25-Ноя 20:38
Часовой пояс: UTC + 5