Symfony, Как Использовать Fosrestbundle

В этом посте я хотел бы поговорить о том, как правильно построить RESTfull API для AngularJS и других интерфейсных фреймворков с бэкендом Symfony. И, как вы уже, наверное, догадались, я буду использовать FOSRestBundle — замечательный бандл, который поможет нам реализовать бэкенд. Не будет примеров того, как работать конкретно с Angular; Я опишу только работу с Symfony FosRestBundle. Для работы нам также понадобится JMSSerializerBundle для сериализации данных из Entity в JSON или другие форматы, исключая некоторые поля для конкретной сущности (например, пароль для метода API для получения списка пользователей) и многое другое.

Подробнее можно прочитать в документации.

Установка и настройка 1) Загрузите необходимые зависимости в наш композитор.

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 — эта настройка позволяет просто вернуть Представление по конкретному запросу сериализатор.

serialize_null — эта настройка означает, что мы также хотим, чтобы NULL сериализовался, как и все остальное, если он не установлен или установлен в значение false, то все поля, имеющие значение NULL, просто не будут отображаться в ответе сервера.

P.S.: спасибо, что напомнили ловадка 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 , где мы сообщаем сериализатору, что конфигурация для того или иного класса (сущности) находится здесь или там :) Давайте договоримся, что нам необходимо отображать весь список пользователей, я лично в своих проектах использую 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="AUTO") * @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, как он нам нужен для настройки того или иного класса.

приложение/конфигурация/сериализатор/AppBundle/Entity.User.yml

AppBundle\Entity\User: exclusion_policy: ALL properties: balance: expose: true

приложение/конфигурация/сериализатор/FosUserBundle/Model.User.yml

FOS\UserBundle\Model\User: exclusion_policy: ALL group: user properties: 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); } }

Обратите внимание, что теперь мы наследуем от ФОС\РестБундле\Контроллер\ФОСРестконтроллер .

Кстати, вы наверняка заметили аннотацию Вид (serializerGroups={"пользователь"}).

Дело в том, что поскольку мы хотим видеть как данные App\Entity\User, так и основную модель FosUserBundle, в которой хранятся все остальные поля, мы должны создать конкретную группу, в данном случае «user».

Итак, у нас есть 2 действия getUserAction и getUsersAllAction. Теперь вы поймете специфику имен методов контроллера.

Давайте отладим все маршруты:

$ 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, не так ли? В комментариях показано, по какому адресу и методу запроса будет выполнен тот или иной метод. В следующий раз я расскажу, как правильно использовать FOSRestBundle, чтобы, например, отображать комментарии конкретного пользователя по адресу: "/users/{id}/comments", создавать\обновлять данные пользователя.

Теги: #symfony #symfony #api

Вместе с данным постом часто просматривают: