Let me tell you something – when I first encountered Doctrine ORM, I was completely overwhelmed. The documentation seemed written for people who already understood ORM concepts, and I was lost in a sea of unfamiliar terms. Fast forward a bit, I couldn’t stop using Doctrine in almost every PHP project I build. It’s that powerful.
Doctrine has absolutely revolutionized how I work with databases in PHP applications. No more writing repetitive SQL queries or dealing with complex database operations manually – Doctrine handles all that heavy lifting for me. And today, I’m going to show you exactly how to perform basic CRUD (Create, Read, Update, Delete) operations with Doctrine that will transform your PHP development experience forever.
Doctrine is the most powerful Object-Relational Mapping (ORM) tool available for PHP. It creates a virtual object database that you can use from within PHP, eliminating the need to write SQL in most cases. Instead of thinking in tables and rows, you’ll work with PHP objects and their properties.
The beauty of Doctrine lies in its ability to:
Before diving into CRUD operations, let’s make sure you’ve got Doctrine properly installed and configured.
The easiest way to install Doctrine is through Composer. If you don’t have Composer yet, download it here.
composer require doctrine/orm
Code language: JavaScript (javascript)
This single command will install Doctrine ORM and all its dependencies for you. Simple!
To use Doctrine, you’ll need to set up entity manager configuration. Here’s a basic setup:
// bootstrap.php
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
// Create a simple "default" Doctrine ORM configuration for Annotations
$config = ORMSetup::createAttributeMetadataConfiguration(
[__DIR__."/src"],
true
);
// Database configuration parameters
$dbParams = [
'driver' => 'pdo_mysql',
'user' => 'db_username',
'password' => 'db_password',
'dbname' => 'database_name',
];
// Obtaining the entity manager
$entityManager = EntityManager::create($dbParams, $config);
Code language: PHP (php)
Let’s create a simple Contact entity class:
// src/Entity/Contact.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: "contacts")]
class Contact
{
#[ORM\Id]
#[ORM\Column(type: "integer")]
#[ORM\GeneratedValue]
private $id;
#[ORM\Column(type: "string", length: 255)]
private $name;
#[ORM\Column(type: "string", length: 255)]
private $email;
#[ORM\Column(type: "string", length: 255)]
private $subject;
#[ORM\Column(type: "text")]
private $message;
// Getters and setters
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
public function getEmail() {
return $this->email;
}
public function setEmail($email) {
$this->email = $email;
}
public function getSubject() {
return $this->subject;
}
public function setSubject($subject) {
$this->subject = $subject;
}
public function getMessage() {
return $this->message;
}
public function setMessage($message) {
$this->message = $message;
}
}
Code language: PHP (php)
With our setup complete, let’s dive into CRUD operations!
Creating and saving new records with Doctrine is beautifully straightforward. Instead of constructing SQL INSERT statements, you simply create a new object, set its properties, and persist it:
function save_contact($data, $id = null) {
global $entityManager;
if (empty($id)) {
// Create new entity for insertion
$contact = new \App\Entity\Contact();
} else {
// Load existing entity for update
$contact = $entityManager->find("App\Entity\Contact", $id);
if (!$contact) {
return false;
}
}
// Set properties
$contact->setName($data["name"]);
$contact->setEmail($data["email"]);
$contact->setSubject($data["subject"]);
$contact->setMessage($data["message"]);
try {
// Save to database
$entityManager->persist($contact);
$entityManager->flush();
return true;
} catch (\Exception $err) {
// Handle error
return false;
}
}
Code language: PHP (php)
Usage is super simple:
$data = [
"name" => "John Doe",
"email" => "john@example.com",
"subject" => "Hello",
"message" => "This is a test message"
];
save_contact($data); // For insertion
Code language: PHP (php)
The beauty of this approach is that the save_contact function handles both creation AND updating – no duplicate code needed!
Reading data is where Doctrine truly shines. Let’s look at a few different ways to retrieve data:
function get_single($id) {
global $entityManager;
try {
$contact = $entityManager->find("App\Entity\Contact", $id);
return $contact;
} catch (\Exception $err) {
return null;
}
}
Code language: PHP (php)
It doesn’t get simpler than that! The find() method automatically looks up the primary key column (in this case, ‘id’).
When building real applications, you’ll frequently need to retrieve multiple records with various conditions:
function get_contacts($start = 0, $limit = 10, $criteria = null, $orderBy = null) {
global $entityManager;
try {
return $entityManager->getRepository("App\Entity\Contact")
->findBy($criteria, $orderBy, $limit, $start);
} catch (\Exception $err) {
return [];
}
}
Code language: PHP (php)
Usage examples:
// Get all active contacts, ordered by name
$activeContacts = get_contacts(
0, // start index
10, // limit
['active' => true], // criteria
['name' => 'ASC'] // order by
);
// Get the 5 most recent contacts
$recentContacts = get_contacts(
0, // start index
5, // limit
null, // no criteria
['createdAt' => 'DESC'] // order by date descending
);
Code language: PHP (php)
Counting is essential for pagination. Here’s how to get the total count:
function get_contact_count($criteria = null) {
global $entityManager;
try {
$queryBuilder = $entityManager->createQueryBuilder()
->select('COUNT(c)')
->from('App\Entity\Contact', 'c');
// Add criteria if provided
if ($criteria) {
foreach ($criteria as $field => $value) {
$queryBuilder->andWhere("c.$field = :$field")
->setParameter($field, $value);
}
}
return $queryBuilder->getQuery()->getSingleScalarResult();
} catch (\Exception $err) {
return 0;
}
}
Code language: PHP (php)
As I mentioned earlier, our save_contact function actually handles both creation and updates. Here’s how you’d use it for updates:
$data = [
"name" => "John Doe Updated",
"email" => "john.updated@example.com",
"subject" => "Updated Subject",
"message" => "This message has been updated"
];
save_contact($data, 1); // The '1' is the ID to update
Code language: PHP (php)
The function will load the existing contact, update its properties, and save the changes. So elegant!
Deleting records with Doctrine is straightforward:
function delete_contacts($ids) {
global $entityManager;
try {
// Convert to array if single ID
if (!is_array($ids)) {
$ids = [$ids];
}
foreach ($ids as $id) {
// Performance optimization: Use partial reference
$contact = $entityManager->getPartialReference("App\Entity\Contact", $id);
$entityManager->remove($contact);
}
// Execute all deletions at once
$entityManager->flush();
return true;
} catch (\Exception $err) {
return false;
}
}
Code language: PHP (php)
The getPartialReference()
method is a performance optimization that deserves special attention. Instead of loading the entire entity from the database (which would happen with find()
), it creates a lightweight reference object that contains just the ID. This is perfect for deletion operations where you don’t need the entity’s data.
While basic CRUD operations cover most needs, Doctrine offers advanced features for complex scenarios:
When the standard repository methods aren’t enough:
function find_contacts_by_keyword($keyword) {
global $entityManager;
$queryBuilder = $entityManager->createQueryBuilder();
$query = $queryBuilder->select('c')
->from('App\Entity\Contact', 'c')
->where($queryBuilder->expr()->orX(
$queryBuilder->expr()->like('c.name', ':keyword'),
$queryBuilder->expr()->like('c.email', ':keyword'),
$queryBuilder->expr()->like('c.subject', ':keyword'),
$queryBuilder->expr()->like('c.message', ':keyword')
))
->setParameter('keyword', '%' . $keyword . '%')
->getQuery();
return $query->getResult();
}
Code language: PHP (php)
Sometimes you need raw SQL power:
function execute_raw_query($sql, $params = []) {
global $entityManager;
$connection = $entityManager->getConnection();
$statement = $connection->prepare($sql);
return $statement->executeQuery($params)->fetchAllAssociative();
}
Code language: PHP (php)
Working with Doctrine in production? Here are some essential optimization tips:
// Process 100 records at a time
$batchSize = 100; $i = 0;
foreach ($largeDataset as $data) {
$contact = new Contact();
// Set properties...
$entityManager->persist($contact);
// Flush every 100 iterations
if (($i % $batchSize) === 0) {
$entityManager->flush();
$entityManager->clear();
// Clear managed objects
}
$i++;
}
<em>// Flush remaining entities</em>
$entityManager->flush();
Code language: PHP (php)
$query = $entityManager->createQuery('SELECT c.id, c.name FROM App\Entity\Contact c');
$partialContacts = $query->getResult();
Code language: PHP (php)
One of the most common performance issues with ORMs is the N+1 query problem:
// BAD: Will execute N+1 queries
$contacts = $entityManager->getRepository(Contact::class)->findAll();
foreach ($contacts as $contact) {
// This will execute a separate query for each contact
$messages = $contact->getMessages();
}
// GOOD: Uses join to fetch related data in one query
$query = $entityManager->createQuery(
'SELECT c, m FROM App\Entity\Contact c JOIN c.messages m'
);
$contacts = $query->getResult();
Code language: PHP (php)
Ever seen this error? “A new entity was found through the relationship…”
Solution:
// Make sure to persist related entities first
$entityManager->persist($relatedEntity);
$entityManager->persist($mainEntity);
$entityManager->flush();
Code language: PHP (php)
Doctrine has completely transformed how I build PHP applications. By abstracting away the database layer and letting me work with PHP objects, I can focus on building features rather than wrestling with SQL queries.
If you’re just starting with Doctrine, I hope this guide has given you a solid foundation. Remember, the initial learning curve might feel steep, but the productivity gains are absolutely worth it. Start with these basic CRUD operations, then gradually explore more advanced features.
Happy coding, and may your database interactions be forever simplified with the power of Doctrine ORM!
Want to learn more? Check out my other Doctrine tutorials:
Learn how to integrate service workers in React, Next.js, Vue, and Angular with practical code examples and production-ready implementations for modern web applications.
Master the essential service worker caching strategies that transform web performance. Learn Cache-First, Network-First, and Stale-While-Revalidate patterns with practical examples that'll make your apps blazingly…
Master the intricate dance of service worker states and events that power modern PWAs. From registration through installation, activation, and termination, understanding the lifecycle unlocks…
This website uses cookies.
View Comments
God bless you dude. You saved my life. I looked from videos to official docs of the Doctrine, they all suck. But this saved me. Really great tutorial. Hope you can do same for PHPUnit too ;)
while performing select/read operation when i am trying to load retrieved data in view page following error occurs
Fatal error: Cannot access private property PdContact::$name in C:\xampp\htdocs\doctrine\application\views\retrievedata.php on line 10
how can i show them in view page.