Az.Youness
Az.Youness

Reputation: 2652

Symfony - Update a unique OneToMany relation property

A Company can have multiple emails and all emails have to be unique.
This my Entites for Company and CompanyEmail

CompanyEmail Entity:

/**
 * @ORM\Entity(repositoryClass="App\Repository\CompanyEmailRepository")
 * @UniqueEntity("name")
 */
class CompanyEmail
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

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

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Company", inversedBy="emails")
     * @ORM\JoinColumn(nullable=false)
     */
    private $company;

    // ...
}

Company Entity:

/**
 * @ORM\Entity(repositoryClass="App\Repository\CompanyRepository")
 */
class Company
{
    // ...

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\CompanyEmail", mappedBy="company", orphanRemoval=true, cascade={"persist"})
     * @Assert\Valid
     */
    private $emails;

    // ...
}

and I'm using an custom EmailsInputType that use this DataTransformer

class EmailArrayToStringTransformer implements DataTransformerInterface
{
    public function transform($emails): string
    {
        return implode(', ', $emails);
    }


    public function reverseTransform($string): array
    {
        if ($string === '' || $string === null) {
            return [];
        }

        $inputEmails = array_filter(array_unique(array_map('trim', explode(',', $string))));

        $cEmails = [];
        foreach($inputEmails as $email){
            $cEmail = new CompanyEmail();
            $cEmail->setName($email);
            $cEmails[] = $cEmail;
        }

        return $cEmails;
    }
}

and in the Controller a use this edit method

/**
     * @Route("/edit/{id}", name="admin_company_edit", requirements={"id": "\d+"}, methods={"GET", "POST"})
     */
    public function edit(Request $request, $id): Response
    {
        $entityManager = $this->getDoctrine()->getManager();
        $company = $entityManager->getRepository(Company::class)->find($id);

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

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager->flush();
        }
    }

There is two problems with this code

1 - In the edit form when i try to keep an already saved email Symfony generate a validation error that tells that this email is already exits.

2 - When I remove the validation restriction from the code, Symfony thrown the database error "*Integrity constraint violation: 1062 Duplicate entry ... *"

What i should do to make my code work as expected !

Upvotes: 0

Views: 537

Answers (1)

DonCallisto
DonCallisto

Reputation: 29922

The problem is right here

public function reverseTransform($string): array
{
  [...]

  foreach($inputEmails as $email){
    $cEmail = new CompanyEmail();
    [...]
  }

  [...]
}

You need to retrieve the email instead of creating new one. So basically, inject a CompanyEmailRepository, try to find if email already exists (findOneBy(['name'])), if it does not exists, create a new one but if exists, use what you've retrieved.

Just few notes

  • Pay attention to email owner (so the retrieve should be do per user I guess as no one can share the same mail UNLESS you can specify some aliases or shared address)
  • Maybe you don't need an extra entity like CompanyEmail as you can use a json field where you can store them in a comma separated fashion (unless you need some extra parameters or unless you need to perform some indexing/querying operation on the emails)

Upvotes: 1

Related Questions