phpsymfonydoctrinesymfony4

What's the propper way to insert data one to many relation symfony4?


It's simple im doing a permission table from Companies and Users where i have to store the user id and the company id right now i have this and i get this error

Expected value of type "App\Entity\CompanyUserPermissionMap" for association field "App\Entity\User#$companyUserPermissionMaps", got "App\Entity\Company" instead.

User Entity

/**
 * @ORM\Id()
 * @ORM\GeneratedValue()
 * @ORM\Column(type="integer")
 */
private $userId;

/**
 * @ORM\Column(type="string", length=12)
 */
private $code;

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


/**
 * @var CompanyUserPermissionMap[]
 * @ORM\OneToMany(targetEntity="App\Entity\CompanyUserPermissionMap", mappedBy="user", orphanRemoval=true)
 */
private $companyUserPermissionMaps;


public function __construct()
{
    $this->companyUserPermissionMaps = new ArrayCollection();
}


public function getCompanyUserPermissionMaps(): Collection
{
    return $this->companyUserPermissionMaps;
}

public function addCompanyUserPermissionMaps(CompanyUserPermissionMaps $permission): self
{
    if (!$this->companyUserPermissionMaps->contains($permission)) {
        $this->companyUserPermissionMaps[] = $permission;
        $permission->setUser($this);
    }

    return $this;
}

Company Entity

#########################
##      PROPERTIES     ##
#########################

/**
 * @ORM\Id()
 * @ORM\GeneratedValue()
 * @ORM\Column(type="integer")
 */
private $companyId;

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



/**
 * @var CompanyUserPermissionMap[]
 * @ORM\OneToMany(targetEntity="App\Entity\CompanyUserPermissionMap", mappedBy="company", orphanRemoval=true)
 */
private $companyUserPermissionMaps;


public function __construct()
{
    $this->companyUserPermissionMaps = new ArrayCollection();

}



/**
 * @return Collection|CompanyUserPermissionMaps[]
 */
public function getCompanyUserPermissionMaps(): Collection
{
    return $this->companyUserPermissionMaps;
}

public function addCompanyUserPermissionMaps(AccountingBankPermission $permission): self
{
    if (!$this->companyUserPermissionMaps->contains($permission)) {
        $this->companyUserPermissionMaps[] = $permission;
        $permission->setAccount($this);
    }

    return $this;
}

public function removeCompanyUserPermissionMaps(AccountingBankPermission $permission): self
{
    if ($this->companyUserPermissionMaps->contains($permission)) {
        $this->companyUserPermissionMaps->removeElement($permission);
        // set the owning side to null (unless already changed)
        if ($permission->getAccount() === $this) {
            $permission->setAccount(null);
        }
    }

    return $this;
}

relation table

Column(type="integer")

private $companyUserPermissionId;


/**
 * @var User
 * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="companyUserPermissionMaps")
 * @ORM\JoinColumn(referencedColumnName="user_id", nullable=false)
 */
private $user;

/**
 * @var Company
 * @ORM\ManyToOne(targetEntity="App\Entity\Company", inversedBy="companyUserPermissionMaps")
 * @ORM\JoinColumn(referencedColumnName="company_id", nullable=true)
 */
private $company;


/**
 * @return int|null
 */
public function getCompanyUserPermissionId(): ?int
{
    return $this->companyUserPermisionId;
}



/**
 * @return User
 */
public function getUser(): ?User
{
    return $this->user;
}

/**
 * @param User $user
 * @return AccountingBankPermission
 */
public function setUser(?User $user): self
{
    $this->user = $user;
    return $this;
}

/**
 * @return Company
 */
public function getCompany(): ?Company
{
    return $this->company;
}

/**
 * @param array $company
 * @return CompanyUserPermissionMap
 */
public function setCompany(?array $company): self
{
    $this->company = $company;
    return $this;
}

Form type

 $builder
            ->add('roles' ,ChoiceType::class ,[
                'required'  => true,
                'choices' => $this->roles,
                'multiple' => true,
                'expanded' => true,
                'label_attr' => [
                    'class' => 'custom-control-label',
                ],
                'choice_attr' => function($val, $key, $index) {
                    return ['class' => 'custom-control-input'];
                },
                'attr'=>['class' =>'custom-checkbox custom-control']
            ])
            ->add('email', EmailType::class, [
                'label' => "E-Mail"
            ])
            ->add('firstName', TextType::class, [
                'label' => "First Name"
            ])
            ->add('lastName', TextType::class, [
                'label' => "Last Name"
            ])
            ->add('companyUserPermissionMaps' ,EntityType::class ,[
                'required' => true,
                'class'    => Company::class,
                'label'    => 'Compañia',
                'multiple' => true,
                'expanded' => false,
                'choice_label'  => 'legalName',
                'mapped'=>false
            ])
            ->add('save', SubmitType::class, [
                'label' => "Save"
            ])

and my controller function looks like this

 $user = new User();

    $originalRoles = $this->getParameter('security.role_hierarchy.roles');

    $options=['roles' => $originalRoles ];

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

    $form->handleRequest($request);

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

        $tempPassword = "some pass";

        $user->setPassword($encoder->encodePassword(
            $user,
            $tempPassword
        ));

        $companies=[];
        $companiesForm=$form->get('companyUserPermissionMaps')->getData();
        foreach ($companiesForm as $value) {
            $companies[] = $value->getCompanyId();
        }

        // Save object to database
        $entityManager = $this->getDoctrine()->getManager();

        /** @var CompanyRepository $companyRepository */
        $companyRepository = $this->getDoctrine()->getRepository(Company::class);
        $companiesArr =$companyRepository->findCompanyByArray($companies);
        $companyUserPermissionMap = new CompanyUserPermissionMap();
        $companyUserPermissionMap->setUser($user);
        $companyUserPermissionMap->setCompany($companiesArr);

        $entityManager->persist($user);
        $entityManager->persist($companyUserPermissionMap);

Solution

  • update

    Okay, so I neglected to mention the problem you're actually facing. The form component, smart as it is, will try to call getters and setters when loading/modifying the form data. Since your form contains ->add('companyUserPermissionMaps',..., the form component will call getCompanyUserPermissionMaps on your entity (which will return a currently probably empty collection) and will try to write back to the entity via either setCompanyUserPermissionMaps or add/remove instead of set, if they are present.

    Since your field actually behaves as if it holds a collection of Company objects, the setting of those on your User object will obviously fail with the error message you encountered:

    Expected value of type "App\Entity\CompanyUserPermissionMap" for association field "App\Entity\User#$companyUserPermissionMaps", got "App\Entity\Company" instead.

    which absolutely makes sense. So, this problem can be fixed in different ways. The one way which you apparently already tried was setting mapped to false, but instead of false you used 'false' (notice the quotes), which evaluates to true ... ironically. So to use your approach, you would have to remove the quotes. However, I propose a different approach, which I would much prefer!

    end update

    My general advice would be to hide stuff you don't want to show. So, in your form builder instead of

    ->add('companyUserPermissionMaps', EntityType::class, [
        'required' => true,
        'class'    => Company::class,
        'label'    => 'Compañia',
        'multiple' => true,
        'expanded' => false,
        'choice_label'  => 'legalName',
        'mapped'=>'false'
    ])
    

    which obviously already is not a field that handles CompanyUserPermissionMaps but companies instead - so apparently you suspected this is semantically something different, you should go back to the User entity and give it a function getCompanies instead

    public function getCompanies() {
        return array_map(function ($map) {
            return $map->getCompany();
        }, $this->getCompanyUserPermissionsMaps());
    }
    public function addCompany(Company $company) {
        foreach($this->companyUserPermissionMaps->toArray() as $map) {
            if($map->getCompany() === $company) {
                return;
            }
        }
        $new = new CompanyUserPermissionMap();
        $new->setCompany($company);
        $new->setUser($this);
        $this->companyUserPermissionMaps->add($new);
    }
    public function removeCompany(Company $company) {
        foreach($this->companyUserPermissionMaps as $map) {
            if($map->getCompany() == $company) {
                $this->companyUserPermissionMaps->removeElement($map);
            }
        }
    }
    

    you would then call the field companies (so ->add('companies', ...)) and act on Company entities instead of those pesky maps. (I also don't really like exposing ArrayCollection and other internals to the outside. But hey, that's your decision.)

    however, if your Maps are going to hold more values at some point, you actually have to work with the maps in your form, and not just with Company entities.