Rayman
Rayman

Reputation: 123

Mapping multiple entities under a single field

Is there a way to have a single field in an entity tied to multiple different entities?

I have a "Task" entity which can be associated with either the Customer entity or Supplier entity (never both). Right now both fields are separate.

I need to use this in my TaskType form so that users can select which Customer/Supplier to associate the Task with, ideally under a single field as I plan on adding more entities that it can associate with.

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\Customer", inversedBy="tasks")
 */
private $customer;
/**
 * @ORM\ManyToOne(targetEntity="App\Entity\Supplier", inversedBy="tasks")
 */
private $supplier;

public function getCustomer(): ?Customer
{
    return $this->customer;
}
public function setCustomer(?Customer $customer): self
{
    $this->customer = $customer;
    return $this;
}
public function getSupplier(): ?Supplier
...etc

Upvotes: 0

Views: 899

Answers (1)

Lucas Delobelle
Lucas Delobelle

Reputation: 434

Maybe you can try the following :

Ideally, I guess you want to share informations between Customer and Supplier. So we could introduce a new parent class, Person for example (I don't know which kind of responsibility they have, so we'll take the most "generic" class name), and use Doctrine inheritance mapping :

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap({
 *  "customer" = "Customer",
 *  "supplier" = "Supplier"
 * })
 */
abstract class Person
{
  //... Fields, traits, embeddables...

  /**
   * A common attribute between our child classes
   * protected to respect encapsulation
   * 
   * @ORM\Column(type="text")
   */
  protected $name;

  /**
   * Here we define the general link to a task. It will be inherited by child classes
   *
   * @ORM\OneToMany(targetEntity="App\Entity\Task", mappedBy="assignedTo")
   */
  protected $tasks;

  // public getters/setters...
}

I think Class table inheritance strategy would fit your needs here, as you would like to add more entities later. That way, we can respect the Open-closed principle and add more child classes later instead of modifying a logic in only one class.

Also, I made the Person class abstract since we usually want to deal with Customer or Supplier instances. But depending on what you need, maybe you can remove the abstract keyword. In that case, you will have to include Person inside the discriminator map.

Of course, now Customer and Supplier have both to extend Person :

//...
class Customer extends Person
//...

//...
class Supplier extends Person
//...

Don't forget to remove shared fields (like id, for instance) from the child classes, it will be now inherited from Person

So then, in a Task, you are able to define the ManyToOne relation to a Person :

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\Person", inversedBy="tasks")
 */
private $assignedTo;

Finally, for your Task form, let's have a select list with the names of all the persons :

<?php

namespace App\Form;

use App\Entity\Person;
use App\Entity\Task;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TaskType extends AbstractType
{
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder
      // other task fields
      ->add('assignedTo', EntityType::class, [
        'class' => Person::class,
        'choice_label' => 'name',
      ]);
  }

  public function configureOptions(OptionsResolver $resolver)
  {
    $resolver->setDefaults([
      'data_class' => Task::class,
    ]);
  }
}

It will select all the persons, regardless of the type. You can then extend it later with other child classes ! I hope this will help.

Upvotes: 2

Related Questions