简体   繁体   中英

Symfony2 Doctrine2 UniqueEntity on ManyToOne field is ignored

I have a OneToMany relationship between Project and Application , and I want to be sure that 2 Application s cannot have the same name inside a Project .

I tried to configure my entity, form type and controller like it should be, but I am getting an Integrity contraint violation for duplicated entry, so I think the validation process is ignored.

Can someone tell me what am I missing ?

My Application entity like this :

namespace App\MainBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use JsonSerializable;

/**
 * @ORM\Entity
 * @ORM\Table(name="application", uniqueConstraints={@ORM\UniqueConstraint(name="IDX_Unique", columns={"name", "project_id"})})
 * @UniqueEntity(
 *      fields={"name", "project"},
 *      message="Name already used in this project.",
 *      groups="application"
 * )
 */
class Application implements JsonSerializable {

    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     * @Assert\NotBlank(
     *      message = "Name cannot be empty."
     * )
     * @Assert\Length(
     *      min = "3",
     *      max = "50",
     *      minMessage = "Name is too short. It should have {{ limit }} characters or more.",
     *      maxMessage = "Name is too long. It should have {{ limit }} characters or less."
     * )
     */
    protected $name;

    // other properties ...

    /**
     * @ORM\ManyToOne(targetEntity="Project", inversedBy="applications")
     * @ORM\JoinColumn(name="project_id", referencedColumnName="id")
     */
    protected $project;

    // constructor, methods, getters, setters
}

My ApplicationType class looks like this :

namespace App\MainBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class ApplicationType extends AbstractType {

    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder->add('name', 'text', array(
            'icon' => 'pencil'
        ));
        $builder->add('description', 'textarea', array(
            'required' => false,
            'icon' => 'info'
        ));
        $builder->add('url', 'url', array(
            'required' => false,
            'icon' => 'link'
        ));
    }

    public function getName() {
        return 'application';
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults(array(
            'data_class' => 'App\MainBundle\Entity\Application',
            'validation_group' => array('application'),
            'cascade_validation' => true
        ));
    }
}

And in my Controller the action looks like this:

/**
 * @Route("/project/{id}/application/add",
 *      name="app_add_application_ajax",
 *      requirements={"_method" = "post"},
 *      options={"expose" = true }
 * )
 * @Secure(roles="ROLE_SUPER_ADMIN")
 * @ParamConverter("project", class="AppMainBundle:Project")
 */
public function addApplicationAction(Project $project, Request $request) {
    $ajaxResponse = array();
    $em = $this->getDoctrine()->getManager();
    if ($request->getMethod() == 'POST' && $request->isXmlHttpRequest()) {
        $formApp = new Application();
        $formApp->setProject($project);
        $form = $this->createForm(new ApplicationType(), $formApp);
        $form->handleRequest($request);
        if ($form->isValid()) {
            $application = $form->getData();
            $em->persist($application);
            $em->flush();
            // build ajax response ...
        } else {
            $ajaxResponse['error'] = $this->getErrorsAsString();
        }
    }
    $response = new Response(json_encode($ajaxResponse));
    $response->headers->set('Content-Type', 'application/json');
    return $response;
}

Your issue is that you configure a validation_group option in your form type, while the option used by Symfony is validation_groups . You don't get an error about an unknown option because you are setting this in the default options of your form type, and so you are marking the option as defined (but it is a separate one).
So the validator runs with the default group, which will validate different constraints (the constraints on the length of the name property are in the default group).

Note that you also have a second issue, which would appear once you run the constraint.
Your validation constraint does not match the DB constraints you have. You are asking the validator to have a unique name and a unique project, not a unique tuple (name, project). So you would reject too much things (the name will be validated as unique globally, not per project). This is because you use 2 separate UniqueEntity constraints instead of a constraint asking for a tuple of multiple fields to be unique.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM