5

我有一个相当简单的UniqueEntity验证实体:

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping\ManyToOne;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 * @UniqueEntity("email", message="Email already in use")
 * 
 *
 */
class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=180, unique=true)
     */
    private $email;

表单(故意删除所有其他表单域)

namespace App\Form;

use App\Entity\Company;
use App\Entity\User;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;


class UserType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add( 'email', EmailType::class,[
                'label' => 'Email'
            ] );


        $builder->add( 'save', SubmitType::class, [
            'attr' => [
                'class' => 'btn btn-primary',
                'id' => 'btn-user-form'
            ],
            'label' => 'Save'
        ] );

    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults( [
            'data_class' => 'App\Entity\User'
        ]);

    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'user';
    }
}

和控制器:

public function edit(User $user, RequestStack $request)
{
    $em = $this->getDoctrine()->getManager();
    $form = $this->createForm( UserType::class, $user);

    $form->handleRequest($request->getCurrentRequest());

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

        $em->persist( $user);
        $em->flush();

        $this->addFlash('success', 'User updated.');
        return $this->redirectToRoute('user_index');
    }

    return $this->render('user/update.html.twig', [
        'form' => $form->createView(),
        'deleteForm' => $this->createDeleteForm($user)->createView()
    ]);

}

当我保存现有记录时,我收到一条错误消息“电子邮件已在使用中”(由我指定)

Symfony Profiler 中显示了以下查询:

SELECT t0.id AS id_1, t0.email AS email_2, t0.firstname AS firstname_3, t0.lastname AS lastname_4, t0.roles AS roles_5, t0.last_ip_address AS last_ip_address_6, t0.last_active AS last_active_7, t0.password AS password_8, t0.reset_token AS reset_token_9, t0.company_id AS company_id_10 FROM user t0 WHERE t0.id = ?
Parameters:
[▼
  1
]
View formatted query    View runnable query    Explain query
2   0.44 ms 
SELECT t0.id AS id_1, t0.email AS email_2, t0.firstname AS firstname_3, t0.lastname AS lastname_4, t0.roles AS roles_5, t0.last_ip_address AS last_ip_address_6, t0.last_active AS last_active_7, t0.password AS password_8, t0.reset_token AS reset_token_9, t0.company_id AS company_id_10 FROM user t0 WHERE t0.id = ?
Parameters:
[▼
  "100"
]
View formatted query    View runnable query    Explain query
3   0.43 ms 
SELECT t0.id AS id_1, t0.name AS name_2 FROM company t0 WHERE t0.id = ?
Parameters:
[▼
  17
]
View formatted query    View runnable query    Explain query
4   0.56 ms 
SELECT c0_.id AS id_0, c0_.name AS name_1 FROM company c0_ WHERE c0_.id IN (?) ORDER BY c0_.name ASC
Parameters:
[▼
  [▼
    "17"
  ]
]
View formatted query    View runnable query    Explain query
5   0.76 ms 
SELECT u0_.id AS id_0, u0_.email AS email_1, u0_.firstname AS firstname_2, u0_.lastname AS lastname_3, u0_.roles AS roles_4, u0_.last_ip_address AS last_ip_address_5, u0_.last_active AS last_active_6, u0_.password AS password_7, u0_.reset_token AS reset_token_8, u0_.company_id AS company_id_9 FROM user u0_ LEFT JOIN company c1_ ON u0_.company_id = c1_.id
Parameters:
[]
View formatted query    View runnable query    Explain query
6   0.31 ms 
SELECT c0_.id AS id_0, c0_.name AS name_1 FROM company c0_ ORDER BY c0_.name ASC
Parameters:
[]
View formatted query    View runnable query    Explain query
7   0.14 ms 
"START TRANSACTION"
Parameters:
[]
View formatted query    View runnable query    Explain query
8   0.49 ms 
UPDATE user SET last_active = ? WHERE id = ?
Parameters:
[▼
  "2020-01-18 07:56:51"
  1
]
View formatted query    View runnable query    Explain query
9   0.77 ms 
"COMMIT"
Parameters:
[]
View formatted query    View runnable query    Explain query
10  0.15 ms 
"START TRANSACTION"
Parameters:
[]
View formatted query    View runnable query    Explain query
11  0.23 ms 
INSERT INTO usage_log (logged, url, user_id, file_type_id) VALUES (?, ?, ?, ?)
Parameters:
[▼
  1 => "2020-01-18 07:56:51"
  2 => "/user/100/edit"
  3 => 1
  4 => null
]
View formatted query    View runnable query    Explain query
12  0.38 ms 
"COMMIT"
Parameters:
[]

有一个听众导致

update last_active... and
insert into usage_log

查询。可以肯定的是,这个问题不仅限于用户实体,当我创建记录时,我在另一个实体公司也有同样的问题。

实体:

<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\ManyToMany;
use Doctrine\ORM\Mapping\JoinColumn;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;


/**
 * @ORM\Entity(repositoryClass="App\Repository\CompanyRepository")
 * @UniqueEntity("name", message="Company already saved")
 */
class Company
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=100, unique=true)
     */
    private $name;

    /**
     * @OneToMany(targetEntity="User", mappedBy="Company")
     */
    private $Users;


    /**
     * @ManyToMany(targetEntity="ModelFile", inversedBy="Companies")
     * @JoinColumn(nullable=true)
     */
    private $ModelFile;

控制器:

public function create(RequestStack $request)
{
    $company = new Company();

    $em = $this->getDoctrine()->getManager();
    $form = $this->createForm(CompanyType::class, $company);

    $form->handleRequest($request->getCurrentRequest());

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

        $em->persist($company);
        $em->flush();

        $this->addFlash('success', 'Company created.');
        return $this->redirectToRoute('company_index');
    }

    return $this->render('company/create.html.twig', [
        'form' => $form->createView()
    ]);
}

我创建公司时的查询日志是

    1   0.84 ms 
SELECT t0.id AS id_1, t0.email AS email_2, t0.firstname AS firstname_3, t0.lastname AS lastname_4, t0.roles AS roles_5, t0.last_ip_address AS last_ip_address_6, t0.last_active AS last_active_7, t0.password AS password_8, t0.reset_token AS reset_token_9, t0.company_id AS company_id_10 FROM user t0 WHERE t0.id = ?
Parameters:
[▼
  1
]
View formatted query    View runnable query    Explain query
2   1.22 ms 
SELECT c0_.id AS id_0, c0_.name AS name_1 FROM company c0_ ORDER BY c0_.name ASC
Parameters:
[]
View formatted query    View runnable query    Explain query
3   0.47 ms 
SELECT m0_.id AS id_0, m0_.name AS name_1, m0_.display_name AS display_name_2, m0_.file AS file_3 FROM model_file m0_
Parameters:
[]
View formatted query    View runnable query    Explain query
4   0.13 ms 
"START TRANSACTION"
Parameters:
[]
View formatted query    View runnable query    Explain query
5   0.51 ms 
UPDATE user SET last_active = ? WHERE id = ?
Parameters:
[▼
  "2020-01-18 07:44:29"
  1
]
View formatted query    View runnable query    Explain query
6   0.55 ms 
"COMMIT"
Parameters:
[]
View formatted query    View runnable query    Explain query
7   0.14 ms 
"START TRANSACTION"
Parameters:
[]
View formatted query    View runnable query    Explain query
8   0.34 ms 
INSERT INTO usage_log (logged, url, user_id, file_type_id) VALUES (?, ?, ?, ?)
Parameters:
[▼
  1 => "2020-01-18 07:44:29"
  2 => "/company/create"
  3 => 1
  4 => null
]
View formatted query    View runnable query    Explain query
9   0.30 ms 
"COMMIT"
Parameters:
[]

我还在这里提供了监听器代码:

<?php
namespace App\Listener;

use App\Entity\UsageLog;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class LastActivityListener implements EventSubscriberInterface
{
    private $tokenStorage;
    private $em;
    private $router;

    public function __construct(TokenStorageInterface $tokenStorage, EntityManagerInterface $em, RouterInterface $router)
    {
        $this->tokenStorage = $tokenStorage;
        $this->em = $em;
        $this->router = $router;
    }

    public function onResponse(FilterResponseEvent $event)
    {
        $token = $this->tokenStorage->getToken();

        if ($token && $token->isAuthenticated() && is_a($token->getUser(), User::class) ) {
            $token->getUser()->setLastActive(new \DateTime());
            $this->em->persist($token->getUser());
            $this->em->flush($token->getUser());

            if ($event->getRequest()->get('_route')) {
                $usageLog = new UsageLog();
                $usageLog
                    ->setUrl($this->router->generate($event->getRequest()->get('_route'), $event->getRequest()->attributes->get('_route_params')))
                    ->setUser($token->getUser())
                    ->setLogged(new \DateTime());
                $this->em->persist($usageLog);
                $this->em->flush($usageLog);
            }

        }
    }

    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::RESPONSE => 'onResponse',
        ];
    }
}

我已经注释掉了 onResponse 中的所有内容,问题仍然存在。

我觉得奇怪的是我保存公司时验证失败的描述。它列出了所有公司作为失败的原因:

公司创建验证失败

它肯定已经存在,但在同一个记录中。我应该使用不同的实体进行插入和更新还是UniqueEntity设计如何使用?

Symfony 4.2

4

3 回答 3

0

我知道这已经晚了,但我也遇到了这个问题。解决方法是将 unique=true 添加到 Column 定义中。结果唯一性查询只发生在插入时。

于 2020-12-30T01:42:51.433 回答
0

我们遇到了同样的问题,因此将我们的自定义验证器作为解决方案,因为我们也在为此努力使用几个默认验证选项。

如果 ID 已经存在,我们会检查验证器,如果 ID 不存在,那么我们会进行唯一的电子邮件验证。如果是并且电子邮件地址不等于我们当前的电子邮件地址,则验证通过。

如果有人有更好的解决方案,请告诉我,但这就是我们设法解决此问题的方法。

这是验证的代码(从上面的实体中删除它并在 User.php 的属性上使用它)

use App\Validator\Constraint\User\UserEmailConstraint;

    /**
     * @var string
     *
     * @Assert\NotNull(message="You need to fill in an email address.")
     *
     * @UserEmailConstraint()
     *
     * @ORM\Column(name="email", type="string", length=255, nullable=false)
     */
    private $email;

验证者

<?php

namespace App\Validator\Constraint\User;

use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
 * Class UserEmailValidator.
 */
class UserEmailValidator extends ConstraintValidator
{
    /**
     * @var UserRepository
     */
    private $userRepository;

    /**
     * UserEmailValidator constructor.
     *
     * @param UserRepository $userRepository
     */
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    /**
     * Check if email is valid, then check if email already exists.
     *
     * @param mixed      $email
     * @param Constraint $constraint
     */
    public function validate($email, Constraint $constraint)
    {
        // This can't be empty
        if (!is_string($email) || strlen(trim($email)) < 1) {
            $this->context->buildViolation($constraint->emailNotProvided)
                ->addViolation();
        // Do the default PHP email validation (correct emailaddress)
        } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->context->buildViolation($constraint->invalidEmailMessage)
                ->setParameter('{{ value }}', $email)
                ->addViolation();
        } else {
            /** @var User $object */
            $object = $this->context->getObject();

            /** @var User $user */
            $user = $this->userRepository->findOneBy([
                'email' => $email,
            ]);

            // Check whether the name is already in use, but it can be that the name is used by same object
            if (!$user || ($user->getId() === $object->getId())) {
                return;
            }

            /* @var $constraint UserEmailConstraint */
            $this->context->buildViolation($constraint->emailInUseMessage)
                ->setParameter('{{ value }}', $email)
                ->addViolation();
        }
    }
}

约束

<?php

namespace App\Validator\Constraint\User;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 * @Target({"PROPERTY", "ANNOTATION"})
 */
class UserEmailConstraint extends Constraint
{
    /**
     * @var string
     */
    public $emailInUseMessage = 'The email: {{ value }}, has already been used.';

    public $invalidEmailMessage = 'The email: {{ value }}, is invalid.';

    public $emailNotProvided = 'You need to fill in an email address.';

    /**
     * @return string
     */
    public function validatedBy()
    {
        return UserEmailValidator::class;
    }
}

于 2020-01-22T12:30:43.683 回答
0

将实体加载到表单中时应该非常小心:

$form = $this->createForm( UserType::class, $user);

$form->handleRequest($request->getCurrentRequest());

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

即使您的表单无效,handleRequest也已更改您的实体,并且它将->flush()在您的代码中首先更新,除非您@ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")在实体的顶部使用。


除此之外,您的代码完全有效,恐怕您需要为我们提供更多上下文。

如果您有疑问,可以尝试运行以下示例:

src/Entity/Test.php

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * @ORM\Entity(repositoryClass="App\Repository\TestRepository")
 * @ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")
 * @UniqueEntity("test")
 */
class Test
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255, unique=true)
     */
    private $test;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTest(): ?string
    {
        return $this->test;
    }

    public function setTest(string $test): self
    {
        $this->test = $test;

        return $this;
    }
}

src/Controller/TestController.php

<?php

namespace App\Controller;

use App\Entity\Test;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class TestController extends Controller
{
    /**
     * @Route(path="/test/{id}", name="test")
     *
     * @param string $code
     * @param int    $action
     *
     * @return Response
     */
    public function testAction(Test $test, Request $request)
    {
        $form = $this->createFormBuilder($test, ['data_class' => Test::class])
                     ->add('test', TextType::class)
                     ->add('save', SubmitType::class)
                     ->getForm();

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($test);
            $em->flush();

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

        return $this->render('test.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

templates/test.html.twig

{{ form(form) }}

这可能是一个愚蠢的评论,但是您是否检查过唯一索引是否已正确插入到您的数据库模式中?如果不是,您可能有另一个用户拥有相同的电子邮件,解释验证失败的原因。

于 2020-01-17T21:44:59.410 回答