Once upon a time in the dark abyss somewere deep in the lands of Symfony there was a frustrated programmer. He tried and tried but somehow the evil doctrine striked again and again. Also the villains Joins
, Associative tables
and One-to-Many/Many-to-One
were giving him a hard time. Then, on a late afternoon StackOverflow
and it's community came to the rescue.
Enough fairytales. My problem is that I have three tables that should all refer to the same table to get attachments.
- Mail
- Order
- Ticket
Each of these three entities can have attachments. So I made an Attachment entity.
Now, my database contains the following
Table: mails
- id
- from
- to
- message
Table attachments
- id
- name
- path
Table: orders
- id
- ...
Table: tickets
- id
- name
- description
- ...
Table attachment_associations
- id
- type
- parent_id
- attachment_id
What I wold like to do is to be able to map orders, tickets and mails to the same attachments table.
However, i'm stuck on how to do this in doctrine.
I tried using the following method. This does seem to get the record I am looking for. But I don't know how to automatically create, update or delete the record in the associative table (join table) using this method.
/**
* @ORM\ManyToMany(targetEntity="\...\...\Entity\Attachment")
* @ORM\JoinTable(name="attachment_associations",
* joinColumns={@ORM\JoinColumn(name="parentId", referencedColumnName="id")},
* inverseJoinColumns={
* @ORM\JoinColumn(name="attachmentId", referencedColumnName="id")
* }
* )
*/
protected $attachments;
If I delete a Mail, Order or Ticket, will all corresponding attachments be deleted as well?
One way to do this very easily is to implement a mapped super class with class table inheritance which your other entities extend from.
Though there are performance implications which you have to judge for your specific project.
Here's a simple example:
The mapped super class
<?php
namespace AcmeBundle\Model;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
* @ORM\InheritanceType("JOINED")
* @ORM\DiscriminatorColumn(name="type", type="string")
*/
abstract class SuperClass
{
/**
* @var int
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @var Attachment[]
*
* @ORM\ManyToMany(targetEntity="Attachment", mappedBy="parents")
*/
protected $attachments = [];
/**
* Constructor
*/
public function __construct()
{
$this->attachments = new ArrayCollection();
}
// put setters/getters for $attachments here
}
and the attachment manage the association.
<?php
namespace AcmeBundle\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class Attachment
{
/**
* @var int
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @var SuperClass
*
* @ORM\ManyToMany(targetEntity="SuperClass", inversedBy="attachments")
*/
private $parents;
/**
* Constructor
*/
public function __construct()
{
$this->parents = new ArrayCollection();
}
}
the entity simply extends the super class
<?php
namespace AcmeBundle\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class Ticket extends SuperClass
{
}
You can have all attachments in one table by using a unidirectional One-To-Many with a join table. In doctrine this is done with a unidirectional Many-To-Many with a unique constraint on the join column. It would mean one table with attachments but different join tables to connect to each parent.
The disadvantage of this solution is that it is unidirectional meaning that your attachment is not aware of the owning side of the relationship.
In full code this would look like this:
A AttachmentsTrait
with setters and getters for attachments to prevent code duplication:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
/**
* @property Collection $attachments
*/
trait AttachmentTrait
{
/**
* Add attachment.
*
* @param Attachment $attachment
* @return self
*/
public function addAttachment(Attachment $attachment)
{
$this->attachments[] = $attachment;
return $this;
}
/**
* Add attachments.
*
* @param Collection $attachments
* @return self
*/
public function addAttachments(Collection $attachments)
{
foreach ($attachments as $attachment) {
$this->addAttachment($attachment);
}
return $this;
}
/**
* Remove attachment.
*
* @param Attachment $attachments
*/
public function removeAttachment(Attachment $attachment)
{
$this->attachments->removeElement($attachment);
}
/**
* Remove attachments.
*
* @param Collection $attachments
* @return self
*/
public function removeAttachments(Collection $attachments)
{
foreach ($attachments as $attachment) {
$this->removeAttachment($attachment);
}
return $this;
}
/**
* Get attachments.
*
* @return Collection
*/
public function getAttachments()
{
return $this->attachments;
}
}
Your Mail
entity:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
class Mail
{
use AttachmentsTrait;
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @ORM\ManyToMany(targetEntity="Attachment")
* @ORM\JoinTable(name="mail_attachments",
* inverseJoinColumns={@ORM\JoinColumn(name="attachment_id", referencedColumnName="id")},
* joinColumns={@ORM\JoinColumn(name="mail_id", referencedColumnName="id", unique=true)}
* )
*/
$attachments;
/**
* Constructor
*/
public function __construct()
{
$this->attachments = new ArrayCollection();
}
}
Your Order
entity:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
class Order
{
use AttachmentsTrait;
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @ORM\ManyToMany(targetEntity="Attachment")
* @ORM\JoinTable(name="order_attachment",
* inverseJoinColumns={@ORM\JoinColumn(name="attachment_id", referencedColumnName="id")},
* joinColumns={@ORM\JoinColumn(name="order_id", referencedColumnName="id", unique=true)}
* )
*/
$attachments;
/**
* Constructor
*/
public function __construct()
{
$this->attachments = new ArrayCollection();
}
}
Your Ticket
entity:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
class Ticket
{
use AttachmentsTrait;
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @ORM\ManyToMany(targetEntity="Attachment")
* @ORM\JoinTable(name="ticket_attachment",
* inverseJoinColumns={@ORM\JoinColumn(name="attachment_id", referencedColumnName="id")},
* joinColumns={@ORM\JoinColumn(name="ticket_id", referencedColumnName="id", unique=true)}
* )
*/
$attachments;
/**
* Constructor
*/
public function __construct()
{
$this->attachments= new ArrayCollection();
}
}
If you really want the Attachment
to be aware of the other side you could add an additional entity in between to manage this. It would mean making the join tables themselves to entities for example: MailAttachment
, TicketAttachment
and OrderAttachment
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With