There are only 2 types of relations. All other types are usually bugs.
Owning Relations
You can easily identify this relation by these properties:
- You usually edit the referenced entity in the same form as the current one.
- The referenced entity must be saved, when the current entity is saved.
- The referenced entity must be deleted together with the current entity.
- The referenced entity cannot be referenced by another entity through the same relationship
- It is usually always a bidirectional
OneToManyrelation (orManyToOnefrom the owned entities view)
Let’s say you have a Product, which has ProductVariants. Those variants are basically part of the Product and not stand alone entities.
class Product {
/**
* @ORM\OneToMany(
* targetEntity=ProductVariant::class,
* mappedBy="product",
* orphanRemoval=true,
* cascade={"ALL"}
* )
* @Assert\Valid()
*/
private Collection $variants;
}class ProductVariant {
/**
* @ORM\ManyToOne(
* targetEntity=Product::class,
* inversedBy="variants"
* )
* @ORM\JoinColumn(
* onDelete="CASCADE",
* nullable=false
* )
*/
private Product $product;
}So what does that mean?:
cascade={"ALL"}ensures that deleting or creating aProductwill also cascade toProductVariants on the Doctrine side.onDelete="CASCADE"modifies the database constraint so that deleting aProduct, deletes theProductVariant.- Deleting or Creating a
ProductVariantwon’t cascade to theProduct, since there is no cascade declaration. orphanRemoval=trueensures that removing a reference to aProductVariantwill delete it, even if it isn’t explicitly deleted.@Assert\Validensures that Validating theProductalso validates theProductVariant.nullable=falseensures that aProductVariantcannot exist in the database without aProduct.
This consistency is enforced both on the doctrine side and on the database side, so you can also easily delete a product in the database without checking the relations first.
As a side note: Bidirectional relations require a lot of logic in their getters and setters. I have created PHPStorm live templates to ease writing those.
Referencing Relations
These relations have the following properties:
- You usually create the referenced entity independent of the current entity (eg. in another form or through another process)
- The referenced entity usually has no knowledge that it is referenced, although that is not a requirement
- The other entity must never be deleted or creating through updating of that reference
- It is usually an unidirectional
ManyToManyorManyToOne, depending on if you need multiple items.
Let’s use a Product example and think of a Category. A Product can have 1 or many Categoryies.
class Product {
/**
* If there can be many
* @ORM\ManyToMany(targetEntity=Category::class)
*/
private Collection $categories;/** * If there can only be one * @ORM\ManyToOne(targetEntity=Category::class) * @ORM\JoinColumn(onDelete="SET NULL") */ private ?Category $category; }
class Category {
// The category usually does not know about the Product
}There is nothing really special to this type. Just note that you usually never have a OneToMany here, as this implies an ownership of the related entity which you do not have here.
onDelete="SET NULL"ensures that the database won’t stop you from deleting aCategoryif needed. But, if you need a value in that relation (@Assert\NotNull), you can choose to omit thatonDeletestatement.
Other relation types?
Other relation type are rare. You should stick with those 2 unless you really know what you are doing.
How to use them consistently
I’d really like some meta annotations that is just correct by default. Something like @OwningRelation(ProductVariant::class) and that would be it, but unfortunately that doesn’t exist.
In the meantime, what I do is use live templates/macros in my IDE so I don’t have to think about the options every time I define a relation:
Use PHPStorm Live Templates for Doctrine fields The Doctrine ORM requires you to use a very verbose programming style where you can easily make needless mistakes if…medium.marco.zone