PHP Doctrine Upsert: Update if exists, insert if not

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

Overview

Managing your database effectively is crucial for the performance and scalability of your applications. In the realm of PHP development, Doctrine is a widely used ORM that allows developers to interact with databases using an object-oriented approach. In this tutorial, we will dive into the concept of ‘upsert’—an operation that updates records if they exist, or inserts them if they do not—and how to implement it using PHP Doctrine.

What is an Upsert?

The term ‘upsert’ is a portmanteau of ‘update’ and ‘insert’. It’s used to describe a database operation that checks whether a record already exists based on a unique constraint and if it does, it updates the existing record, otherwise, it inserts a new record. This operation is handy in avoiding duplicate records and is more efficient than running separate insert and update operations.

Prerequisites

To get the most out of this tutorial, you should have the following:

  • Understanding of PHP and object-oriented programming.
  • Basic knowledge of databases and SQL.
  • Installed version of PHP.
  • A configured Doctrine environment in a PHP project.

Naive Approach

Before we discuss how to do an upsert the Doctrine way, let’s go over a naive approach:

<?php

$entity = $entityManager->getRepository('User')->findOneBy(['email' => '[email protected]']);

if ($entity) {
    $entity->setName('Updated Name');
    $entityManager->flush();
} else {
    $entity = new User();
    $entity->setName('New Name');
    $entity->setEmail('[email protected]');
    $entityManager->persist($entity);
    $entityManager->flush();
}

This code checks if a user with the given email exists and updates the name if it does. Otherwise, it creates a new User entity and persists it to the database.

Doctrine’s Upsert Operation

Instead of manually checking for the entity’s existence, Doctrine provides a more elegant way. The EntityManager class’s methods persist() and flush() can be used together to achieve an upsert.

However, Doctrine doesn’t have a dedicated upsert method, so what we’ll need to do is leverage the unique constraints system Doctrine provides to ensure that when the flush() method is called, MySQL (or another supported database platform) knows to perform an INSERT or UPDATE operation as necessary.

At first, you’ll need to make sure your entity has a unique constraint. For that, you need to modify your entity class. Let’s consider a ‘User’ entity with a unique ’email’ field:

<?php

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\UniqueConstraint(name="email_idx", columns={"email"})
 */
class User
{
    // ...
}
?>

Now that you have a unique constraint in place, a simple persist() and flush() should suffice:

<?php

use Doctrine\ORM\Exception\UniqueConstraintViolationException;

$user = new User();
$user->setName('John Doe');
$user->setEmail('[email protected]');
$entityManager->persist($user);

try {
    $entityManager->flush();
} catch (UniqueConstraintViolationException $e) {
    // Handle exception: record already exists
}

The above block will perform an upsert. If a user with the email ‘[email protected]’ already exists, Doctrine will throw an UniqueConstraintViolationException. You can catch this exception and decide how to update the existing record.

Handling the UniqueConstraintViolationException

When you catch the exception, you can then find the existing entity and update it. Here is an example of how you might do that:

<?php

use Doctrine\ORM\Exception\UniqueConstraintViolationException;

// ... try/catch block from previous sample ...
catch (UniqueConstraintViolationException $e) {
    $existingUser = $entityManager->getRepository('User')->findOneBy(['email' => '[email protected]']);
    if ($existingUser) {
        $existingUser->setName('John Doe Updated');
        $entityManager->flush();
    }
}

Using Doctrine’s Query Builder for an Upsert

Another alternative is to use Doctrine’s Query Builder to write an upsert-like operation. Here’s an idea of how you might do it:

<?php

use Doctrine\DBAL\Exception;

$qb = $entityManager->getConnection()->createQueryBuilder();

$result = $qb
    ->insert('User')
    ->values([
        'name' => ':name',
        'email' => ':email'
    ])
    ->setParameter('name', 'John Doe')
    ->setParameter('email', '[email protected]')
    ->getSQL();

try {
    $entityManager->getConnection()->executeStatement($result);
} catch (Exception $e) {
    // Handle exception: record already exists
}

In this example, you attempt to insert into the User table. If a record with that email already exists, an exception will be thrown, indicating a duplicate entry for the unique field ’email’. Then, you can catch that exception and perform the update accordingly.

Conclusion

Performing an upsert operation in Doctrine requires a bit of handling around unique constraints but following the patterns outlined above, you can keep your database interactions efficient and effective. Use Doctrine’s ORM capabilities to ensure data consistency and avoid writing repetitive, boilerplate SQL queries. As you become more familiar with Doctrine’s features, you’ll likely discover even more elegant patterns that suit your application’s needs.

Remember, working with databases and an ORM is as much about understanding the tools at your disposal as it is about knowing what your application requires. Efficiently using upsert operations is just one piece of the larger puzzle of writing maintainable, scalable applications with PHP and Doctrine.