Doctrine: How to define an entity

Updated: January 13, 2024 By: Guest Contributor Post a comment

Overview

Doctrine is a powerful PHP Object Relational Mapper (ORM) that is primarily used with the Symfony framework but can also be used as a standalone library. In this tutorial, we’ll explore how to define an entity in Doctrine, complete with code examples to illustrate each step from basic to advanced usage. An entity represents a business object in Doctrine that can be persistently stored in a database.

Before we dive into the specifics of defining entities, you should have a basic understanding of PHP and Object-Oriented Programming (OOP). Additionally, you should have a working installation of Doctrine ORM and a configured database connection.

Defining a Basic Entity

At the most basic level, an entity in Doctrine is a PHP class that maps to a database table. Here’s a simple example of an entity that represents a product:

<?php
use Doctrine\ORM\Mapping as ORM;

/** 
 * @ORM\Entity
 * @ORM\Table(name="products")
 */
class Product
{
    /** 
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /** 
     * @ORM\Column(type="string")
     */
    private $name;

    // Getters and setters
    public function getId()
    {
        return $this->id;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

This code snippet defines a simple entity with two properties: an ID and a name. The ORM\Entity annotation above the class tells Doctrine that this class is an entity. The ORM\Table annotation defines the table name in the database.

Utilizing Relationships

Entities often have relationships with other entities. Let’s add a many-to-one relationship to our Product entity, linking it to a Category entity:

// ... existing Product class

/**
 * @ORM\ManyToOne(targetEntity="Category")
 * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
 */
private $category;

// ... Getters and setters for category

The ORM\ManyToOne annotation establishes a many-to-one relationship between the Product and the Category entities. The ORM\JoinColumn annotation specifies the name of the column that holds the foreign key reference.

Handling Complex Relationships

Doctrine offers several types of relationships like One-to-One, One-to-Many, and Many-to-Many. Let’s add a bidirectional One-to-Many relationship, mapping a product to multiple reviews:

/**
 * @ORM\OneToMany(targetEntity="Review", mappedBy="product")
 */
private $reviews;

public function __construct() {
    $this->reviews = new \Doctrine\Common\Collections\ArrayCollection();
}

// ... Getters and setters for reviews 

This establishes a One-to-Many relationship where a Product can have multiple Reviews. The mappedBy attribute points to the owning side of the relationship.

Advanced Mapping Options

Entities can be further customized with advanced options. For example, to tell Doctrine to fetch associated entities lazily, or to specify unique constraints:

/**
 * @ORM\Entity
 * @ORM\Table(name="products", uniqueConstraints={@ORM\UniqueConstraint(name="product_name_unique", columns={"name"})})
 */
class Product
{
    // ... Class content remains the same
}

//Specify lazy loading for the relationship
/**
 * @ORM\ManyToOne(targetEntity="Category", fetch="LAZY")
 * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
 */
private $category;

The uniqueConstraints option ensures that each product name is unique within the products table. The fetch option set to LAZY in the ManyToOne relationship tells Doctrine to only load the Category entity when it’s accessed for the first time.

Doctrine Lifecycle Callbacks

Doctrine provides lifecycle callbacks, which are methods that can be automatically called at certain points in an entity’s lifecycle, such as before saving to or after loading from the database. To utilize these, you need to define them in your entity and configure Doctrine to use them:

/**
 * @ORM\HasLifecycleCallbacks()
 */
class Product
{
    // ...

    /**
     * @ORM\PrePersist
     */
    public function setCreatedAtValue()
    {
        if(!$this->createdAt) {
            $this->createdAt = new \DateTime();
        }
    }
    // ...
}

In the example above, the setCreatedAtValue() method will be called before Doctrine persists the entity, automatically setting a createdAt timestamp if it’s null.

Working with Indexes

You may also define indexes on your entities to improve performance. Here we add an index on the name column of the Product:

/**
 * @ORM\Entity
 * @ORM\Table(name="products", indexes={@ORM\Index(name="product_name_index", columns={"name"})})
 */
class Product
{
    // ... Existing content
}

Indexes are especially important for large datasets and where the column is frequently searched or joined on.

Conclusion

Defining entities is the foundation of working with Doctrine ORM. In this tutorial, we’ve gone through the basics of creating an entity, handling relationships, adding advanced mapping options, using lifecycle callbacks, and indexing. Doctrine entities equip you with a structured, powerful way to interact with the database using object-oriented PHP. Remember that every entity should have a clear purpose and mirror the structure of your database. With these concepts and examples, you’re on your way to mastering the art of defining entities in Doctrine.