Я пытаюсь использовать Symfony Voters и Controller Annotation, чтобы разрешить или ограничить доступ к определенным действиям в моем приложении Symfony 4.
Например, «Мой интерфейс» предоставляет возможность удалить «Сообщение», но только если у пользователя установлен атрибут «DELETE_POST» для этого поста.
Внешний интерфейс отправляет действие HTTP «DELETE» в мою конечную точку Symfony, передавая идентификатор сообщения в URL-адресе (например, /api/post/delete/19).
Пытаюсь использовать аннотацию @IsGranted, как описано здесь.
Вот моя конечная точка Symfony:
/**
* @Route("/delete/{id}")
* @Method("DELETE")
* @IsGranted("DELETE_POST", subject = "post")
*/
public function deletePost($post) {
... some logic to delete post
return new Response("Deleting " . $post->getId());
}
Вот мой избиратель:
class PostVoter extends Voter {
private $attributes = array(
"VIEW_POST", "EDIT_POST", "DELETE_POST", "CREATE_POST"
);
protected function supports($attribute, $subject) {
return in_array($attribute, $this->attributes, true) && $subject instanceof Post;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) {
... logic to figure out if user has permissions.
return $check;
}
}
Проблема, с которой я столкнулся, заключается в том, что мой интерфейс просто отправляет идентификатор ресурса в мою конечную точку. Затем Symfony разрешает аннотацию @IsGranted, вызывая Voters и передавая атрибут "DELETE_POST" и идентификатор сообщения.
Проблема в том, что $ post - это просто идентификатор сообщения, а не фактический объект Post. Поэтому, когда избиратель достигает $subject instanceof Post, он возвращает false.
Я попытался ввести Post в свой метод контроллера, изменив сигнатуру метода на public function deletePost(Post $post). Конечно, это не работает, потому что javascript отправляет идентификатор в URL-адресе, а не объект Post.
(Кстати: я знаю, что этот тип внедрения должен работать с Doctrine, но я использую Doctrine как нет).
Мой вопрос в том, как мне заставить @IsGranted понять, что «пост» должен быть объектом поста? Есть ли способ указать ему искать сообщение по переданному идентификатору и оценивать его на основе этого? Или даже обратиться к другому методу контроллера, чтобы определить, что должен представлять subject = "post"?
Спасибо.
ОБНОВИТЬ
Благодаря @NicolasB я добавил ParamConverter:
class PostConverter implements ParamConverterInterface {
private $dao;
public function __construct(MySqlPostDAO $dao) {
$this->dao = $dao;
}
public function apply(Request $request, ParamConverter $configuration) {
$name = $configuration->getName();
$object = $this->dao->getById($request->get("id"));
if (!$object) {
throw new NotFoundHttpException("Post not found!");
}
$request->attributes->set($name, $object);
return true;
}
public function supports(ParamConverter $configuration) {
if ($configuration->getClass() === "App\\Model\\Objects\\Post") {
return true;
}
return false;
}
}
Кажется, это работает, как ожидалось. Мне даже не пришлось использовать аннотацию @ParamConverter, чтобы она работала. Единственным другим изменением, которое мне пришлось внести в контроллер, было изменение сигнатуры метода моего маршрута на public function deletePost(Post $post) (как я пробовал ранее - но теперь работает благодаря моему PostConverter).
Мои последние два вопроса:
Что именно я должен проверить в методе supports()? Сейчас я просто проверяю соответствие класса. Следует ли мне также проверить этот $configuration->getName() == "id", чтобы убедиться, что я работаю с правильным полем?
Как я могу сделать его более общим? Правильно ли я предполагаю, что каждый раз, когда вы вводите объект в метод контроллера, Symfony будет вызывать метод supports для всего, что реализует ParamConverterInterface?
Спасибо.






Что бы произошло, если бы вы использовали Doctrine, так это то, что вам нужно было бы ввести свою переменную $post. После того, как вы это сделаете, об остальном позаботится Doctrine ParamConverter. Прямо сейчас Symfony не знает, как связать заполнитель URL-адреса id с параметром $post, потому что он не знает, к какому объекту относится $post. Подсказывая тип с помощью чего-то вроде public function deletePost(Post $post) и используя ParamConverter, Symfony будет знать, что $post относится к объекту Post с идентификатором из заполнителя URL-адреса id.
Из документа:
Normally, you'd expect a $id argument to show(). Instead, by creating a new argument ($post) and type-hinting it with the Post class (which is a Doctrine entity), the ParamConverter automatically queries for an object whose $id property matches the {id} value. It will also show a 404 page if no Post can be found.
Тогда избиратель также узнает, что такое $post и как с ним лечить.
Теперь, поскольку вы не используете Doctrine, у вас нет ParamConverter по умолчанию, и, как мы только что видели, это ключевой элемент здесь. Итак, что вам нужно сделать, это просто определить свой собственный ParamConverter.
Эта страница документации Symfony расскажет вам больше о том, как это сделать, особенно последний раздел «Создание конвертера». Вам нужно будет рассказать ему, как преобразовать строку "id" в объект Post, используя логику вашей модели. Сначала вы можете сделать его очень специфичным для объектов Post (и вы можете явно указать этот ParamConverter в аннотации, используя опцию converter = "name"). Позже, когда у вас будет рабочая версия, вы можете сделать ее более общей.
api/post/delete/19- не совсем конечная точка REST. Это должен бытьapi/post/19, и вы знаете, что это удаление из команды HTTP. У вас была особая причина, почему бы не сделать это какapi/post/19?