Symfony2数据转换器,验证器和错误消息

时间:2012-02-22 16:18:55

标签: symfony validation symfony-forms

我问this question并发现我们无法获取DataTransformer抛出的错误消息(根据唯一回答的用户,也许是可能的,我不知道)。

无论如何,现在我知道了,我遇到了验证问题。假设我的模型是这个:我有包含几个参与者(用户)的线程。

<?php

class Thread
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\ManyToMany(targetEntity="My\UserBundle\Entity\User")
     * @ORM\JoinTable(name="messaging_thread_user")
     */
    private $participants;

    // other fields, getters, setters, etc
}

对于线程创建,我希望用户在textarea中指定参与者用户名,以“\ n”分隔。 我希望如果指定的一个或多个用户名不存在,则会显示一条消息,其中包含不存在的用户名。 例如,“用户titi,tata和toto不存在”。

为此,我创建了一个DataTransformer,它将textarea中的原始文本转换为包含用户实例的ArrayCollection。由于我无法获得此DataTransformer提供的错误消息(这太可惜了!这真的不可能吗?),我不会检查DataTransformer中的每个用户名是否存在,而是在Validator中。

这是DataTransformer,它将\ n分离的用户列表转换为ArrayCollection(以便DataBinding正常):

<?php

public function reverseTransform($val)
{
    if (empty($val)) {
        return null;
    }

    $return = new ArrayCollection();

    // Extract usernames in an array from the raw text
    $val = str_replace("\r\n", "\n", trim($val));
    $usernames = explode("\n", $val);
    array_map('trim', $usernames);

    foreach ($usernames as $username) {
        $user = new User();
        $user->setUsername($username);
        if (!$return->contains($user)) {
            $return->add($user);
        }
    }

    return $return;
}

这是我的验证员:

<?php

public function isValid($value, Constraint $constraint)
{
    $repo = $this->em->getRepository('MyUserBundle:User');
    $notValidUsernames = array();

    foreach ($value as $user) {
        $username = $user->getUsername();
        if (!($user = $repo->findOneByUsername($username))) {
            $notValidUsernames[] = $username;
        }
    }

    if (count($notValidUsernames) == 0) {
        return true;
    }

    // At least one username is not ok here

    // Create the list of usernames separated by commas
    $list = '';
    $i = 1;

    foreach ($notValidUsernames as $username) {
        if ($i < count($notValidUsernames)) {
            $list .= $username;
            if ($i < count($notValidUsernames) - 1) {
                $list .= ', ';
            }
        }
        $i++;
    }

    $this->setMessage(
            $this->translator->transChoice(
                'form.error.participant_not_found',
                count($notValidUsernames),
                array(
                    '%usernames%' => $list,
                    '%last_username%' => end($notValidUsernames)
                )
            )
    );

    return false;
}

这个当前的实现看起来很难看。我可以很好地看到错误消息,但DataTransformer返回的ArrayCollection中的用户与Doctrine不同步。

我有两个问题:

  • 我的验证器有没有办法修改参数中给出的值?这样我可以将DataTransformer返回的ArrayCollection中的简单User实例替换为从数据库中检索的实例吗?
  • 有没有一种简单而优雅的方式来做我正在做的事情?

我想最简单的方法是能够获取DataTransformer给出的错误消息。在cookbook中,他们抛出了这个异常:throw new TransformationFailedException(sprintf('An issue with number %s does not exist!', $val));,如果我可以在错误消息中放入不存在的用户名列表,那就太酷了。

谢谢!

1 个答案:

答案 0 :(得分:2)

我是那个回答你之前的帖子的人,所以也许其他人会跳进这里。

您的代码可以大大简化。您只处理用户名。无需使用对象或数组集合。

public function reverseTransform($val)
{
    if (empty($val)) { return null; }


    // Extract usernames in an array from the raw text
    // $val = str_replace("\r\n", "\n", trim($val));
    $usernames = explode("\n", $val);
    array_map('trim', $usernames);

    // No real need to check for dups here
    return $usernames;
}

验证员:

public function isValid($userNames, Constraint $constraint)
{
    $repo = $this->em->getRepository('SkepinUserBundle:User');
    $notValidUsernames = array();

    foreach ($userNames as $userName) 
    {
      if (!($user = $repo->findOneByUsername($username))) 
        {
            $notValidUsernames[$userName] = $userName; // Takes care of dups
        }
    }

    if (count($notValidUsernames) == 0) {
        return true;
    }

    // At least one username is not ok here
    $invalidNames = implode(' ,',$notValidUsernames);


$this->setMessage(
        $this->translator->transChoice(
            'form.error.participant_not_found',
            count($notValidUsernames),
            array(
                '%usernames%' => $invalidNames,
                '%last_username%' => end($notValidUsernames)
            )
        )
);

    return false;
}

=============================================== ==========================

所以此时

  1. 我们使用变换器从文本区域复制数据,并在form-&gt; bind()期间生成一组用户名。

  2. 然后我们使用验证器确认每个用户名实际存在于数据库中。如果有任何不这样做,那么我们会生成错误消息,而form-&gt; isValid()将失败。

  3. 所以现在我们回到控制器中,我们知道我们有一个有效用户名列表(可能是逗号分隔或可能只是一个数组)。现在我们要将这些添加到我们的线程对象中。

  4. 一种方法是创建线程管理器服务并向其添加此功能。所以在控制器中我们可能有:

    $threadManager = $this->get('thread.manager');
    $threadManager->addUsersToThread($thread,$users);
    

    对于线程管理器,我们将注入我们的实体管理器。在add users方法中,我们将获得对每个用户的引用,验证该线程还没有到该用户的链接,调用$ thread-&gt; addUser()然后刷新。

    我们将这种功能包装到服务类中的事实将使事情更容易测试,因为我们也可以创建一个命令对象并从命令行运行它。它还为我们提供了添加其他线程相关功能的好地方。我们甚至可以考虑将此管理器注入用户名验证器并将一些isValid代码移动到管理器。