Facebook
Twitter
Google+
Kommentare
0

05.12. Doctrine 2

Introduction

Object-relational mapping (ORM) frameworks have been around for several years now and for some people, ORM is
already outdated by now. As we have seen with other technologies and concepts before, PHP is not exactly what we call
an early adopter among the programming languages. Thus it took some time for ORM to grow up in the PHP context.

There have been some frameworks before Doctrine 2 that implement ORM (remember e.g. Propel) specific tasks but most
of them lack the required maturity to be used in large projects. With Doctrine 2, PHP takes a huge step into the right
direction – Doctrine 2 is fast, extensible and easy to use.

This article will take you on a tour through the main concepts of Doctrine 2 in the first part and then explain how
to use it in a real world application in the second part. Since at the time of writing Zend Framework 1.11.xx (ZF) is
very popular, we will integrate Doctrine 2 into a ZF project.

Basic Concepts

To understand Doctrine 2, we have to take a look at some relevant terms (or in this case objects), study their behavior
and practice their usage. We start with some introductory phrases on ORM systems and then go on to the concepts underlying
Doctrine 2: Entity Objects, the Entity Manager, Repositories and Proxies.

Object-relational Mapping

Since the beginning of Object-Orientation, people had to manage the persistence of their application’s state resp.
their objects. In the context of Web Application Development, this usually involves a Database server which is being
consulted using a Query Language. One example for this pattern is a PHP application that uses some kind of SQL server
by sending SQL queries to it. Another one is an application using a CouchDB server by querying it via its REST API.
Due to the author’s laziness, we will talk in terms of relational databases from now on. Keep in mind, that you can
accomplish almost everything mentioned here with NoSQL databases, too.

ORM relates value objects that exist in an application’s business logic to database records.
Thus every object that should be persistent is saved in one row of a database table. The most common approach is to
map classes to tables and the classes‘ objects to rows in the these tables.
Besides writing objects to a database, ORM systems are also intended to ease the process of finding data stored in the
database. When talking in terms of ORM, finding data always means making the framework fetch one or many objects
that meet a certain criteria.

Entity Objects

The objects that are being managed by an ORM system are called Entity Objects. Every entity object relates to one
entry in a table. In Doctrine 2, the classes that represent entities do not have to fulfill special requirements
like inheriting from a certain super class (as you might have seen in other database abstraction frameworks like
Zend_Db). When creating a new entity class with Doctrine 2, all you have to do is to write down a regular PHP class
with properties. Besides this, you have to provide some hints on how these attributes should be persisted.
The information how entity attributes relate to columns in the DB is called Metadata. Metadata can be described in
different ways: By default there are metadata drivers for descriptions in XML, YAML and PHP. The fourth
and most popular driver is based on DocBlock annotations (since in PHP, annotations aren’t a language feature as in
Java (see Wikipedia), they are contained by the classes‘ and attributes‘ DocBlocks).
We will use annotations to describe our entities metadata. To get an impression on how easy this is,
take a look at the following example.


This example contains all it needs to tell Doctrine 2 about the new entity User. With this class, you can create,
find, delete and modify user objects and persist their state to the underlying database. But keep in mind: as long
as you don’t need any persistence features, you can use your user objects just like any other objects!

The next two objects resp. object types we will describe are responsible for doing the ORM functionality:
persisting and finding.

The Entity Manager

To use ORM functionality, the Entity Manager (Doctrine\ORM\EntityManager) is the main access point to Doctrine 2. The entity manager is
responsible – as you might have guessed – for managing entities and for building a facade for the whole framework.
To accomplish its tasks, the entity manager uses some helpers. The Unit of Work object for example collects entities
that should be written back to the database and is capable of doing this in batches. This way, database operation
can be executed with almost no overhead and therefore are really fast.

Another dependency of the entity manager is the Event Manager. To be as extensible as possible, Doctrine 2 comes
with an event system that publishes all important state changes to the outside as events. You can register for such
events and extend the life cycle of your entity objects at one single point.

The entity manager’s API combines methods for managing entities (find, persist, contains, copy, detatch, merge,
remove and refresh), methods that control the use of transations (beginTransaction, commit, flush, rollback and
transactional) and some helper methods for creating custom queries and accessing some of the entity manager’s dependencies.

The following example shows how to query an object from the entity manager, modify it and write the changes back into
the database.


Creating a new persistent object is almost as easy as modifying it:

Repositories

For finding entities, Repositories are used. Every entity class has its own repository which is responsible for
finding entities of that type. By default, repositories have some handy methods for fetching entities that match certain criteria:

  • find: Finds an entity by its primary key / identifier
  • findAll: Finds all entities of the repository’s entity type
  • findBy / findOneBy: Finds all resp. one entity that matches the passed criteria:
  • findBy<attribute> / findOneBy<attribute>: Magic methods that ease the filtering by a single attribute:

To access a repository, all you have to do is ask the entity manager for one. If you have implemented your own
repository, it will be returned by Doctrine\ORM\EntityManager::getRepository(). Otherwise, Doctrine 2 will provide
a generic repository. The main reason to implement custom repository classes is to group custom queries for an entity
type to make them reusable. For custom query logic, there are several mechanisms you can use: You can either use
Doctrine’s query builder that implements an API similar to Zend_Db_Select or queries written in the Doctrine Query
Language
(DQL) or you can even execute plain SQL queries. With these options, it is also possible to migrate old
applications which use complex queries by just wrapping these queries into the methods of custom repositories.

Proxies

When traversing a graph of entity objects (which is required when entities are having relations to other entities),
it would be very expensive (in the sense of “requiring many database queries”) to fetch every depending entity with
an additional query. Therefore Doctrine 2 uses the concept of Proxy objects that represent regular entity objects
which have not been populated with data from the database. Take a look at the following example where the entity
Group aggregates a list of User objects in its member property. When accessing the members list, Doctrine 2 provides a
collection of proxy objects instead of complete User objects. When an object of this collection is being asked for
one of its properties, Doctrine loads the object’s data from the database. This way, the users‘ data is not loaded
until it is really needed.

Advanced Mapping Concepts

This section describes some advanced concepts that are required when mapping entity classes that have relationships to
other entity classes. Possible relationship types are association and inheritance. Inheritance is the mechanism used for
representing subtypes in object-oriented programming languages. An example would be a class User that implements
methods every user of a software should have and a class Administrator inheriting from User that adds methods for
determining the administrator’s access rights.

Association is a weaker relation type. It means that an entity object can be related to other entity objects of other
types. In terms of relational databases, there are three types of association which differ in the number of entities
an object is related to: 1:1, 1:n and n:m relationships. n and m are placeholders and mean multiple.

Association

To put objects of an entity type into relation, you just have to mention this relation in the entity class‘ mapping
information. The simplest case is a unidirectional 1:1 relationship. In the following example we describe a User entity
which has its access information (user credentials) encapsulated into another entity class called UserCredential. Since
every user has at most one credential object and every credential object may only be associated to one user object,
this is a 1:1 relationship.


If the relationship should be bidirectional, include the OneToOne attribute in the other class, too, and add an
attribute which denotes the attribute of the other entity that mapps the related object:


This way, you can access the user object from the credentials object, too.

Most of the times, developers have to deal with relationships which include many objects on at least one side.
These relationships are called 1:n or n:m relationships. This means that either one or multiple entities are standing
in relationship with an arbitrary number of entities of another type. To accomplish this, you have to use the mapping
keywords OneToMany or ManyToMany when describing your entities. Besides that, the mapping works the exact same way as
with 1:1 relationships.

There are however some tricks you should know when dealing with collections of associated entity objects. Consider
the following relationship between the entity classes User and Group:


When a group has at least one member, the group object will have a collection of the type
Doctrine\Common\Collections\ArrayCollection set as its members property. This collection contains
all user objects (or proxy objects as we have seen before) and can be modified intuitively with the methods add and
removeElement. To honor object-orientation, you might want to introduce custom methods for these tasks. If you do so,
you get into trouble when the group object does not have any users associated. In this case, the collection will simply be
set to null. To avoid checks whether the collection has already be initialized, you should to this by yourself
in the entity class‘ constructor:


It is also important to notice that one entity has to update the other entity’s state as well when a relationship
between to objects is created or removed. Take care to do this only in one class to avoid endless recursion loops!
This class is called the Owning Side of the relationship. When implementing a bidirectional relationship, the other
class is called the Inverse Side. It is important to determine owning and inverse side and implement the the classes
accordingly to avoid greater trouble during debugging.

There are some more features implemented by Doctrine 2 enabling developers to specify their entities‘ relationships
including sorting, pre-fetching and indexing. These topics are not covered in this article but are explained very
understandable in the Doctrine 2 documentation.

Inheritance

Subtyping can be implemented in different ways using Doctrine 2. The main difference between these implementations
is how the inheritance is mapped to the database. The options are to have one table for every class (Class Table Inheritance),
to have one table for all classes in a hierarchy (Single Table Inheritance) and to have a table for every specialized
sub-class of a given super-class (Mapped Super Class).We will give a short overview on all three alternatives, you have
to pick the right one yourself. This decision should be made based on how many common attributes there are in your sub-classes.

Mapped Superclasses

Introducing a mapped superclass is probably the easiest way for specifying inheritance but might lead to
many duplicate columns in your database schema. The superclass of your entities is not being declared as an entity
itself (and might also be declared abstract) but provides attributes and optionally methods that will be available
in all subclasses. When creating the database schema, Doctrine 2 merges all attributes and relationships of the
superclass into the definitions of the subclasses and processes them as regular entities.


After creating the database from this mapping information, your tables will look like this:

Single Table Inheritance

When having entities that are very similar besides some few attributes, you might want to store them together in one
database table. This approach is called Single Table Inheritance. To distinguish between the different types, there is
always a column marked as discriminator column and a discriminator map that tells Doctrine 2 which values in the
discriminator indicate what entity types.


These definitions cause the existing of one single table called User with all the attributes declared inside the
classes User and Administrator plus a column type – the discriminator column. When working with entities of these
types, Doctrine will manage the type flag automatically for you.

The resulting database schema looks as illustrated by the following diagram:

Class Table Inheritance

Having each entity type stored in its own table is always good for keeping your schema extensible. When you have to
create a new subtype, Doctrine 2 will just create a new table for this type and it can inherit the logic and common
attributes of a superclass. The only overhead you have with this approach is that all tables that correspond to subtypes
have to maintain a relationship to their supertype’s table. Using class table inheritance, the example with the entities
User and Administrator looks like this:


Besides the inheritance type, there is no difference to the example using single table inheritance. The outcome on
the resulting database scheme is huge. Now you have to separate tables which store users and administrators. Every
record in the table Administrator has a corresponding record in the User table.


This was the first part of this article. Stay tuned for part II which will be published tomorrow (on 6th of December 2011)! In the second part, we will integrate Doctrine 2 into a Zend Framework application and include a generic sandbox (ZF-)project with Doctrine 2!

Über den Autor

Mayflower Blog

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen