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

标签 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
}

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

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

这是将\n 分隔的用户列表转换为 ArrayCollection 的 DataTransformer(以便 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 给出的错误消息。在食谱中,他们抛出这个异常:抛出新的TransformationFailedException(sprintf('An issues with number %s does not exit!', $val));,如果我可以把非-的列表错误消息中存在现有的用户名,那就太酷了。

谢谢!

最佳答案

我是回答你上一个帖子的人,所以也许其他人会跳到这里。

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

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->bind() 期间生成用户名数组。

  2. 然后我们使用验证器来确认每个用户名确实存在于数据库中。如果有任何不符合的情况,我们会生成一条错误消息,并且 form->isValid() 将失败。

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

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

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

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

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

关于Symfony2 数据转换器、验证器和错误消息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9398660/

相关文章:

php - Symfony 2.6 覆盖 PHPUnit_Framework_Error

many-to-many - 在中间人对象中检索关系的另一面

.net - 什么是 IDataErrorInfo 以及它如何与 WPF 一起使用?

php - 将约束传递给 symfony 集合类型

php - Symfony 形式的单选选项之间的换行符 (<br>)

php - 为什么我的 Symfony 使用的 PHP 扩展在 ubuntu 更新后无效?

mongodb - 不存在的服务 "fos_user.doctrine_registry"

验证失败 wpf mvvm

php - Laravel 只验证发布的项目并忽略验证数组的其余部分

html - Symfony2 自定义表单字段类型 HTML5 颜色