cult3

How is Doctrine 2 different to Eloquent?

Jul 07, 2014

Table of contents:

  1. Entities are just plain ol’ PHP
  2. Annotations within the entity
  3. The Entity Manager is the boss
  4. What is the Unit of Work?
  5. The Identity Map
  6. Getting entities from the database
  7. Repositories are really repositories
  8. Custom Repositories
  9. Doctrine Query Language
  10. Conclusion

One of the really great things about ORM’s that implement the Active Record pattern like Eloquent is, they are really easy and really intuitive to use.

With Active Record, you basically just have an object that you can manipulate and then save. Calling save() on the object updates the database, and all of the magic persistence happens behind the scenes.

Having the business logic of the object and the persistence logic of the database tied together definitely makes working with an Active Record ORM easier to pick up.

In this tutorial I want to explore exactly how Doctrine 2, an ORM that implements the Data Mapper pattern, is different to Eloquent.

Entities are just plain ol’ PHP

One of the big differences between Doctrine 2 and Eloquent is, Doctrine 2 entities are just plain old PHP, whereas Eloquent entities inherit all of the persistence logic of the ORM.

Typically whilst using Eloquent, you would write something like this:

class User extends Eloquent
{
}

The User class might have a couple of relationships and perhaps some helper methods, but for the most part it would be an empty class.

When interacting with the User entity, you have all of the methods of Eloquent available because you are extending Eloquent. So for example, you can save the entity at any point in your code just by calling the save() method:

$user = User::find(123);
$user->name = "Philip Brown";
$user->save();

With Doctrine, your entities are just plain PHP objects as they do not inherit any of the overhead from extending an ORM class.

A Doctrine User entity might look something like this:

use Doctrine\ORM\Mapping as ORM;

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

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

As you can see this is just a plain PHP class. It is therefore very light because it does not come with the overhead of the entire ORM.

Annotations within the entity

Unlike Eloquent, Doctrine 2 entities have annotations that describe the entity. This looks a little bit overwhelming at first, but it is really just simple markup that gives Doctrine meta data about the class.

You will notice in the example above that we list details about how the entity should be stored such as what the table should be called and what type of column should be used.

In Laravel we use migration files to create the database schema. In Doctrine 2, the database is automatically created to reflect the meta data in the entity annotations.

The Entity Manager is the boss

Doctrine 2 entities are just plain PHP objects and so they don’t know anything about persistence. This means we can’t just call the save() method on the object to save it to the database.

In Doctrine 2 we have a service class known as the Entity Manager. The Entity Manager has the sole responsibility of dealing with all persistence logic.

This means that whenever you want to save an entity you must pass the entity to the Entity Manager, persist and flush:

$user = new User();
$user->setName("Philip Brown");

EntityManager::persist($user);
EntityManager::flush();

Why would you persist() and then flush()? Doctrine 2 updates the database in transactions, rather than individual queries. This is known as the Unit of Work.

What is the Unit of Work?

An important concept to understand when using Doctrine 2 is the Unit of Work.

You will notice that when we want to interact with the database we have to first persist($user) and then flush().

Why do you have to persist() and then flush() rather than just saving?

Doctrine 2 uses a strategy called transactional write-behind when interacting with the database. This means that Doctrine 2 will delay interacting with the database until the flush() method is called.

When you call persist($user) on the Entity Manager, Doctrine will keep track of this and any other objects that are persisted. When the flush() method is called, Doctrine 2 will automatically generate the SQL queries to update the database in the most efficient way using a database transaction.

I don’t think it is really important to understand exactly how Doctrine 2 is managing this behind the scenes. However the Unit of Work pattern can be advantageous in a number of ways.

The Identity Map

Another nice feature of Doctrine 2 is The Identity Map. Take for instance the following code:

$user = EntityManager::find("Cribbb\Entities\User", 1);
$user->setUsername("philipbrown");

$user = EntityManager::find("Cribbb\Entities\User", 1);
echo $user->getUsername();

In the code above we are retrieving a user from the database and setting the username property.

Next we retrieve the same user from the database again and display their username.

Normally this would cause multiple SQL queries with the database as the object was shuttled back and forth.

Doctrine 2 however manages these kinds of scenarios through it’s Identity Map.

Doctrine 2 is clever enough to keep track of entities and so it will just return you the same instance over and over for the duration of the PHP request. This means you can request the same object in multiple different ways, but Doctrine 2 won’t need to hit the database.

Getting entities from the database

When you want to retrieve an entity from the database using the Active Record pattern you can simply call the find() method on the model.

For example:

$user = User::find(1);

As you can probably guess, in order to retrieve entities from the database in Doctrine 2 we need to use the Entity Manager instead of the model itself:

$user = EntityManager::find("Cribbb\Entities\User", 1);

Repositories are really repositories

Previously in this series we’ve looked at using Repositories to allow us mock the database when testing Controllers.

Strictly speaking, using “repositories” with Active Record style objects isn’t following the repository pattern because every Active Record object can manipulate the database whether it’s inside of the repository or not. In order to be true to the Repository Pattern we should really be returning standard PHP objects instead of instances of Eloquent’s model.

Doctrine is more faithful to the repository pattern as a way of working with persisting and querying data from the database.

When we want to find an entity by it’s id we can do the following (as shown above):

$user = EntityManager::find("Cribbb\Entities\User", 1);

However this is really just a shortcut for:

$user = EntityManager::getRepository("Cribbb\Entities\User")->find($id);

The getRepository() method will return a repository that will allow you to query the database. You don’t have to actually create this repository as Doctrine 2 will simply return an instance of Doctrine\ORM\EntityRepository. This repository object has a number of helpful methods for querying the database, such as:

$users = EntityManager::getRepository("Cribbb\Entities\User")->findBy([
    "location" => "UK",
]);

$users = EntityManager::getRepository("Cribbb\Entities\User")->findOneBy([
    "username" => "philipbrown",
]);

Each of these methods accept an array of query conditions.

Custom Repositories

You will probably find that a lot of the queries you want to perform are made possible by Doctrine 2’s EntityRepository instance.

However at some point you will need to write your own queries or methods.

To do this you can create a custom repository that extends the EntityRepository instance. This means you inherit all of those existing methods, but you also have a place to keep all of your custom methods that your application requires.

To return an instance of your custom repository instead of the generic repository for a particular entity, all you have to do is specify it within the annotations in your entity model:

/**
 * @entity(repositoryClass="Cribbb\Repositories\UserRepository")
 */
class User
{
}

Next you can define your custom repository by extending the EntityRepository class:

class UserRepository extends EntityRepository
{
    public function getAllSuperUsers()
    {
        //
    }
}

Now when you query using the Entity Manager you will automatically be returned an instance of UserRepository.

Doctrine Query Language

The final big difference between Doctrine 2 and Eloquent is the Doctrine Query Language (DQL).

If you’ve ever built a complex application in Laravel, you’ve probably hit the problem of needing to do an intricate database query that just isn’t possible using Eloquent.

Fortunately Laravel provides the excellent Query Builder that can give you more control over how your queries work.

Doctrine Query Language is an object query language. This means instead of thinking of queries in the terms of relations, you think of them in the terms of objects.

For example, you could do something like this:

$query = EntityManager::createQuery(
    "select u from Cribbb\Entities\User u where u.karma >= 10 and u.karma <= 100"
);
$users = $query->getResult();

DQL is the most powerful way of querying your database and so it will become an invaluable tool to reach for when you need a very precise query in your application. DQL could probably deserve it’s own article because there is so much to cover, but we’ll cross that bridge when we come to it.

Conclusion

If you’ve never really looked into Doctrine 2 before, hopefully this has been an interesting article on what is possible in Doctrine 2 and how it differs from an Active Record implementation like Eloquent.

I really like some of the features and capabilities of Doctrine 2. I think for certain types of applications, using Doctrine 2 feels a lot more natural than using Eloquent.

Of course, for other types of applications, using Doctrine 2 is going to be massively overkill.

I think you should be pragmatic in your decision when it comes to looking at what ORM to use. Whilst Doctrine 2 is great, there really is no need to add unneeded complexity.

However if you are building an application that would benefit from using the Data Mapper pattern, hopefully this article has shown how powerful Doctrine 2 can really be.

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.