Doctrine one-to-many relationship tutorial
last modified July 10, 2020
Doctrine one-to-many relationship tutorial shows how to create a one-to-many relationship in PHP with Doctrime ORM.
Doctrine
Doctrine is a set of PHP libraries primarily focused on providing persistence services in PHP. Its main projects are an object-relational mapper (ORM) and the database abstraction layer (DBAL). Doctrine is an open source project made available for free under the MIT license.
Doctrine entity relationships
Entity is a PHP object that is going to be persisted. Entity classes
are decorated with annotations such as @Id
, @Table
,
or @Column
. There are relationships between entities (also called
associations.) The basic associations are one-to-one, one-to-many, and many-to-many.
Relationships may be unidirectional or bidirectional. In a unidirectional relationship we have a navigational access in one direction, in a bidirectional relationship, we have access in both directions. Suppose we have two entities: User and Post. There is a one-to-many relationship between the entities; one user may have multiple posts. In a unidirectional relationship, we can get posts from a user. In a bidirectional, we can get the user from the posts as well.
One-to-many relationship
In a one-to-many relationship, one record in a table can be associated with one or more records in another table. For example, one customer can have many orders.
The bidirectional one-to-many mapping requires the mappedBy
attribute
to be on the one side and the inversedBy
attribute on the many side.
There is no difference between a bidirectional one-to-many and a bidirectional
many-to-one relationship.
The @ManyToOne
represents the owning side of a bidirectional assocation.
The @OneToMany
represents the inverse side of a bidirectional assocation.
The owning side of a bidirectional relationship is the side that contains the foreign key.
The targetEntity
property defines the entity to which we are creating
the relationship.
The mappedBy
attribute hints the name of the variable that maps the child
entity in the parent entity. The inversedBy
attribute hints the name of
the variable that maps the parent entity in the child entity.
Both attributes help Doctrine optimize the queries.
The @JoinColumn
defines the name of the column that contains the foreign
key. The foreign key is created on the table which has the annotation.
The annotation is no required; if not used, the necessary attributes are inferred.
If specified, the name
and referencedColumnName
attributes
are required. The name
specifies the column name that holds the foreign key
identifier for the relation. The referencedColumnName
specified the name of
the primary key identifier that is used for joining of the relation.
Doctrine installation
We install Doctrine and some helper tools.
$ composer req doctrine/dbal
We install Doctrine. Note that the DBAL layer is included in the doctrine/dbal
package.
$ composer req symfony/var-dumper $ composer req tightenco/collect
We install Symfony's dumper and Laravel collections. We will be using them in our examples.
$ composer dumpautoload
We generate a list of all classes that need to be included in the project.
The composer
re-reads the composer.json
file to build
up the list of files to autoload.
Bootstrapping Doctrine CLI examples
We create a bootstrap file that will be included in all examples.
<?php use Doctrine\ORM\Tools\Setup; use Doctrine\ORM\EntityManager; require_once "vendor/autoload.php"; $isDevMode = true; $config = Setup::createAnnotationMetadataConfiguration([__DIR__ . "/src"], $isDevMode); $config->addEntityNamespace('', 'App\Entity'); $conn = ['driver' => 'pdo_pgsql', 'host' => 'localhost', 'dbname' => 'testdb', 'port' => 5432, 'user' => 'postgres', 'password' => 's$cret']; $em = EntityManager::create($conn, $config);
In the bootstrap file, we include the autoloading file and set up the connection to the PostgreSQL database.
$isDevMode = true; $config = Setup::createAnnotationMetadataConfiguration([__DIR__ . "/src"], $isDevMode); $config->addEntityNamespace('', 'App\Entity');
We create a Doctrine ORM configuration for annotations.
$conn = ['driver' => 'pdo_pgsql', 'host' => 'localhost', 'dbname' => 'testdb', 'port' => 5432, 'user' => 'postgres', 'password' => 's$cret'];
We specify the database configuration parameters.
$em = EntityManager::create($conn, $config);
We obtain the entity manager.
{ "require": { "doctrine/orm": "^2.6" }, "autoload": { "psr-4": { "App\\": "src" } }, "require-dev": { "symfony/var-dumper": "^4.2" } }
This is the composer.json
file.
Doctrine one-to-many bidirectional example
<?php namespace App\Entity; use App\Entity\User; /** * @Entity * @Table(name="tasks") **/ class Task { /** * @Id * @Column(type="integer") * @GeneratedValue(strategy="IDENTITY") */ protected $id; /** * @Column(type="string") */ protected $name; /** * @ManyToOne(targetEntity="User", inversedBy="tasks", cascade={"persist", "remove"}) * @JoinColumn(name="user_id", referencedColumnName="id") */ protected $user; public function getId() : int { return $this->id; } public function getName() : string { return $this->name; } public function setName($name) : void { $this->name = $name; } public function addUser(User $user): void { $this->user = $user; } public function getUser() : User { return $this->user; } }
<?php namespace App\Entity; use App\Entity\Task; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; /** * @Entity * @Table(name="users") **/ class User { /** * @Id * @Column(type="integer") * @GeneratedValue(strategy="IDENTITY") */ protected $id; /** * @Column(type="string") */ protected $name; /** * @OneToMany(targetEntity="Task", mappedBy="user", cascade={"persist", "remove"}) */ protected $tasks; public function __construct() { $this->tasks = new ArrayCollection(); } public function getId() : int { return $this->id; } public function getName() : string { return $this->name; } public function setName($name) : void { $this->name = $name; } public function getTasks() : Collection { return $this->tasks; } public function addTask(Task $task): void { $task->addUser($this); $this->tasks[] = $task; } }
$ vendor\bin\doctrine orm:schema-tool:create
With the above command, we create tables from the entities.
<?php require_once 'bootstrap.php'; use App\Entity\User; use App\Entity\Task; $task1 = new Task(); $task1->setName('Task A'); $task2 = new Task(); $task2->setName('Task B'); $task3 = new Task(); $task3->setName('Task C'); $task4 = new Task(); $task4->setName('Task D'); $task5 = new Task(); $task5->setName('Task E'); $user1 = new User(); $user1->setName('John Doe'); $user1->addTask($task1); $user1->addTask($task2); $user1->addTask($task3); $user2 = new User(); $user2->setName('Lucia Brenner'); $user2->addTask($task4); $user2->addTask($task5); $em->persist($task1); $em->persist($task2); $em->persist($task3); $em->persist($task4); $em->persist($task5); $em->persist($user1); $em->persist($user2); $em->flush();
In the program, we create two users who have five tasks.
<?php require_once "bootstrap.php"; use App\Entity\User; $userId = 2; $repository = $em->getRepository(User::class); $user = $repository->find($userId); echo "User: " . $user->getName() . "\n"; dump($user->getTasks()); $tasks = $user->getTasks(); foreach ($tasks as $task) { echo $task->getName() . "\n"; }
<?php require_once "bootstrap.php"; use App\Entity\Task; $taskId = 5; $repository = $em->getRepository(Task::class); $task = $repository->find($taskId); if ($task !== null) { echo "Task: " . $task->getName() . "\n"; echo "User: " . $task->getUser()->getName() . "\n"; }
In the example, we get a task and find out its user.
In this tutorial, we have
List all PHP tutorials.