В этом посте я хотел бы поговорить о том, как правильно построить RESTfull API для AngularJS и других интерфейсных фреймворков с бэкендом Symfony. И, как вы уже, наверное, догадались, я буду использовать FOSRestBundle — замечательный бандл, который поможет нам реализовать бэкенд. Не будет примеров того, как работать конкретно с Angular; Я опишу только работу с Symfony FosRestBundle. Для работы нам также понадобится JMSSerializerBundle для сериализации данных из Entity в JSON или другие форматы, исключая некоторые поля для конкретной сущности (например, пароль для метода API для получения списка пользователей) и многое другое.
Подробнее можно прочитать в документации.
Установка и настройка 1) Загрузите необходимые зависимости в наш композитор.
json
2)Конфигурация"friendsofsymfony/rest-bundle": "^1.7", "jms/serializer-bundle": "^1.1"
// 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
-
Воск
19 Oct, 24 -
Релиз Mc-4.8.2
19 Oct, 24 -
Зачем Разработчику Нужна Сертификация Agile?
19 Oct, 24 -
Дисковый Ускоритель От Sandisk
19 Oct, 24