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

发布于 2025-01-08 02:01:25 字数 3300 浏览 1 评论 0原文

我问这个问题并发现我们无法收到错误消息由 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));,如果我可以列出非-错误消息中现有的用户名,那就太酷了。

谢谢!

I asked this question and found out that we can't get the error message thrown by a DataTransformer (according to the only user who answered, maybe it's possible, I don't know).

Anyway, now that I know that, I am stucked with a problem of validation. Suppose my model is this one: I have threads that contains several participants (users).

<?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
}

For thread creation, I want the user to specify the participants usernames in a textarea, separated by "\n".
And I want that if one or more of the usernames specified don't exist, a message is displayed with the usernames that don't exist.
For example, "Users titi, tata and toto don't exist".

For that I created a DataTransformer that transforms the raw text in the textarea into an ArrayCollection containing instances of users. Since I can't get the error message provided by this DataTransformer (such a shame! Is it really impossible?), I don't check the existence of each usernames in the DataTransformer but in the Validator.

Here is the DataTransformer that converts \n-separated user list into an ArrayCollection (so that the DataBinding is ok):

<?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;
}

And here is my validator:

<?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;
}

This current implementation looks ugly. I can see the error message well, but the users in the ArrayCollection returned by the DataTransformer are not synchronized with Doctrine.

I got two questions:

  • Is there any way that my validator could modify the value given in parameter? So that I can replace the simple User instances in the ArrayCollection returned by the DataTransformer into instances retrieved from the database?
  • Is there a simple and elegant way to do what I'm doing?

I guess the most simple way to do this is to be able to get the error message given by the DataTransformer. In the cookbook, they throw this exception: throw new TransformationFailedException(sprintf('An issue with number %s does not exist!', $val));, if I could put the list of non-existing usernames in the error message, it would be cool.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

意犹 2025-01-15 02:01:25

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

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

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. 我们已经使用transformer从文本区域复制了数据并生成了一个数组form->bind() 期间的用户名。

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

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

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

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

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

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

I am the one that answered your previous thread so maybe someone else will jump in here.

Your code can be simplified considerably. You are only dealing with user names. No need for use objects or array collections.

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;
}

The validator:

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;
}

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

So at this point

  1. We have used transformer to copy the data from the text area and generated an array of user names during form->bind().

  2. We then used a validator to confirm that each user name actually exists in the database. If there are any that don't then we generate an error message and form->isValid() will fail.

  3. So now we are back in the controller, we know we have a list of valid user names (possibly comma delimited or possibly just an array). Now we want to add these to our thread object.

One way would to create a thread manager service and add this functionality to it. So in the controller we might have:

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

For the thread manager we would inject our entity manager. In the add users method we would get a reference to each of the users, verify that the thread does not already have a link to this user, call $thread->addUser() and then flush.

The fact that we have wrapped up this sort of functionality into a service class will make things easier to test as we can also make a command object and run this from the command line. it also gives us a nice spot to add additional thread related functionality. We might even consider injecting this manager into the user name validator and moving some of the isValid code to the manager.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文