Symfony як використовувати FOSRestBundle

У цьому пості я б хотів розповісти про те, як потрібно правильно вибудовувати RESTfull API для AngularJS та інших фронтенд фреймворків з бекендом на Symfony.
І, як ви вже напевно здогадалися, я буду використовувати FOSRestBundle — чудовий bundle, який і допоможе нам реалізувати backend.
Тут не буде прикладів як працювати саме з Ангуляром, я буду описувати виключно тільки роботу з Symfony FosRestBundle.

Для роботи нам знадобиться JMSSerializerBundle для серіалізації даних з Entity в JSON або інші формати, виключення деяких полів для тієї чи іншої сутності (наприклад, пароль для API методу отримання списку користувачів) і багато іншого. Детальніше можете почитати в документації.


Установка й конфігурування
1)Завантажуємо потрібні залежності в нашому composer.json

"friendsofsymfony/rest-bundle": "^1.7",
 
"jms/serializer-bundle": "^1.1"
 


2)Конфігурування
// app/AppKernel.php
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ...
new JMS\SerializerBundle\JMSSerializerBundle(),
new FOS\RestBundle\FOSRestBundle(),
);

// ...
}
}


А тепер редагуємо наш config.yml
Для початку будемо налаштовувати наш FOSRestBundle
fos_rest:
body_listener: true
view:
view_response_listener: true
serializer:
serialize_null: true
body_converter:
enabled: true
format_listener:
rules:
- { path: '^/api', priorities: ['json'], fallback_format: json, exception_fallback_format: html, prefer_extension: true }
- { path: '^/', priorities: [ 'html', '*/*'], fallback_format: html, prefer_extension: true }

body_listener включає EventListener для того, щоб відстежувати який формат відповіді потрібен користувачу, грунтуючись на його Accept-* заголовках
view_response_listener — ця настройка дозволяє просто повернути View для того чи іншого запиту
serializer.serialize_null — ця настройка говорить про те, що ми так само хочемо, щоб NULL сериализовывался, як і всі, якщо її не встановити або встановити як false, тоді всі поля, що мають null — просто напросто не будуть відображатися у відповіді сервера.
P. S.: дякую, що нагадав lowadka
body_converter.rules — містить масив для налаштувань, орієнтований на той чи інший адресу, у даному прикладі ми для усіх запитів, які мають префікс /api, будемо повертати JSON, у всіх інших випадках — html.

Тепер почнемо налаштування нашого JMSSerializeBundle
jms_serializer:
property_naming:
separator: _
lower_case: true

metadata:
cache: file
debug: "%kernel.debug%"
file_cache:
dir: "%kernel.cache_dir%/serializer"
directories:
FOSUserBundle:
namespace_prefix: FOS\UserBundle
path: %kernel.root_dir%/config/serializer/FosUserBundle
AppBundle:
namespace_prefix: AppBundle
path: %kernel.root_dir%/config/serializer/AppBundle
auto_detection: true


Тут має сенс зупинитися на моменті з jms_serializer.metadata.directories, де ми говоримо serializer-у про те, що конфігурація для того чи іншого класу (сутності) знаходиться там-то й там-то :)
Умовимося, що нам потрібно вивести весь список користувачів, я особисто використовую FosUserBundle у своїх проектах і ось моя сутність:
<?php

namespace AppBundle\Entity;

use JMS\Serializer\Annotation\Expose;
use JMS\Serializer\Annotation\Groups;
use JMS\Serializer\Annotation\Exclude;
use JMS\Serializer\Annotation\VirtualProperty;
use JMS\Serializer\Annotation\ExclusionPolicy;

use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
use FOS\UserBundle\Model\Group;

/**
* User
*
* @ORM\Table(name="user")
* @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
* @ExclusionPolicy("all")
*/
class User extends BaseUser
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="АВТО")
* @Exclude
*/
protected $id;

/**
* @ORM\Column(type="integer")
* @Groups({"user"})
* @Expose
*/
private $balance = 0;

/**
* Set balance
*
* @param integer $balance
*
* @return User
*/
public function setBalance($balance)
{
$this->balance = $balance;

return $this;
}

/**
* Get balance
*
* @return integer
*/
public function getBalance()
{
return $this->balance;
}
}



Я наводжу в приклад саме цю сутність, яка успадковується від основної моделі FosUserBundle. Це важливо тому що обидва класи доведеться конфігурувати для JmsSerializerBundle окремо.
Отже, повернемося jms_serializer.metadata.directories:
directories:
FOSUserBundle:
namespace_prefix: FOS\UserBundle
path: %kernel.root_dir%/config/serializer/FosUserBundle
AppBundle:
namespace_prefix: AppBundle
path: %kernel.root_dir%/config/serializer/AppBundle

Тут ми вказуємо, що для AppBundle класів ми будемо шукати конфігурацію для серіалізації в app/config/serializer/AppBundle, а для FosUserBundle — в app/config/serializer/FosUserBundle.
Конфігурація для класу буде знаходитися автоматично в форматі:
Для класу AppBundle\Entity\User — app/config/serializer/AppBundle/Entity.User.(yml|xml|php)
Для класу базової моделі FosUserBundle — app/config/serializer/FosUserBundle/Model.User.(yml|xml|php)

Особисто я віддаю перевагу використовувати YAML. Почнемо нарешті-таки розповідати JMSSerializer яким чином нам потрібно щоб він налаштовував той або інший клас.
app/config/serializer/AppBundle/Entity.User.yml
AppBundle\Entity\User:
exclusion_policy: ALL
властивості:
balance:
expose: true


app/config/serializer/FosUserBundle/Model.User.yml
FOS\UserBundle\Model\User:
exclusion_policy: ALL
group: user
властивості:
id:
expose: true
username:
expose: true
email:
expose: true
balance:
expose: true


Ось так просто ми змогли розповісти про те, що хочемо бачити приблизно наступний формат відповіді від сервера при отриманні даних від 1 користувач:
{"id":1,"username":"admin","email":"admin","balance":0}


В принципі дану конфігурацію обов'язково прописувати і сервер буде повернути всі дані про сутності. Тільки в даному випадку нам нелогічно показувати багато речей, наприклад такі, як пароль. Тому я вважав за потрібне продемонструвати в даному прикладі саме таку реалізацію.

Тепер приступимо до створення контролера
Першим ділом створимо рауса:
backend_user:
resource: "@BackendUserBundle/Resources/config/routing.yml"
prefix: /api

Зверніть увагу на /api — не забувайте додавати його, а якщо хочете змінити, то доведеться міняти і конфігурацію для fos_rest в config.yml

Тепер сам BackendUserBundle/Resources/config/routing.yml:
backend_user_users:
type: rest
resource: "@BackendUserBundle/Controller/UsersController.php"
prefix: /v1


Тепер можна приступати до створення самого контролера:
<?php

namespace Backend\UserBundle\Controller;

use AppBundle\Entity\User;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\Annotations\View;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
* Class UsersController
* @package Backend\UserBundle\Controller
*/
class UsersController extends FOSRestController
{
/**
* @return \Symfony\Component\HttpFoundation\Response
* @View(serializerGroups={"user"})
*/
public function getUsersAllAction()
{
$users = $this->getDoctrine()->getRepository('AppBundle:User')->findAll();

$view = $this->view($users, 200);
return $this->handleView($view);
}


/**
* @param $id
* @return \Symfony\Component\HttpFoundation\Response
* @View(serializerGroups={"user"})
*/
public function getUserAction($id)
{
$user = $this->getDoctrine()->getRepository('AppBundle:User')->find($id);

if (!$user instanceof User) {
throw new NotFoundHttpException('User not found');
}

$view = $this->view($user, 200);
return $this->handleView($view);
}
}



Зауважимо, що наследуемся ми тепер від FOS\RestBundle\Controller\FOSRestController.
До речі, ви напевно звернули увагу на анотацію View(serializerGroups={«user»}).
Справа в тому, що оскільки ми хочемо бачити і дані App\Entity\User і основної моделі FosUserBundle, в якій зберігаються всі інші поля, ми повинні створити певну групу, в даному випадку — «user».

Отже, у нас є 2 екшену getUserAction і getUsersAllAction. Зараз ви зрозумієте суть специфіки назв методів контролера.
Зробимо debug всіх роутов:
$ app/console debug:route | grep api
Отримуємо:
get_users_all GET ANY ANY /api/v1/users/all.{_format} 
get_user GET ANY ANY /api/v1/users/{id}.{_format} 


Розглянемо наступний приклад з новими методами:
<?php
class UsersComment extends Controller
{
public function postUser($id)
{} // "post_user_comment_vote" [POST] /users/{id}

public function getUser($id)
{} // "get_user" [GET] /users/{id}

public function deleteUserAction($id)
{} // "delete_user" [DELETE] /users/{id}

public function newUserAction($id)
{} // "new_user" [GET] /users/{id}/new

public function editUserAction($slug, $id)
{} // "edit_user" [GET] /users/{id}/edit

public function removeUserAction($slug)
{} // "remove_user" [GET] /users/{slug}/remove
}

Нагадує Laravel Resource Controller, правда?

В коментарях показано за якою адресою і методом запиту буде виконаний той чи інший метод.
Наступного разу я розповім вам про те, як правильно використовувати FOSRestBundle для, наприклад, виведення коментарів певного користувача за адресою: "/users/{id}/comments", створювати \ оновлювати дані користувачів.

Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.