Doctrine ORM

What's This Then?

with Margaret Staples

Hello, Nice to Meet You

I'm Margaret

Hello, Nice to Meet You

I'm Margaret

I have been working with Doctrine

mostly as part of the Symfony2 framework

for about 4 years

Hello, Nice to Meet You

I'm Margaret

I'm developing a Social Strategy City-Builder RPG game of awesomeness.

brunegame.com

Doctrine ORM

Topics

1. Overview

2. Installation

3. Creating Structure

4. Basic Interactions

5. Complex Queries

6. Custom Repositories

7. Lifecycle Events

8. Questions

Doctrine ORM

Overview

"Object Relational Mapping"

Doctrine ORM

Overview

"Object Relational Mapping"

How your app will think about stored information.

Doctrine ORM

Overview

"Object Relational Mapping"

How your app will think about stored information.

Reasons with a database on behalf of your app.

Doctrine ORM

Overview

"Object Relational Mapping"

How your app will think about stored information.

Reasons with a database on behalf of your app.

Part of Symfony Standard Edition

Installation

Local Webserver

Linux + Apache + MySQL + PHP

Installation

Composer

Dependency Manager for PHP

getcomposer.org

Installation

Option #1: Install Symfony Standard Edition

​> php composer.phar

create-project

symfony/framework-standard-edition

/path/to/webroot/NewProject

Installation

Option #1: Install Symfony Standard Edition

 Install the demo bundle?
 database_driver (pdo_mysql)?
 database_host (127.0.0.1)?
 database_port (null)?
 database_name (symfony)?
 database_user (root)?
 database_password (null)?

Installation

Option #2: Install Doctrine Alone

​> php composer.phar

require

doctrine/orm:*

Include Composer's Autoloader (relative to project root).

require_once "vendor/autoload.php";

Installation

Option #2: Install Doctrine Alone

Setup your Entity Manager: bootstrap.php

use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
 
$paths = ["/path/to/entity-files"];
$isDevMode = false;
 
// the connection configuration
$dbParams = array(
    'driver'   => 'pdo_mysql',
    'user'     => 'root',
    'password' => '',
    'dbname'   => 'foo',
);
 
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);

Installation

Option #2: Install Doctrine Alone

Setup the command line tool: cli-config.php

use Doctrine\ORM\Tools\Console\ConsoleRunner;
 
require_once 'bootstrap.php';
 
$entityManager = GetEntityManager();
 
return ConsoleRunner::createHelperSet($entityManager);

Installation

Option #2: Install Doctrine Alone

doctrine-orm.readthedocs.org

Creating Structure

Entity

Describes the Structure of a database table.

Holds methods for interacting with data from records in that table.

Creating Structure

Entity: Annotations: Simple

use Doctrine\ORM\Mapping as ORM;
/**
 * @ORM\Entity
 * @ORM\Table(name="item")
 */
class Item
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

Creating Structure

Entity: Annotations: Simple

/**
 * @ORM\Column(type="string", length=64)
 */
protected $name;
/**
 * @ORM\Column(type="decimal", scale=2)
 */
protected $value;
/**
 * @ORM\Column(type="integer", nullable=true)
 */
protected $available;
/**
 * @ORM\Column(type="array")
 */
protected $attributes;

Creating Structure

Entity: Annotations Associations: One to One

// Join one Character to one AdventureClass
// (Unidirectional)
class Character
{
    /**
     * @OneToOne(targetEntity="AdventureClass")
     * @JoinColumn(name="class_id", referencedColumnName="id")
     **/
    private $adventure_class;
}

Creating Structure

Entity: Annotations Associations: One to One

// Join one Character to one Mount
// (Bidirectional 1 of 2)
class Character
{
    /**
     * @OneToOne(targetEntity="Mount", mappedBy="character")
     **/
    private $mount;
}

Creating Structure

Entity: Annotations Associations: One to One

// Join one Character to one Mount
// (Bidirectional 2 of 2)
class Mount
{
    /**
     * @OneToOne(targetEntity="Character", inversedBy="mount")
     * @JoinColumn(name="character_id", referencedColumnName="id")
     **/
    private $character;
}

Creating Structure

Entity: Annotations Associations: One to One

// Join one Character to one Character
// (self referencing)
class Character
{
    /**
     * @OneToOne(targetEntity="Character")
     * @JoinColumn(name="sovereign_id", referencedColumnName="id")
     **/
    private $sovereign;
}

Creating Structure

Entity: Annotations Associations: Many to One

// Join potentially many Character to one Region
// (Unidirectional)
class Character
{
    /**
     * @ManyToOne(targetEntity="Region")
     * @JoinColumn(name="region_id", referencedColumnName="id")
     **/
    private $region;
}

Creating Structure

Entity: Annotations Associations: One to Many

// Join one User to potentially many Characters
// (Bidirectional 1 of 2)
class User
{
    /**
     * @OneToMany(targetEntity="Character", mappedBy="user")
     **/
    private $characters;

    public function __construct() {
        $this->characters = new ArrayCollection();
    }
}

Creating Structure

Entity: Annotations Associations: One to Many

// Join one User to potentially many Characters
// (Bidirectional 2 of 2)
class Character
{
    /**
     * @ManyToOne(targetEntity="User", inversedBy="characters")
     * @JoinColumn(name="user_id", referencedColumnName="id")
     **/
    private $user;
}

Creating Structure

Entity: Console Commands

Update Database Structure based on Entity Annotations

​> php app/console doctrine:schema:update --force

Creating Structure

Entity: Console Commands

Generate Getters / Setters for Entity properties

​> php app/console doctrine:generate:entities 

Creating Structure

Entity: Console Commands

Update Database Structure based on Entity Annotations

​> php vendor/bin/doctrine orm:schema-tool:update --force

Creating Structure

Entity: Console Commands

Generate Getters / Setters for Entity properties

​> php vendor/bin/doctrine orm:generate-entities

Creating Structure

Entity: Generated Getter

class User
{
    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
}

Creating Structure

Entity: Generated Setter

class User
{
    /**
     * Set name
     *
     * @param string $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }
}

Creating Structure

Entity: Modified Setter

class Item
{
    /**
     * Set type
     *
     * @param string $type
     */
    public function setType($type)
    {
        if (!in_array($type,$types) {
            $type = $default_type;
        }
        $this->type = $type;
    }
}

Basic Interactions

Access the Entity Manager

// From a Controller
public function indexAction()
{
    $em = $this->getDoctrine()->getManager();
}

// From a Command
public function myCommand()
{
    $em = $this->getContainer()->get('doctrine')->getManager('default');
}

Basic Interactions

Access the Entity Manager

// From a Service
// Resources/config/services.yml
my_service:
    class:        Bundle\Namespace\Services
    arguments:
        entityManager:      "@doctrine.orm.entity_manager"

// Services/MyService.php
class MyService
{
    public function __construct(EntityManager $entityManager)
    {
        $this->em = $entityManager;
    }
    public function someServiceFunction()
    {
        $em = $this->em;

Basic Interactions

Access the Entity Manager

// bootstrap.php
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
 
require_once "vendor/autoload.php";
 
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration([__DIR__."/src"], $isDevMode);
 
$conn = array(
    'driver' => 'pdo_sqlite',
    'path' => __DIR__ . '/db.sqlite',
);
 
$em = EntityManager::create($conn, $config);

Basic Interactions

Create a New Record

$item = new Item();
$item->setName("Sword");
$item->setType("Weapon");
$item->setAmount(1);
 
$em->persist($item);
$em->flush();

Include a "use" statement to create or type hint entities
Example: use MyProjectPath\Entity\Item;

Basic Interactions

Using Entities in Controllers

$item_repo = $em->getRepository('Item');
 
// Find all items: returns array of item records or an empty array
$all_items = $item_repo->findAll();
 
// Find item by id
$item = $item_repo->findOneById($item_id);
 
// Find items matching parameters
$some_items = $item_repo->findBy(['type' => 'Shield', 'amount' => 10]);

In Symfony2 use the bundle name prefix for repository calls.
Example: getRepository('MyProjectBundle:Item')

Basic Interactions

Using Entities in Controllers

// One item
$name = $item->getName();
$item->setAmount($item->getAmount() + 5);
$em->persist($item);
$em->flush();

// Many items
foreach ($items as $item){
    $name = $item->getName();
    if (in_array($name,$cancelled) {
        $em->remove($item);
    }
}
$em->flush();

Complex Queries

Doctrine Query Language

$prefer = "Fire";
$types = [ 'Weapon', 'Armor', 'Spell' ];
 
$query = $em->createQueryBuilder()
    ->select('c.slot')
    ->from('MyProject:Item', 'c')
    ->where('c.name LIKE :prefer')
    ->andWhere('c.destroyed IS NULL')
    ->andWhere($qb->expr()->in('c.type',':types'))
    ->setParameters(['prefer'=>$prefer,'types' => $types])
    ->distinct()
    ->orderBy("c.value","DESC")
    ->getQuery();
$slots = $query->getResult();

foreach ($slots as $each){
    $slot_name = $each['slot'];
}

Complex Queries

Doctrine Query Language

$prefer = "Fire";
$types = [ 'Weapon', 'Armor', 'Spell' ];
 
$query = $em->createQueryBuilder()
    ->select('c.slot')
    ->from('MyProject:Item', 'c')
...
    ->getQuery();
$slots = $query->getResult();

foreach ($slots as $each){
    $slot_name = $each['slot'];
}

Complex Queries

Doctrine Query Language

$prefer = "Fire";
$types = [ 'Weapon', 'Armor', 'Spell' ];
...
->where('c.name LIKE :prefer')
->andWhere('c.destroyed IS NULL')
->andWhere($qb->expr()->in('c.type',':types'))
->setParameters(['prefer'=>"%$prefer%",'types' => $types])
->distinct()
->orderBy("c.value","DESC")
...

Custom Repos

Add a Custom Repository to an Entity

/**
 * @ORM\Entity
 * @ORM\Table(name="character")
 * @ORM\Entity(repositoryClass="MyProject\Repository\CharacterRepository")
 */
class Character
{
...

Custom Repos

Create the Repository Declared in the Entity

namespace MyProject\Repository;
 
use Doctrine\ORM\EntityRepository;
 
class CharacterRepository extends EntityRepository
{
    // custom functions go here
}

Custom Repos

Custom Repository Functions

class AdventureClassRepository extends EntityRepository
{
    public function getClassIdsByClassType($type)
    {
        $em = $this->getEntityManager();
     
        $query = $em->createQueryBuilder()
            ->select('c.id')
            ->from('MyProject:AdventureClass', 'c')
            ->where('c.type = :type')
            ->setParameters(array('type'=>$type))
            ->getQuery();
     
        return $query->getResult();
    }

Custom Repos

class CharacterRepository extends EntityRepository
{
    public function getTopTenByClassType($type)
    {
        $em = $this->getEntityManager();
    
        $class_repo = $em->getRepository('MyProject:AdventureClass');
    
        $class_ids = $class_repo->getClassIdsByClassType($type);

Custom Repository Functions

Custom Repos

public function getTopTenByClassType($type)
{
    ...
    $query = $em->createQueryBuilder()
        ->select('c')
        ->from('MyProject:Character', 'c')
        ->andWhere('c.inactive IS NULL')
        ->andWhere($qb->expr()->in('c.class_id',':class_ids'))
        ->setParameters(array('class_ids' => $class_ids))
        ->orderBy("c.level","DESC")
        ->setMaxResults(10)
        ->getQuery();

    return $query->getResult();

Custom Repository Functions

Custom Repos

$type = "Ranger";
$char_repo = $em->getRepository('Character');
$top_rangers = $char_repo->getTopTenByClassType($type);

Access Custom Repository Functions

Custom repository functions can be accessed anywhere you have the Entity Manager:

Lifecycle Callbacks

/**
 * @ORM\Entity()
 * @ORM\HasLifecycleCallbacks()
 */
class Character
{

Create Automatic Changes

Callbacks can be set to trigger on entity insert, update, delete, etc

Lifecycle Callbacks

...
 /**
 * @ORM\Column(type="datetime")
 */
protected $updated_on;
 
/**
 * @ORM\PreUpdate
 */
public function setUpdatedOnValue()
{
    $this->updated_on = new \DateTime();
}

Create Automatic Changes

Callbacks can be set to trigger on entity insert, update, delete, etc

Lifecycle Callbacks

...
 /**
 * @ORM\Column(type="datetime")
 */
protected $updated_on;
 
/**
 * @ORM\PreUpdate
 */
public function setUpdatedOnValue()
{
    $this->updated_on = new \DateTime();
}

Create Automatic Changes

Callbacks can be set to trigger on entity insert, update, delete, etc

Lifecycle Callbacks

Create Automatic Changes

Callbacks can be set to trigger on entity insert, update, delete, etc

http://docs.doctrine-project.org/

projects/doctrine-orm/

en/latest/reference/

events.html#lifecycle-events

Questions?

Doctrine ORM

http://slides.com/margaretstaples/deck

https://joind.in/15632

Questions?

Margaret Staples

@dead_lugosi on twitter

deadlugosi on freenode

brunegame.com