Comments 40
Когда открывал статью то думал что первым же пунктом увижу что-то по поводу форм. Обрадовался когда увидел хотя бы упоминания что формы юзать в контексте API не ок (да, когда-то я думал что ок).
JMS Serializer — самое распространенное решение не лишенное проблем. По сути оно навязывает решение проблем, которое само и привносит.
Далее я надеялся увидеть альтернативные подходы… но увы и ах, конец… на самом интересном месте.
Я все же думаю что стоит выкинуть JmsSerializer и выкидывать простенькие DTO из и в сервисный слой. Хоть в виде ассоциативных массивов, хоть в виде классов с сеттерами, хоть в виде stdObject.
Я бы хотел увидеть, скажем… отложенное формирование view во фронт контроллере, что бы можно было флаш доктрины туда же вынести.
JMS Serializer — самое распространенное решение не лишенное проблем. По сути оно навязывает решение проблем, которое само и привносит.
Далее я надеялся увидеть альтернативные подходы… но увы и ах, конец… на самом интересном месте.
Я все же думаю что стоит выкинуть JmsSerializer и выкидывать простенькие DTO из и в сервисный слой. Хоть в виде ассоциативных массивов, хоть в виде классов с сеттерами, хоть в виде stdObject.
Я бы хотел увидеть, скажем… отложенное формирование view во фронт контроллере, что бы можно было флаш доктрины туда же вынести.
+2
А можно пример какой-нибудь, как вы работаете без JMS Serializer?
Насчет форм:
У нас все запросы, которые содержат в себе какие-либо параметры, обязательно мапятся на модели. Даже если это какой-нибудь список с фильтрами. Поэтому мы отказались полностью от форм и просто в моделях пишем что-то подобное:
И в контроллере:
Насчет форм:
У нас все запросы, которые содержат в себе какие-либо параметры, обязательно мапятся на модели. Даже если это какой-нибудь список с фильтрами. Поэтому мы отказались полностью от форм и просто в моделях пишем что-то подобное:
class IntakeFilter
{
protected $user;
/**
* @RequestMapper(type="integer")
*/
protected $limit = 20;
/**
* @RequestMapper(type="integer")
*/
protected $offset = 0;
/**
* @RequestMapper(type="boolean")
*/
protected $isCompleted;
/**
* @RequestMapper(type="string")
* @Assert\Length(min=2)
*/
protected $query;
}
И в контроллере:
public function actionList()
{
$model = $this->handleRequest(new App\Model\IntakeFilter($this->getUser());
return $repository->findByFilter($model);
}
0
Ну не сказать что я отказался от JmsSerializer, только на домашних проектах пока что. Но как-то так:
вот как-то так. Заметте что внутри сервисного слоя заключена все знание о том как наша бизнес логика работает и ни одного упоминания о доктрине. Так же, поскольку все энтити крутятся в unit-of-work, а коммит транзакции происходит вне оного, не очень безопасно возвращать в контроллер саму энтити, так как можно случайно там поменять состояние оной. Лучше плюнуть наружу DTO, что мы и делаем. А уж с простым массивом справится и json_encode.
Как-то так. Пока вариантов лучше я не придумал и не знаю…
class SomeEntity {
public function getShortInfoView() {
// думаю эту штуку можно сократить для пущей читабельности.
// А если у нас будет много вьюшек, можно DRY-ить эти вещи
// или если дела будут совсем плохи, делать нормальные объекты DTO.
return [
'id' => $this->id,
'name' => $this->name,
'some_info' => $this->someInfo,
'connected_entities' => $this->connectedEntities->map(function (SomeConntectedEntity $entity) {
return $entity->getShortInfoView();
})->toArray();
];
}
}
class SomeController {
public function addSomeAction(Request $request) {
// обычно это происходит во фронт контроллере для всех запросов с Content-type application/json
// тут просто для наглядности
$dto = json_encode($request->getContent());
// у меня умерла под конец дня фантазия, так что простите за именования в духе some creator
$shortInfoView = $this->get('app.some_creator')->createSome($dto);
// это так же происходит во фронт контроллере но мне лень
// что до ситуация с ID и их получением до flush
// я использую postgresql и тамошние последовательности, которые прекрасно это дело разруливают
// а автоинкремент mysql это зло
$this->get('doctrine.orm.entity_manager')->flush();
// обычно из контроллера я возвращаю только данные или какой-то View объект в духе FostRest
return new JsonResponse($shortInfoView);
}
}
class SomeCreator
{
private $someRepository;
public function __construct(SomeRepository $repository) {
$this->someRepository = $repository;
}
public function create($dto) {
// тут можно провалидировать $dto но лень
// всеравто чуть что конструктор энтити бросит исключение
// ну и лучше что бы $dto был \stdObject.
// Тогда потом можно будет в случае усложнения логики
// переделать его на нормальный DTO объект со своим типом
$entity = new SomeEntity($dto['name'], $dto['short_info']);
$this->someRepository->add($entity);
return $entity->getShortInfoView();
}
}
вот как-то так. Заметте что внутри сервисного слоя заключена все знание о том как наша бизнес логика работает и ни одного упоминания о доктрине. Так же, поскольку все энтити крутятся в unit-of-work, а коммит транзакции происходит вне оного, не очень безопасно возвращать в контроллер саму энтити, так как можно случайно там поменять состояние оной. Лучше плюнуть наружу DTO, что мы и делаем. А уж с простым массивом справится и json_encode.
Как-то так. Пока вариантов лучше я не придумал и не знаю…
+1
Ммм… опечатался слегка… вместо
надо
$entity = new SomeEntity($dto['name'], $dto['short_info']);
надо
$entity = new SomeEntity($dto['name'], $dto['some_info']);
0
Интересный вариант, схема правда та же самая, но инструменты другие. Мне нравится, что это работает довольно быстро, ибо выбрасываем jms serializaer, но больше плюсов, честно говоря, не вижу :(
0
Как же не видите? Все явно, можно полностью проследить всю логику от момента получения данных запроса до вывода наружу. И все это из сервисного слоя приложения. Полностью вся бизнес логика по одной фиче в одном месте. И все можно покрыть тестами. Как по мне это намного более весомый аргумент чем производительность.
0
А ну и еще, я указывал это в комментах в коде но может вы не обратили внимание. У меня обычно flush выполняется непосредственно перед отправкой ответа, во фронт контроллере. И если мы выплюнем сущность из сервиса в контроллер, кто-то может изменить (случайно или специально) состояние сущности и мы получим баги. При моем варианте же возвращается DTO, и если мы чего поменяем в нем, то как бы и пофигу.
0
И еще очень интересно, о каких именно проблемах с JMS Serializer вы говорите, ибо пока что это очень удобно. Контроллеры выглядят примерно так:
/**
* @Common\Annotation\SerializationGroup("clinic_details")
* @Common\Annotation\HttpStatus(201)
*
* @Method("POST")
* @Route
*/
public function addAction()
{
$entity = $this->handleRequest(new Common\Entity\Clinic($this->getUser());
$this->persist($entity, true);
return $entity;
}
0
группы сериализации. Это очень мощный инструмент но он вносит в умы людей много смуты. Они начинают оперировать целыми сущностями в рамках какой-то бизнес логики, а не частью оной. То есть по сути необходимость в них есть только за счет того что никто не хочет писать скучный код формирующий DTO или вообще не понимает зачем это нужно и в чем крутость. У меня вот и symfony/validation не используется). Ну а для сложной иерархии состояний можно подключить еще и Value Object-ы, и разруливать это дело на уровне контроллера. Код можно DRY-ить, он явный, можно посадить любого человека знающего PHP и он сразу сможет писать бизнес логику. Ну и все можно покрыть юнит тестами, со штуками типа JMSSerializer, FosRest и т.д. спасают только интеграционные и функциональные тесты.
0
Мне кажется у JMSSerializer слишком много плюшек, чтобы от него отказываться, а проблему с сериализационными группами надо решать не выделением имен по каким-то действиям (list, details), а используя предметную область (default, secure).
0
Мы у себя сделали _fields парметр, как во всех API. Оно ни в коем случае не заменяет группы, которые мы используем чтобы разделить рендеринг приватной информации от общей.
0
Проблемы с выделением групп вообще нет, просто JmsSerializer позволяет тебе выплюнуть наружу целиком твои сущности и дальше разруливать сериализацию гурппами, вместо того что бы выплюнуть DTO где есть все что нужно и ничего лишнего. Оно конечно удобно, позволяет тебе не писать туповатый бойлерплейт, но многие слишком много на него вешают. Доходит до того что весь контроллер состоит из десериализации и персиста, а вся логика размазана по хэндлерам и т.д
0
UFO just landed and posted this here
Не медленно, у вас нету формы, потому использовать Symfony/forms вне форм не целесообразно. Обычно их используют только из-за хорошей интеграции с доктриной из коробки.
0
Причин много, во первых в API нет форм, как уже говорили, во вторых, для того, чтобы формы начали более менее работать – нужно множество костылей, которых накапливается такое количество, что в какой-то момент задаешься вопросом «Ну и нафига все это?», в третьих это действительно очень медлено
0
что для вас формы?
0
Обычные html формы, у которых другие типы данных, есть отображение, данные берутся из $_GET/$_POST
+1
ну вас никто не заставляет их рендерить в html. Я не вижу особых проблем почему для API не стоит использовать формы. Они хорошо подходят для маппинг, валидация реквест данных. Конечно в какой-то момент они избыточны, но согласитесь, что писать велосипед дороже. Хотя есть еще вариант использовать OptionsResolver, но не использовал, не могу по нему сказать какие плюсы или минусы.
0
Ок, допустим мы выбрали формы. И флоу будет таким:
— Приняли запрос
— Во фронт контроллере сделали json_encode тела запроса
— Забиндили результат работы json_encode на энтити через формы
— Валидируем состояние нашей сущности
— забираем из формы готовую сущность, куда формы запихали все через сеттеры. (ненавижу тупые сеттеры в сущностях)
А как можно:
— Приняли запрос
— Во фронт контроллере сделали десериализацию напрямую в сущность через JmsSerializer тела запроса, При этом JmsSerializer достаточно гибок, и имеет меньше ограничений.
— Валидируем состояние нашей сущности
— Как бы все, можно это делать в ParamConverter, и тогда в контроллер придет уже все готовое
(хоть я и не одобряю такой подход)
Вообще если вы берете формы, то работать напрямую с сущностью не очень хорошая идея. Да, на маленьких проектах норм, но чем сложнее проект тем больше боли они приносят. Если работать с DTO то и боли меньше с формами, и сущности красивые выходят.
— Приняли запрос
— Во фронт контроллере сделали json_encode тела запроса
— Забиндили результат работы json_encode на энтити через формы
— Валидируем состояние нашей сущности
— забираем из формы готовую сущность, куда формы запихали все через сеттеры. (ненавижу тупые сеттеры в сущностях)
А как можно:
— Приняли запрос
— Во фронт контроллере сделали десериализацию напрямую в сущность через JmsSerializer тела запроса, При этом JmsSerializer достаточно гибок, и имеет меньше ограничений.
— Валидируем состояние нашей сущности
— Как бы все, можно это делать в ParamConverter, и тогда в контроллер придет уже все готовое
(хоть я и не одобряю такой подход)
Вообще если вы берете формы, то работать напрямую с сущностью не очень хорошая идея. Да, на маленьких проектах норм, но чем сложнее проект тем больше боли они приносят. Если работать с DTO то и боли меньше с формами, и сущности красивые выходят.
0
(ненавижу тупые сеттеры в сущностях)
не вы один
Ну а если помимо API у вас есть веб сайт, где есть такие же сущности, то тогда легче юзать форму для двух вариантов.
Основной мой посыл в том что формы это не панацея. Все зависит от разработчика, который должен понимать что делает, а то потом может быть очень плохо)
0
По поводу entity type. Все очень просто, вы можете указать в опциях query builder и тогда он будет вытягивать с условием in. Да не очевидный кейс но он работает
+1
Я бы в контексте Symfony + API добавил бы в статью ещё и рассказ об FOSRestBundle, о какой-нибудь JWT аутентификации, обновлении и удалении токена и прочем. Ибо когда мне пришлось выполнять данную задачу, то столкнулся с кучей проблем уже на первых парах.
Было бы интересно почитать :)
Было бы интересно почитать :)
+1
JWT аутентификации,
зачем он вам? лучше взять FOSOAuthServerBundle и проблем не будет.
+2
Вот видите, об этом я и говорю — информации в статье крайне мало. Мне, как минимум, было бы интересно почитать и перенять опыт :)
За бандл отдельное спасибо ;)
За бандл отдельное спасибо ;)
+1
Согласен с тем что информации мало, в первую очередь хотел написать статью, для того, чтобы понять насколько это интересно другим. Опыта не мало, особенно в каких-то мелочах, но со временем это все кажется очевидным и просто не понимаешь о чем писать.
У нас на проектах, так исторически сложилось, что мы не используем бандлы для авторизации. Каждый запрос подписывается с помощью заголовка X-Access-Token, а в случае успеха юзер подставляется в security.context. Все это делается буквально в 1 listener, и мне до конца не понятно, зачем использовать что-то готовое, когда написать код дело 3 минут.
Если интересно, могу поделиться кодом или еще лучше написать об этом в следующей статье :)
У нас на проектах, так исторически сложилось, что мы не используем бандлы для авторизации. Каждый запрос подписывается с помощью заголовка X-Access-Token, а в случае успеха юзер подставляется в security.context. Все это делается буквально в 1 listener, и мне до конца не понятно, зачем использовать что-то готовое, когда написать код дело 3 минут.
Если интересно, могу поделиться кодом или еще лучше написать об этом в следующей статье :)
+1
Многие считают OAuth2 избыточным для REST API. Мне этот вариант лично нравится, но не могли бы вы как-то аргументировать почему не будет проблем и почему это круто? Ну мол… можно использовать старую добрую digest авторизацию которая есть в symfony из коробки.
0
Про FOSRestBundle, как и про многие бандлы, которые советуют использовать при создании API, я могу сказать только одно: их очень удобно использовать первое время, пока проект очень маленький и простой. Потом начинаются проблемы, из-за того, что разработчик не понимает как это работает и начинает это использовать не как это изначально, возникают проблемы и на их решение уходит большая часть времени. Но как только схема работы становится очевидной, приходит понимание, что можно сделать и лучше, не использовать кучу кода, который будет висеть мертвым грузом.
Rest Bundle – это же по сути набор небольших скриптов и библиотек, уложенных в определенную структуру. Мне гораздо удобнее использовать их отдельно. Например: бандл для сериализации использует тот же JMS Serializer, только лишь ограничивая в его кастомизации.
Rest Bundle – это же по сути набор небольших скриптов и библиотек, уложенных в определенную структуру. Мне гораздо удобнее использовать их отдельно. Например: бандл для сериализации использует тот же JMS Serializer, только лишь ограничивая в его кастомизации.
+1
Да, это выбор каждого. Я предпочитаю использовать уже написанное и покрытое тестами, а не писать велосипеды. В некоторых случаях своё, конечно, оправданно, не спорю.
0
Если будет лишнее время, на покрытие тестами, постараюсь залить куда-нибудь свои наработки. Что-то вроде своего взгляда на то, как должен выглядеть REST Bundle, ориентированный в первую очередь на скорость разработки и гибкость.
0
Разрешите поделиться нашим опытом:
1. JMSSerializer переусложнен и очень медленный. Даже опытные разработчики могли потерять часы, чтобы что-то подправить в API сгенерированном этим сервисом.
Если нужно сделать что-то сложное, например поиск, это вообще ад.
Джуниора вообще подпускать к этому бандлу нереально.
— Выход, сделали свои трансформеры, простые.
Код понятен, отлично дебажится и тестируется.
Сильно ускорили разработку, поскольку просто (KISS).
2. Symfony2 Form Component не используем.
Хороший компонент, но когда формы обычные.
Если речь идет о REST API, особенно если нужно сделать ~100 entity — формы начинают сильно замедлять разработку. Особенно для PATCH метода.
p.s. Вообще сейчас мигрируем на Spring(java).
С Symfony достаточно давно (c 2009), много кода написано.
Да еще и афилированные партнеры SensioLabs (про это могу в привате отдельно рассказать, кому интересно).
Но… java работает примерно от 10 до 200 раз быстрее.
Архитектура очень похожа на SF2, работы столько же, а выхлоп в разы круче.
1. JMSSerializer переусложнен и очень медленный. Даже опытные разработчики могли потерять часы, чтобы что-то подправить в API сгенерированном этим сервисом.
Если нужно сделать что-то сложное, например поиск, это вообще ад.
Джуниора вообще подпускать к этому бандлу нереально.
— Выход, сделали свои трансформеры, простые.
Код понятен, отлично дебажится и тестируется.
Сильно ускорили разработку, поскольку просто (KISS).
2. Symfony2 Form Component не используем.
Хороший компонент, но когда формы обычные.
Если речь идет о REST API, особенно если нужно сделать ~100 entity — формы начинают сильно замедлять разработку. Особенно для PATCH метода.
p.s. Вообще сейчас мигрируем на Spring(java).
С Symfony достаточно давно (c 2009), много кода написано.
Да еще и афилированные партнеры SensioLabs (про это могу в привате отдельно рассказать, кому интересно).
Но… java работает примерно от 10 до 200 раз быстрее.
Архитектура очень похожа на SF2, работы столько же, а выхлоп в разы круче.
0
JMSSerializer переусложнен и очень медленный. Даже опытные разработчики могли потерять часы, чтобы что-то подправить в API сгенерированном этим сервисом.
Приведите, пожалуйста, конкретный пример. К слову, с такой же ситуацией я и сам недавно столкнулся, но количество времени, которое JMSSerializer сэкономил, ставит на нет некоторые недочеты и сложности.
Если нужно сделать что-то сложное, например поиск, это вообще ад.
А причем serializer к поиску? Если и необходимо дополнительные данные вывести (количество всех записей, текущая страница/сдвиг и т.д.) — ну добавили объект-обертку ResultSet к результирующей коллекции и полет нормальный
Джуниора вообще подпускать к этому бандлу нереально.
Тут сложно не согласиться.
Но… java работает примерно от 10 до 200 раз быстрее.
До 200 раз? Приведите, пожалуйста, пример
0
Приведите, пожалуйста, конкретный пример. К слову, с такой же ситуацией я и сам недавно столкнулся, но количество времени, которое JMSSerializer сэкономил, ставит на нет некоторые недочеты и сложности.
Сделайте кастомную выгрузку коллекции, чтобы там было 3-4 связи и это было скажем на хотябы на 100K записей. А также выводились данные о пагинации.
До 200 раз? Приведите, пожалуйста, пример
www.techempower.com/benchmarks — Искать Spring и Symfony2. На самом деле так и есть.
p.s. Я холиварить не особенно хочу, просто выбор сделал и делюсь с вами.
0
Сделайте кастомную выгрузку коллекции, чтобы там было 3-4 связи и это было скажем на хотябы на 100K записей. А также выводились данные о пагинации.
Ну вы же не 100К записей выводите. У нас есть пример с многомиллионной таблицей — все работает. Сами связи и степень их вложенности ведь запросто контролируются аннотациями, более того можно что-нибудь сложное выбирать в POST_SERIALIZE джоином, если много запросов не устраивает.
Про пагинацию в предыдущем комментарии ответил, нет ничего сложного и «очень медленного».
> www.techempower.com/benchmarks — Искать Spring и Symfony2. На самом деле так и есть.
Хотелось бы реальный пример, ну да ладно.
PS никто не холиварит, здоровый интерес :)
0
Sign up to leave a comment.
Особенности разработки API на Symfony2