如何安全地使用UniqueEntity(在具有多个同时用户的站点上)

时间:2016-11-25 00:21:56

标签: php symfony concurrency doctrine-orm

聪明的人可以共享他们使用的设计模式,以避免Doctrine \ Symfony中的这个基本和常见的并发问题吗?

场景:每个用户都必须拥有唯一的用户名。

解决方案失败:

为什么失败:在验证和保留用户之间,用户名可能会被用户使用。如果是这样,Doctrine在尝试保留最新用户时会抛出UniqueConstraintViolationException。

4 个答案:

答案 0 :(得分:2)

以下是我的回答:

  • 如果发生约束违规,它会优雅地向用户显示错误,就像验证者处理它一样,

  • 它可以防止非“protected”的数据库更新破坏您的控制器逻辑(例如,使用UPDATE语句或表单提交“unprotected”控制器),

  • 这是一个独立于数据库的解决方案。

以下是代码,其中包含对注释的解释:

Func<int, T>

答案 1 :(得分:1)

实现目标的一种方法是使用symfony LockHandler锁定。

这是一个简单的例子,使用你在问题中提到的模式:

<?php

// ...
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Filesystem\LockHandler;
use Symfony\Component\Form\FormError;

public function newAction(Request $request)
{
    $task = new Task();

    $form = $this->createFormBuilder($task)
        ->add('task', TextType::class)
        ->add('dueDate', DateType::class)
        ->add('save', SubmitType::class, array('label' => 'Create Task'))
        ->getForm();

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        // locking here
        $lock = new LockHandler('task_validator.lock');
        $lock->lock();

        // since entity is validated when the form is submitted, you
        // have to call the validator manually
        $validator = $this->get('validator');

        if (empty($validator->validate($task))) {
            $task = $form->getData();
            $em = $this->getDoctrine()->getManager();
            $em->persist($task);
            $em->flush();

            // lock is released by garbage collector
            return $this->redirectToRoute('task_success');
        }

        $form->addError(new FormError('An error occured, please retry'));
        // explicit release here to avoid keeping the Lock too much time.
        $lock->release();

    }

    return $this->render('default/new.html.twig', array(
        'form' => $form->createView(),
    ));
}

注意:如果您在多个主机上运行应用程序,则无法使用此文档:

  

锁定处理程序仅在您只使用一台服务器时才有效。如果您有多个主机,则不得使用此帮助程序。

您还可以覆盖EntityManager以创建像validateAndFlush($entity)这样的新功能来管理LockHandler和验证过程本身。

答案 2 :(得分:0)

您是否可以在数据库级别设置唯一约束。 您还可以查看Doctrine2 documentation如何执行此操作:

/**
 * @Entity
 * @Table(name="user",
 *      uniqueConstraints={@UniqueConstraint(name="username_unique", columns={"username"})},
 * )
 */
class User { 

    //...

    /**
     * @var string
     * @Column(type="string", name="username", nullable=false)
     */
    protected $username;

    //...
}

现在,您对数据库级别有一个独特的限制(因此永远不会在用户表中插入相同的用户名两次)。

执行插入操作时,如果用户名已存在(UniqueConstraintViolationException),则会抛出异常。您可以捕获异常并向客户端返回有效响应,在该客户端上您已经使用了该用户名(在您的数据库中)。

答案 3 :(得分:0)

如果我正确理解了这个问题,你就为自己设定了很高的标准。你的持久层显然不可能看到未来。因此,不可能只使用域实体来支持验证器,以确保插入成功(而不是抛出UniqueConstraintViolationException)。您需要在某处保持其他状态。

如果您想要一些增量改进,您需要一些方法在验证时保留用户名。当然,这很简单 - 您只需在某处创建一个列表即可跟踪&#34;在飞行中&#34;用户名,除了在验证期间检查持久层外,还要检查该列表。

它变得棘手的是设计一种理智的方法来修剪该列表并释放提交用于验证但从未在成功注册中使用的用户名。

这是一个实施细节,您需要考虑用户名预留多长时间。

我头脑中的一个简单实现:使用(username,session_id,reserved_at)维护数据库中的表,并让某些进程定期删除reserved_at&lt;的所有行。 :日期时间

您需要跟踪session_id,因为您需要为特定用户保留用户名。由于用户尚未创建帐户,因此识别帐户的唯一方法是通过其会话标识符。