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

Doctrine
By Margaret Staples
Doctrine
- 3,505