Marco Pivetta Marco Pivetta

Marco Pivetta Ocramius

Doctrine Project octrine team

Zend Framework 2 contributor

Ocramius on Github Ocramius on Twitter Ocramius

Current projects

ProxyManager, BjyAuthorize, AssetManager, ZeffMu, ZfrRest, OcraDiCompiler, OcraServiceManager, OcraCachedViewResolver, DoctrineModule, DoctrineORMModule, DoctrineMongoODMModule, VersionEyeModule

WHY?

For the glory of satan, of course!

Doctrine 2 & ZF2

Doctrine Project + Zend Framework 2

Doctrine Project

An incubator for persistence-oriented libraries

asm89 avalanche123 beberlei davidino dbu FabioBatSilva fabpot golovanov guilhermeblanco jmikola jwage kore kriswallsmith lsmith77 naderman nrk Ocramius odino richardfullmer rndstr romanb schmittjoh Seldaek stof

What is Doctrine ORM?

Doctrine ORM is an Object Relational Mapper

It is inspired by Hibernate and the JPA (JSR-317)

It is based on a DBAL (DataBase Abstraction Layer)

Allows developers to save and load POPO with SQL

An ORM gives you the impression that you are working with a "virtual" graph composed by objects...

... backed by any persistence layer!

Simpler:

Forget the database!

Doctrine works with simple objects (POPO)

Objects with an identifier are defined as Entities

namespace Application\Entity;
use Doctrine\ORM\Mapping as ORM;

/** @ORM\Entity */
class User {
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    protected $id;

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

    // getters/setters
}

Everything OK so far?

php public/index.php orm:validate-schema

Validate schema mappings

Generate the database

php public/index.php orm:schema-tool:create

generating the database

$objectManager = \Doctrine\ORM\EntityManager::create(/* yadda */);
$objectManager = \Doctrine\ORM\EntityManager::create(/* yadda */);

$user = new \Application\Entity\User();
$user->setName('Zaphod Beeblebrox');

$objectManager->persist($user);
$objectManager->flush();

var_dump($user->getId());

ಠ_ಠ

Persisting an object

$user = new \Application\Entity\User();
$user->setName('Arthur Dent');

$objectManager->persist($user); // $user is now "managed"
$objectManager->flush();        // commit changes to db

var_dump($user->getId()); // 1

Loading an object

$ford = $objectManager
    ->getRepository('Application\Entity\User')
    ->findOneBy(array('name' => 'Ford Prefect'));

Updating an object

$marvin = $objectManager
    ->getRepository('Application\Entity\User')
    ->findOneBy(array('name' => 'Marvin'));

$marvin->setName('Marvin the Paranoid Android');

$objectManager->flush();

Deleting an object

$president = $objectManager
    ->getRepository('Application\Entity\User')
    ->findOneBy(array('name' => 'Zaphod Beeblebrox'));

$objectManager->remove($president);
$objectManager->flush();

Associations

namespace Application\Entity;
use Doctrine\ORM\Mapping as ORM;

/** @ORM\Entity */
class Address {
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    protected $id;

    /** @ORM\Column(type="string") */
    protected $country;

    // getters/setters
}
class User {
    // ...

    /** @ORM\ManyToOne(targetEntity="Address") */
    protected $address;

    // ...
}
$user = new User();
$user->setName('Arthur Dent');
$objectManager->persist($user);

$address = new Address();
$address->setCountry('UK');
$objectManager->persist($address);

$user->setAddress($address);
$objectManager->flush();
$user = $objectManager
    ->getRepository('Application\Entity\User')
    ->findOneBy(array('name' => 'Arthur Dent'));

echo $user->getAddress()->getCountry(); // UK
class User {
    // ...

    /** @ORM\ManyToMany(targetEntity="User") */
    protected $friends;

    public function __construct() {
        $this->friends = new \Doctrine\Common\Collections\ArrayCollection();
    }

    // ...
}
$arthur = new User();
$arthur->setName('Arthur Dent');
$objectManager->persist($arthur);

$trillian = new User();
$trillian->setName('Tricia Marie McMillan');
$objectManager->persist($trillian);

$zaphod = new User();
$zaphod->setName('Zaphod Beeblebrox');
$objectManager->persist($zaphod);

$arthur->getFriends()->add($trillian);
$trillian->getFriends()->add($zaphod);

$objectManager->flush();
$arthur = $objectManager
    ->getRepository('Application\Entity\User')
    ->findOneBy(array('name' => 'Arthur Dent'));

foreach ($arthur->getFriends() as $friend) {
    echo $friend->getName() . "\n";
}

Note: Trillian doesn't see Arthur as a friend

Note2: Zaphod doesn't see Trillian as a friend

Doctrine ORM + Zend Framework 2

The Modules!

DoctrineModule
basic common functionality

DoctrineORMModule
ORM/SQL Connection

DoctrineMongoODMModule
ODM/MongoDB Connection

Installation!

php composer.phar require doctrine/doctrine-orm-module:0.7.*
Wait a bit
Doctrine ORM Module successfully installed!
php composer.phar require zendframework/zend-developer-tools:dev-master
Wait a bit more

Enabling the modules

config/application.config.php
return [
    'modules' => [
        'ZendDeveloperTools',
        'DoctrineModule',
        'DoctrineORMModule',
        'Application',
    ],
    // [...]
];

Configure mappings

module/Application/config/module.config.php
return [
    'doctrine' => [
        'driver' => [
            'application_entities' => [
                'class' =>'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
                'cache' => 'array',
                'paths' => [__DIR__ . '/../src/Application/Entity']
            ],

            'orm_default' => [
                'drivers' => [
                    'Application\Entity' => 'application_entities'
                ]
]]], // [...]

Configure connection

config/autoload/doctrine.local.php
return [
    'doctrine' => [
        'connection' => [
            'orm_default' => [
            'driverClass' =>'Doctrine\DBAL\Driver\PDOMySql\Driver',
            'params' => [
                'host'     => 'localhost',
                'port'     => '3306',
                'user'     => 'username',
                'password' => 'password',
                'dbname'   => 'database',
]]]]];

Test it!

module/Application/src/Application/Controller/IndexController.php
public function indexAction() {
    $objectManager = $this
        ->getServiceLocator()
        ->get('Doctrine\ORM\EntityManager');

    $user = new \Application\Entity\User();
    $user->setFullName('Slartibarfast');

    $objectManager->persist($user);
    $objectManager->flush();

    die(var_dump($user->getId()));
}

DoctrineModule
goodies

EER UML model

See what your entities look like in a graph:

Example EER UML diagram generated by DoctrineORMModule

Paginator Adapter

use Doctrine\Common\Collections\ArrayCollection;
use DoctrineModule\Paginator\Adapter\Collection as Adapter;
use Zend\Paginator\Paginator;

// Create a Doctrine Collection
$collection = new ArrayCollection(range(1, 101));

// Create the paginator itself
$paginator = new Paginator(new Adapter($collection));

$paginator
    ->setCurrentPageNumber(1)
    ->setItemCountPerPage(5);

ORM Paginator Adapter

use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator;
use Doctrine\ORM\Tools\Pagination\Paginator as ORMPaginator;
use Zend\Paginator\Paginator;

// Create a Doctrine Collection
$query = $em->createQuery('SELECT u FROM User u JOIN u.friends f');

// Create the paginator itself
$paginator = new Paginator(
    new DoctrinePaginator(new ORMPaginator($query))
);

$paginator
    ->setCurrentPageNumber(1)
    ->setItemCountPerPage(5);

Object-Exists Validator

$repository = $objectManager
    ->getRepository('Application\Entity\User');

$validator = new \DoctrineModule\Validator\ObjectExists([
    'object_repository' => $repository,
    'fields' => ['email'],
]);

var_dump($validator->isValid('test@example.com'));
var_dump($validator->isValid(['email' => 'test@example.com']));

Cache bridges

$zendCache = new \Zend\Cache\Storage\Adapter\Memory();

$cache = new \DoctrineModule\Cache\ZendStorageCache($zendCache);
$doctrineCache = new \Doctrine\Common\Cache\ArrayCache();
$options = new \Zend\Cache\Storage\Adapter\AdapterOptions();

$cache = new \DoctrineModule\Cache\DoctrineCacheStorage(
    $options,
    $doctrineCache
);

Hydrator

use DoctrineModule\Stdlib\Hydrator\DoctrineObject;

$hydrator = new DoctrineObject($objectManager, 'Application\Entity\City');

$city = new City();
$data = array('name' => 'Béziers');

$city = $hydrator->hydrate($data, $city);

echo $city->getName(); // prints "Béziers"

$dataArray = $hydrator->extract($city);
echo $dataArray['name']; // prints "Béziers"

Form Elements

$form->add([
    'type' => 'DoctrineModule\Form\Element\ObjectSelect',
    'name' => 'user',
    'options' => [
        'object_manager' => $objectManager,
        'target_class'   => 'Module\Entity\User',
        'property'       => 'fullName',
        'is_method'      => true,
        'find_method'    => [
            'name'   => 'findBy',
            'params' => array(
                'criteria' => ['active' => 1],
                'orderBy'  => ['lastName' => 'ASC'],
            ],
        ],
    ],
]);

More stuff!

Works with MongoDB ODM too!

CouchDB ODM / PHPCR ODM / OrientDB ODM

Good Practices

Entities are simple

Data, keep them simple!

No logic, just simple checks

Only aware only of themselves + associations

doctrine/common API

If you stick with using only doctrine/common API, you can switch between
ORM / MongoDB ODM / CouchDB ODM / PHPCR ODM / OrientDB ODM

Instead of

Doctrine\ORM\EntityManager

use

Doctrine\Common\Persistence\ObjectManager

Instead of

Doctrine\ORM\EntityRepository

use

Doctrine\Common\Persistence\ObjectRepository

Collections

Doctrine comes with a powerful collections API

OOP API for array-like data structures

Criteria API

Collections provide a Criteria API

Abstracts the problem of "searching"

Filter virtually any kind of data structure

Criteria API example

use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ArrayCollection;

$collection = new ArrayCollection(array($user1, $user2, $user3));
$criteria   = new Criteria();

$criteria->andWhere(
    $criteria->expr()->gt(
        'lastLogin',
        new \DateTime('-1 day')
    )
);

$recentVisitors = $collection->matching($criteria);
$recentVisitors = $em
    ->getRepository('Application\Entity\Users')
    ->matching($criteria);

Works in ORM Repositories, Collections, etc...

Same criteria for different persistence layers (ORM, ODM, Memory, ElasticSearch, cache...)

Criteria objects make code readable!

Allows you to define your own RecentUsersCriteria or InactiveUsersCriteria...

$frequentVisitors = $em
        ->getRepository('Application\Entity\Users')
        ->matching(new FrequentVisitorsCriteria());

Inject Object Manager

Use Dependency Injection, luke!

class MyService {
    public function __construct(ObjectManager $objectManager) {
    // [...]
    }
}
'factories' => [
    'my_service' => function ($serviceLocator) {
        $objectManager = $serviceLocator->get('Doctrine\ORM\EntityManager');
        return new MyService($objectManager);
    }
],

Keep Object Manager out of Controllers

If you inject ObjectManager in your controllers, you're gonna have a bad architecture

Don't use persistence to solve service layer problems

Filtering data when saved to DB

Validating data when saved to DB

Saving files when records are saved to DB

Using DB-level errors to check input validity

Keep your object graph valid

An ObjectManager works under the assumption that managed objects are valid!

Assign values to your entities only when data is valid!

Questions?

Fork it!

Doctrine2 ZF2 Introduction on Github Ocramius/doctrine2-zf2-introduction

Thanks!