Doctrine ORM 2 – Part 1

Doctrine 2

Doctrine 2 is an ORM that implements the Data mapper pattern. Most other PHP ORM libraries implement the Active record pattern.

The advantage of the Data mapper pattern is that the objects don’t manage their own persistence like in Active record pattern. An Active record object inherits all the facilities to retrieve, update and save itself. The Data Mapper pattern has a separate mapper class to manage object persistence. It separates these two things: the object data and the storage methods. Your domain objects are clean and simple, they don’t extend classes which add many irrelevant methods to your classes (violating SRP). They’re just simple entities with a few properties and a few methods that are a part of your business layer.

Entities and metadata annotations

Example “Product” entity:

<?php
/**
 * @Entity @Table(name="products")
 **/
class Product
{
    /** @Id @Column(type="integer") @GeneratedValue **/
    protected $id;
    /** @Column(type="string") **/
    protected $name;

    // .. (other code)
}

Metadata for entities are configured using a XML, YAML or Docblock Annotations.

Obtaining the Entity Manager:

$entityManager = EntityManager::create($conn, $config);

The Entity Manager transforms entities from and back to persistence.

Insert products into the database:

$product = new Product($name);

$entityManager->persist($product);
$entityManager->flush();

echo $product->getId();

To actually perform the insertion, you have to explicitly call flush() on the EntityManager. This allows to aggregate all writes (INSERT, UPDATE, DELETE) into one single transaction, which is executed when flush is called.

Fetch a product from the database

$productRepository = $entityManager->getRepository('Product');
$product = $productRepository->find($id);

Looking up data is not a responsibility of the EntityManager: it’s a repository concern. The repository is an object finder where you can keep methods to find and fetch entities. Here the Entity Manager will look for the Repository which is attached to your Entity class (Product). If you have not set this yet it will provide you with a default Repository class (Doctrine\ORM\EntityRepository) which contains the following methods for accessing your data:

public function find($id)
public function findAll()
public function findBy(array $criteria)
public function findOneBy(array $criteria)

public function createQueryBuilder($alias)
public function createNamedQuery($queryName)

Most likely, you will create Product’s own repository to use the queryBuilder. You need to tell Doctrine now that you have created this repository:

/** @Entity(repositoryClass="Repositories\ProductRepository")
* @Table(name="products")
*/
class Product { ... }

These tutorials explain very well the use of custom repositories:
http://mackstar.com/blog/2010/10/04/using-repositories-doctrine-2
http://weavora.com/blog/2013/08/23/how-we-organize-doctrine2-repositories
http://www.wjgilmore.com/blog/2014/04/09/the-power-of-doctrine-2-s-custom-repositories-and-native-queries/

Update product

$productRepository = $entityManager->getRepository('Product');
$product = $productRepository->find($id);

if ($product === null) {
    echo "Product $id does not exist.\n";
    exit(1);
}

$product->setName($newName);
$entityManager->flush();

References between entities

References between objects are foreign keys in the database.
Must read: http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html
List of all the possible association mappings:

  • Many-To-One, Unidirectional
  • One-To-One, Unidirectional
  • One-To-One, Bidirectional
  • One-To-One, Self-referencing
  • One-To-Many, Bidirectional
  • One-To-Many, Unidirectional with Join Table
  • One-To-Many, Self-referencing
  • Many-To-Many, Unidirectional
  • Many-To-Many, Bidirectional
  • Many-To-Many, Self-referencing

@OneToMany and @ManyToMany associations make use of a Collection interface and its default implementation ArrayCollection that are both defined in the Doctrine\Common\Collections namespace.

Many-to-many associations less common. Frequently you want to associate additional attributes with an association, in which case you introduce an association class. Consequently, the direct many-to-many association disappears and is replaced by one-to-many/many-to-one associations between the 3 participating classes.
Examples:
http://codemonkeys.be/2013/02/example-of-a-doctrine-2-x-many-to-many-association-class/
https://groups.google.com/forum/#!topic/doctrine-user/0dh8lgUudvc
http://stackoverflow.com/questions/3542243/doctrine2-best-way-to-handle-many-to-many-with-extra-columns-in-reference-table

Best practices:

http://doctrine-orm.readthedocs.org/en/latest/reference/best-practices.html
http://doctrine-orm.readthedocs.org/en/latest/reference/improving-performance.html
http://www.uvd.co.uk/blog/some-doctrine-2-best-practices/
http://stackoverflow.com/questions/6794619/doctrine2-best-hydration-mode

One Reply to “Doctrine ORM 2 – Part 1”

  1. I would like to know how can use the method findBy with a criteria(array) which requires to have a condition for a field that needs to be greater than a specific value

Post a Comment