Doctrine Entity Relations

An entity is an identifiable object that is stored in a database.

Relation means belonging of one entity to another.
For example, a relation between the Order and DeliveryAddress entities means that the order has a delivery address or the delivery address belongs to the order.

There are several relation types between entities: OneToOne, OneToMany, ManyToOne, ManyToMany.

OneToOne

OneToOne means that only one entity belongs to one entity.
For example, one order has only one delivery address.
The OneToOne relation makes it possible to get the DeliveryAddress entity from the Order entity.

#[ORM\Entity(repositoryClass: OrderRepository::class)]
#[ORM\Table(name: '`order`')]
class Order
{
    #[ORM\OneToOne()]
    private ?DeliveryAddress $deliveryAddress = null;
}
#[ORM\Entity(repositoryClass: DeliveryAddressRepository::class)]
class DeliveryAddress
{
    // relation is set on the side of the Order entity
}
CREATE TABLE "order" (id INT NOT NULL, delivery_address_id INT DEFAULT NULL, PRIMARY KEY(id));
CREATE UNIQUE INDEX UNIQ_F5299398EBF23851 ON "order" (delivery_address_id);
CREATE TABLE delivery_address (id INT NOT NULL, PRIMARY KEY(id));
ALTER TABLE "order" ADD CONSTRAINT FK_F5299398EBF23851 FOREIGN KEY (delivery_address_id) REFERENCES delivery_address (id) NOT DEFERRABLE INITIALLY IMMEDIATE;

OneToMany

OneToMany means that one entity has many entities.
For example, one category belongs to many products.
The Category entity does not contain a foreign key on its side, it is contained on the side of the Product entity.

The OneToMany relation makes it possible to get the Product entity collection from the Category entity.

#[ORM\Entity(repositoryClass: CategoryRepository::class)]
class Category
{
    #[ORM\OneToMany(mappedBy: 'category', targetEntity: Product::class)]
    private $products;
}
#[ORM\Entity(repositoryClass: ProductRepository::class)]
class Product
{
    #[ORM\ManyToOne(inversedBy: 'products')]
    #[ORM\JoinColumn(nullable: false)] // nullable: false means that the product must have a category
    private ?Category $category = null;
}
CREATE TABLE category (id INT NOT NULL, PRIMARY KEY(id));
CREATE TABLE product (id INT NOT NULL, category_id INT NOT NULL, PRIMARY KEY(id));
CREATE INDEX IDX_D34A04AD12469DE2 ON product (category_id);
ALTER TABLE product ADD CONSTRAINT FK_D34A04AD12469DE2 FOREIGN KEY (category_id) REFERENCES category (id) NOT DEFERRABLE INITIALLY IMMEDIATE;

ManyToOne

ManyToOne means that many entities belong to the one entity.
For example, many products belong to the one category.

The ManyToOne relation makes it possible to get the Category entity from the Product entity.

#[ORM\Entity(repositoryClass: ProductRepository::class)]
class Product
{
    #[ORM\ManyToOne(inversedBy: 'products')]
    #[ORM\JoinColumn(nullable: false)] // nullable: false means that the product must have a category
    private ?Category $category = null;
}
#[ORM\Entity(repositoryClass: CategoryRepository::class)]
class Category
{
    #[ORM\OneToMany(mappedBy: 'category', targetEntity: Product::class)]
    private $products;
}
CREATE TABLE product (id INT NOT NULL, category_id INT NOT NULL, PRIMARY KEY(id));
CREATE INDEX IDX_D34A04AD12469DE2 ON product (category_id);
CREATE TABLE category (id INT NOT NULL, PRIMARY KEY(id));
ALTER TABLE product ADD CONSTRAINT FK_D34A04AD12469DE2 FOREIGN KEY (category_id) REFERENCES category (id) NOT DEFERRABLE INITIALLY IMMEDIATE;

ManyToMany

ManyToMany means that the same entity belongs to many entities.
For example, the same product is stored in different stocks.

The ManyToMany relation makes it possible to get the Stock entity collection from a Product entity, or a Product entity collection from a Stock entity.

#[ORM\Entity(repositoryClass: ProductRepository::class)]
class Product
{
    #[ORM\ManyToMany(targetEntity: Stock::class, inversedBy: 'products')]
    private Collection $stocks;
}
#[ORM\Entity(repositoryClass: StockRepository::class)]
class Stock
{
    #[ORM\ManyToMany(targetEntity: Product::class, mappedBy: 'stocks')]
    private Collection $products;
}
CREATE TABLE product (id INT NOT NULL, PRIMARY KEY(id));
CREATE TABLE stock (id INT NOT NULL, PRIMARY KEY(id));
CREATE TABLE product_stock (product_id INT NOT NULL, stock_id INT NOT NULL, PRIMARY KEY(product_id, stock_id));
CREATE INDEX IDX_EA6A2D3C4584665A ON product_stock (product_id);
CREATE INDEX IDX_EA6A2D3CDCD6110 ON product_stock (stock_id);
ALTER TABLE product_stock ADD CONSTRAINT FK_EA6A2D3C4584665A FOREIGN KEY (product_id) REFERENCES product (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE product_stock ADD CONSTRAINT FK_EA6A2D3CDCD6110 FOREIGN KEY (stock_id) REFERENCES stock (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;