Php 保护实体的某些属性不受修改

Php 保护实体的某些属性不受修改,php,symfony,orm,crud,symfony-forms,Php,Symfony,Orm,Crud,Symfony Forms,我有一个Symfony 3应用程序,它使用ORM进行实体管理。目前,我正在致力于启用CRUD支持。我已经发现我可以使用它来限制对实体或控制器的访问。例如,我将其配置为只有管理员才能创建、更新或删除类型为A的实体 对于我的实体类型B的实例,我还希望赋予相应的所有者更新(而不是创建或删除)的权限,这一点我很容易做到。但是,不应该允许所有者修改实体的所有属性——只是其中的一部分。我怎样才能通过Symfony实现这一点?此外,我还使用表单包创建和验证表单 编辑:我添加了一些相关代码。控制器调用denya

我有一个Symfony 3应用程序,它使用ORM进行实体管理。目前,我正在致力于启用CRUD支持。我已经发现我可以使用它来限制对实体或控制器的访问。例如,我将其配置为只有管理员才能创建、更新或删除类型为A的实体

对于我的实体类型B的实例,我还希望赋予相应的所有者更新(而不是创建或删除)的权限,这一点我很容易做到。但是,不应该允许所有者修改实体的所有属性——只是其中的一部分。我怎样才能通过Symfony实现这一点?此外,我还使用表单包创建和验证表单

编辑:我添加了一些相关代码。控制器调用
denyaccessunlessgrated
,触发投票者。只是澄清一下,该代码已经可以正常工作了。我的问题与我还没有的代码有关

控制器:

public function editAction(Request $request, int $id) {

    $em = $this->getDoctrine()->getManager();

    $project = $em->getRepository(Project::class)->findOneBy(['id'=>$id]);

    $this->denyAccessUnlessGranted(ProjectVoter::EDIT, $project);

    $users = $em->getRepository(EntityUser::class)->findAll();
    $groups = $em->getRepository(Group::class)->findAll();
    $tags = $em->getRepository(Tag::class)->findAll();

    $form = $this->createForm(ProjectType::class, $project, [
        'possibleAdmins' => $users,
        'possibleRequiredGroups' => $groups,
        'possibleTags' => $tags,
    ]);

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {

        $project = $form->getData();
        $em->flush();

        return $this->redirectToRoute('projects_show', ['id'=>$project->getId()]);
    }

    return $this->render('project/editor.html.twig',
        ['project'=>$project, 'form'=>$form->createView()]);
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) {

    /** @var UserInterface $user */
    $user = $token->getUser();

    if (!$user instanceof UserInterface) {
        // the user must be logged in; if not, deny access
        return false;
    }
    else if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
        return true; // system-wide admins shall always have access
    }

    switch($attribute) {

        case self::SHOW:
            return  ($subject->isVisible() || $subject->getAdmins()->contains($user);

        case self::EDIT:
            return $subject->getAdmins()->contains($user);

        case self::REMOVE:
            return false;
    }

    return false;
}
投票人:

public function editAction(Request $request, int $id) {

    $em = $this->getDoctrine()->getManager();

    $project = $em->getRepository(Project::class)->findOneBy(['id'=>$id]);

    $this->denyAccessUnlessGranted(ProjectVoter::EDIT, $project);

    $users = $em->getRepository(EntityUser::class)->findAll();
    $groups = $em->getRepository(Group::class)->findAll();
    $tags = $em->getRepository(Tag::class)->findAll();

    $form = $this->createForm(ProjectType::class, $project, [
        'possibleAdmins' => $users,
        'possibleRequiredGroups' => $groups,
        'possibleTags' => $tags,
    ]);

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {

        $project = $form->getData();
        $em->flush();

        return $this->redirectToRoute('projects_show', ['id'=>$project->getId()]);
    }

    return $this->render('project/editor.html.twig',
        ['project'=>$project, 'form'=>$form->createView()]);
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) {

    /** @var UserInterface $user */
    $user = $token->getUser();

    if (!$user instanceof UserInterface) {
        // the user must be logged in; if not, deny access
        return false;
    }
    else if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
        return true; // system-wide admins shall always have access
    }

    switch($attribute) {

        case self::SHOW:
            return  ($subject->isVisible() || $subject->getAdmins()->contains($user);

        case self::EDIT:
            return $subject->getAdmins()->contains($user);

        case self::REMOVE:
            return false;
    }

    return false;
}

据我所知,没有专门与单个属性相关的访问功能。当然,一旦我发布了这篇文章,其他人就会带着这篇文章过来

您可能要考虑的是定义两个编辑角色,EdgEyByAdmin和EdgyByButhObjor。然后可以测试条件并选择要使用的表单类型

$projectTypeClass = null;
if ($this->isGranted(ProjectVoter::EDIT_BY_ADMIN,$project)) {
    $projectTypeClass = ProjectAdminType::class);
}
elseif ($this->isGranted(ProjectVoter::EDIT_BY_OWNER,$project)) {
    $projectTypeClass = ProjectOwnerType::class);
}
if (!$projectTypeClass) {
    // throw access denied exception
}
$form = $this->createForm($projectTypeClass, $project, [
这就应该奏效了。当然有很多变化。您可以坚持使用一种项目类型,并在类型类中进行访问测试,尽管这需要一个表单侦听器

如果您需要更大的粒度,那么可以添加一些EDIT_PROP1、EDIT_PROP2类型的角色


当然,如果你真的对它感兴趣,那么你可以将一些访问代码移动到某种数据库中。或者可以看看访问控制列表的一些组件。

我最终想出了这个解决方案:

我没有使用多个
FormType
s,而是只使用了一个,并根据投票者的结果启用或禁用了属性的表单字段。为此,我按照Cerad的建议定义了一个新的交换机案例(在这个答案中命名为
ProjectVoter::MODIFY\u PROTECTED\u PROPERTY
),并根据自己的喜好添加了业务逻辑

我只是启用或禁用表单字段,因为我实际上希望用户看到他/她无法编辑该属性。但是很可能不在第一个位置添加字段,因此它不可见

表单类型:

public function editAction(Request $request, int $id) {

    $em = $this->getDoctrine()->getManager();

    $project = $em->getRepository(Project::class)->findOneBy(['id'=>$id]);

    $this->denyAccessUnlessGranted(ProjectVoter::EDIT, $project);

    $users = $em->getRepository(EntityUser::class)->findAll();
    $groups = $em->getRepository(Group::class)->findAll();
    $tags = $em->getRepository(Tag::class)->findAll();

    $form = $this->createForm(ProjectType::class, $project, [
        'possibleAdmins' => $users,
        'possibleRequiredGroups' => $groups,
        'possibleTags' => $tags,
    ]);

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {

        $project = $form->getData();
        $em->flush();

        return $this->redirectToRoute('projects_show', ['id'=>$project->getId()]);
    }

    return $this->render('project/editor.html.twig',
        ['project'=>$project, 'form'=>$form->createView()]);
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) {

    /** @var UserInterface $user */
    $user = $token->getUser();

    if (!$user instanceof UserInterface) {
        // the user must be logged in; if not, deny access
        return false;
    }
    else if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
        return true; // system-wide admins shall always have access
    }

    switch($attribute) {

        case self::SHOW:
            return  ($subject->isVisible() || $subject->getAdmins()->contains($user);

        case self::EDIT:
            return $subject->getAdmins()->contains($user);

        case self::REMOVE:
            return false;
    }

    return false;
}
信息:
$this->tokenStorage
$this->accessDecisionManager
是注入式服务(
“security.token\u storage”
“security.access.decision\u manager”


我还在表单类型的
configureOptions
函数中添加了一个名为
token
的选项,该选项默认为
null
,因此可以为任意用户而不是当前登录的用户生成表单,如果需要。

为了更清楚,您应该添加您已有的相关代码,以便我们可以基于此提供特定的解决方案。@gp\u是的,首先应该这样做。谢谢你的提醒。谢谢你的投入,真的很有帮助!如果你想知道我最后做了什么,请看我的答案。