Enterprise Database Design      Patterns in PHP     Hugo Hamon – OSIDays 2011
By Martin Fowler§    Table Module§    Transaction Script§    Row Data Gateway§    Table Data Gateway§    Active Recor...
Table Data Gateway
« An object that acts as a Gateway toa database table. One instancehandles all the rows in the table. »                   ...
Same asData Access Object                     Martin Fowler	  
CRUD
$table = new OrderGateway(new Connection(...));$table->insert(XX123456789, 358.80, unpaid);$table->update(42, XX123456789,...
class OrderGateway{    private $conn;    public function __construct(Connection $conn)    {        $this->conn = $conn;   ...
class OrderGateway{    public function insert($reference, $amount, $status)    {        $query = INSERT INTO orders (refer...
class OrderGateway{    public function update($pk, $ref, $amount, $status)    {        $query = UPDATE orders SET referenc...
class OrderGateway{    public function delete($pk)    {        return $this->conn->executeQuery(            DELETE FROM or...
Finders
$orders = $table->findAll();$orders = $table->findPaidOrders();$orders = $table->findUnpaidOrders();$orders = $table->find...
class OrderGateway{    public function findAll()    {        $query = SELECT * FROM orders;        return $this->conn->fet...
public function findBy(array $criteria){    $where = array();    foreach ($criteria as $field => $value) {        $where[]...
public function findPaidOrders(){    return $this->findBy(array(status => paid));}public function findUnpaidOrders(){    r...
When to use it?
Row Data Gateway
« An object that acts as a Gateway toa single record in a data source. Thereis one instance per row. »                    ...
CRUD
class Order{    private   $id;    private   $reference;    private   $amount;    private   $vat;    private   $total;    p...
$conn = new Connection(...);$order = new OrderGateway();$order->setReference(XX12345678);$order->setAmount(300.00);$order-...
class OrderGateway{    public function insert(Connection $conn)    {        $query = INSERT INTO orders (reference, amount...
Finders
OrderFinder::setConnection($conn);$order = OrderFinder::findByReference(XX12345678);echo sprintf(%01.2f euros, $order->get...
class OrderFinder{    static public function findByReference($reference)    {       $query = SELECT * FROM orders WHERE re...
class OrderGateway{    static public function load(array $rs)    {        $order = new OrderGateway($rs[id]);        $orde...
OrderFinder::setConnection($conn);$orders = OrderFinder::findMostExpensiveOrders(10);foreach ($orders as $order) {    echo...
class OrderFinder{    static public function findMostExpensiveOrders($limit)    {        $orders = array();        $query ...
When to use it?
Active Record
« An object that wraps a row in adatabase table or view, encapsulatesthe database access, and addsdomain logic on that dat...
Active Record                =Row Data Gateway + Business Logic
Active Record       =Data + Behaviors
Active Record          =Properties + Methods
class Order{    private   $id;    private   $reference;    private   $amount;    private   $vat;    private   $vatRate;   ...
class Order{    public function __construct($id = null)    {        if (null !== $id) {            $this->id = $id;       ...
$conn = new Connection(...);$order = new Order();$order->setReference(XX12345678);$order->setAmount(300.00);$order->setVat...
class Order{    public function applyDiscount($discount)    {        $this->amount -= $discount;    }    public function u...
class Order{    public function isPaid()    {        return $this->isPaid;    }    public function setPaid()    {         ...
class Order{    public function isReadyForShipment()    {        return $this->isPaid() && complete == $this->status;    }...
class OrderController{    public function confirmAction($reference)    {        $conn = $this->getDatabaseConnection();   ...
Refactoring
abstract class ActiveRecord{    protected $fields = array();    abstract public function getTableName();    public functio...
class Order extends ActiveRecord{    private $amount;    abstract public function getTableName()    {        return tbl_or...
When to use it?
Data Mapper
« A layer of Mappers that moves databetween objects and a databasewhile keeping them independent ofeach other and the mapp...
« Man in the Middle »
http://martinfowler.com	  
class OrderMapper{    private $conn;    public function __construct(Connection $conn) {        $this->conn = $conn;    }  ...
$order = new Order();$order->setReference(XX12345678);$order->setAmount(300.00);$order->setVatRate(0.196);$order->updateTo...
class OrderMapper{    public function findAll()    {        $objects = array();        $query = SELECT id, reference, vat ...
class OrderMapper{    public function find($pk)    {        $query = SELECT id, vat ... FROM orders WHERE id = ?;        $...
$conn = new Connection(mysql:host=localhost ...);$mapper = new OrderMapper($conn);$order = $mapper->find(42);$order->setAm...
Unit testing
class OrderTest extends PHPUnit_Framework_TestCase{    public function testUpdateTotal()    {        $order = new Order();...
When to use it?
IdentityMap
« Ensures that each object getsloaded only once by keeping everyloaded object in a map. »                              Mar...
$conn = new Connection(mysql:host=localhost ...);$mapper = new OrderMapper($conn);$orderA = $mapper->find(42);$orderB = $m...
The solution
class IdentityMap implements IdentityMapInterface{    private $entities;    public function fetch($class, $pk)    {       ...
class IdentityMap implements IdentityMapInterface{    public function store(ValueObjectInterface $entity)    {        $key...
class Order   implements ValueObjectInterface{    private   $id;    private   $reference;    private   $amount;    // ... ...
class OrderMapper extends DatabaseMapper{    private $map;    public function __construct(IdentityMap $map, ...)    {     ...
class OrderMapper extends DatabaseMapper{    public function store(Order $order)    {        parent::store($order);       ...
class OrderMapper extends DatabaseMapper{    public function find($pk)    {        if (false !== $object = $this->map->fet...
$conn = new Connection(mysql:host=localhost ...);$mapper = new OrderMapper(new IdentityMap(), $conn);$orderA = $mapper->fi...
Query Object
$query = Query::create()  ->select(array(id, reference, amount, status))  ->from(orders)  ->where(Criteria::equals(status,...
class Criteria{    private $field;    private $operator;    private $parameters;    public function __construct($field, $o...
class Criteria{  static public function equal($field, $value, $vars)  {    return new Criteria($field, =, $vars);  }    st...
Custom Queries
class OrderQuery extends Query{    public function filterByPriority($amount)    {        return $this            ->where(C...
$query = OrderQuery::create()    ->filterByPriority(2000)    ->filterByReference(%567%)    ->orderByAmount(DESC)    ->getS...
ORM Tools
Zend_DB            Propel    Doctrine 2.xPomm        Doctrine 1.2
QuizzCan you guess the patterns?
$table = new Author();// New empty row$row = $table->createRow();// Insert a new row$row->firstName = Jules;$row->lastName...
$pax1 = new Passenger(Hugo Hamon, 7B);$pax2 = new Passenger(John Smith, 3A);$aircraft = new Plane();$aircraft->setCapacity...
$post = new BlogPost();$post->setTitle(My First Blog Post);$post->setBody(Some content...);$author = new Author();$author-...
$data = array(    first_name => Jules,    last_name => Vernes,);$table = new AuthorTable();$table->insert($data);
Conclusion
By Martin Fowler§    Table Module§    Transaction Script§    Row Data Gateway§    Table Data Gateway§    Active Recor...
Ques&ons?	   92-98, boulevard Victor Hugo 92 115 Clichy Cedex trainings@sensio.com (+33 (0)1 40 99 82 11) sensiolabs.com -...
Database Design Patterns
Upcoming SlideShare
Loading in...5
×

Database Design Patterns

6,876

Published on

This session introduces most well known design patterns to build PHP classes and objects that need to store and fetch data from a relational databases. The session will describe the difference between of the Active Record, the Table and Row Data Gateway and the Data Mapper pattern. We will also examine some technical advantages and drawbacks of these implementations. This talk will expose some of the best PHP tools, which ease database interactions and are built on top of these patterns.

Published in: Technology, Business

Database Design Patterns

  1. 1. Enterprise Database Design Patterns in PHP Hugo Hamon – OSIDays 2011
  2. 2. By Martin Fowler§  Table Module§  Transaction Script§  Row Data Gateway§  Table Data Gateway§  Active Record§  Data Mapper§  Unit of Work§  Identity Map§  Data Transfer Object§  …
  3. 3. Table Data Gateway
  4. 4. « An object that acts as a Gateway toa database table. One instancehandles all the rows in the table. » Martin Fowler  
  5. 5. Same asData Access Object Martin Fowler  
  6. 6. CRUD
  7. 7. $table = new OrderGateway(new Connection(...));$table->insert(XX123456789, 358.80, unpaid);$table->update(42, XX123456789, 358.80, paid);$table->delete(42);
  8. 8. class OrderGateway{ private $conn; public function __construct(Connection $conn) { $this->conn = $conn; }}
  9. 9. class OrderGateway{ public function insert($reference, $amount, $status) { $query = INSERT INTO orders (reference, amount,status) VALUES (?, ?, ?); $data = array($reference, $amount, $status); $this->conn->executeQuery($query, $data); return $this->conn->lastInsertId(); }}
  10. 10. class OrderGateway{ public function update($pk, $ref, $amount, $status) { $query = UPDATE orders SET reference = ?, amount= ?, status = ? WHERE id = ?; $data = array($ref, $amount, $status, $pk); return $this->conn->executeQuery($query, $data); }}
  11. 11. class OrderGateway{ public function delete($pk) { return $this->conn->executeQuery( DELETE FROM orders WHERE id = ?, array($pk) ); }}
  12. 12. Finders
  13. 13. $orders = $table->findAll();$orders = $table->findPaidOrders();$orders = $table->findUnpaidOrders();$orders = $table->findBy(array( status => paid, amount => 250.00));$order = $table->find(42);$order = $table->findOneBy(array(reference => ...));
  14. 14. class OrderGateway{ public function findAll() { $query = SELECT * FROM orders; return $this->conn->fetchAll($query); } public function find($pk) { $rs = $this->conn->findBy(array(id => $pk)); return 1 === count($rs) ? $rs[0] : false; }}
  15. 15. public function findBy(array $criteria){ $where = array(); foreach ($criteria as $field => $value) { $where[] = sprintf(%s = ?); } $q = sprintf( SELECT * FROM orders WHERE %s, implode( AND , $where) ); return $this->conn->fetchAll($q, array_values($criteria));}
  16. 16. public function findPaidOrders(){ return $this->findBy(array(status => paid));}public function findUnpaidOrders(){ return $this->findBy(array(status => unpaid));}
  17. 17. When to use it?
  18. 18. Row Data Gateway
  19. 19. « An object that acts as a Gateway toa single record in a data source. Thereis one instance per row. » Martin Fowler  
  20. 20. CRUD
  21. 21. class Order{ private $id; private $reference; private $amount; private $vat; private $total; private $createdAt; // Getters and setters for each property // ...}
  22. 22. $conn = new Connection(...);$order = new OrderGateway();$order->setReference(XX12345678);$order->setAmount(300.00);$order->setVat(58.80);$order->setTotal(358.80);$order->setCreatedAt(new DateTime());$order->insert($conn);
  23. 23. class OrderGateway{ public function insert(Connection $conn) { $query = INSERT INTO orders (reference, amount, vat,total, created_at) VALUES (?, ?, ?, ?, ?); $data = array( $this->reference, $this->amount, $this->vat, $this->total, $this->createdAt->format(Y-m-d H:i:s) ); $conn->executeQuery($query, $data); $this->id = $conn->lastInsertId(); }}
  24. 24. Finders
  25. 25. OrderFinder::setConnection($conn);$order = OrderFinder::findByReference(XX12345678);echo sprintf(%01.2f euros, $order->getTotal());
  26. 26. class OrderFinder{ static public function findByReference($reference) { $query = SELECT * FROM orders WHERE reference = ?; $rs = static::getConnection() ->fetchSingle($query, array($reference)) ; return $rs ? OrderGateway::load($rs) : false; }}
  27. 27. class OrderGateway{ static public function load(array $rs) { $order = new OrderGateway($rs[id]); $order->setReference($rs[reference]); $order->setAmount($rs[amount]); $order->setVat($rs[vat]); $order->setTotal($rs[total]); $order->setCreatedAt(new DateTime($rs[created_at])); return $order; }}
  28. 28. OrderFinder::setConnection($conn);$orders = OrderFinder::findMostExpensiveOrders(10);foreach ($orders as $order) { echo $order->getReference(), "n"; echo sprintf(%01.2f euros, $order->getTotal()), "n"; echo "n-----n";}
  29. 29. class OrderFinder{ static public function findMostExpensiveOrders($limit) { $orders = array(); $query = SELECT * FROM orders ORDER BY total DESC LIMIT ?; $rs = static::getConnection()->fetchAll($query, array($limit)); foreach ($rs as $data) { $orders[] = OrderGateway::load($data); } return $orders; }}
  30. 30. When to use it?
  31. 31. Active Record
  32. 32. « An object that wraps a row in adatabase table or view, encapsulatesthe database access, and addsdomain logic on that data. » Martin Fowler  
  33. 33. Active Record =Row Data Gateway + Business Logic
  34. 34. Active Record =Data + Behaviors
  35. 35. Active Record =Properties + Methods
  36. 36. class Order{ private $id; private $reference; private $amount; private $vat; private $vatRate; private $total; private $createdAt; private $status; private $isPaid; // Getters and setters for each property // ...}
  37. 37. class Order{ public function __construct($id = null) { if (null !== $id) { $this->id = $id; } $this->vatRate = 0.00; $this->vat = 0.00; $this->amount = 0.00; $this->total = 0.00; $this->isPaid = false; $this->status = processing; $this->createdAt = new DateTime(); }}
  38. 38. $conn = new Connection(...);$order = new Order();$order->setReference(XX12345678);$order->setAmount(300.00);$order->setVatRate(0.196);$order->applyDiscount(20.00);$order->updateTotal();$order->save($conn);
  39. 39. class Order{ public function applyDiscount($discount) { $this->amount -= $discount; } public function updateTotal() { if ($this->vatRate) { $this->vat = $this->amount * $this->vatRate; } $this->total = $this->amount + $this->vat; }}
  40. 40. class Order{ public function isPaid() { return $this->isPaid; } public function setPaid() { $this->isPaid = true; }}
  41. 41. class Order{ public function isReadyForShipment() { return $this->isPaid() && complete == $this->status; } public function ship($address) { $this->doShipment($address); $this->status = shipped; }}
  42. 42. class OrderController{ public function confirmAction($reference) { $conn = $this->getDatabaseConnection(); $order = ...; $order->setPaid(); $order->save($conn); if ($order->isReadyForShipment()) { $order->ship(); return $this->view->render(ship.php, array(order => $order)); } return $this->view->render(pending.php, array(order => $order)); }}
  43. 43. Refactoring
  44. 44. abstract class ActiveRecord{ protected $fields = array(); abstract public function getTableName(); public function save(Connection $conn) { // insert or update $fields in the database } public function delete(Connection $conn) { // delete the object from the database }}
  45. 45. class Order extends ActiveRecord{ private $amount; abstract public function getTableName() { return tbl_orders; } public function setAmount($amount) { $this->amount = $amount; $this->fields[amount] = $amount; }}
  46. 46. When to use it?
  47. 47. Data Mapper
  48. 48. « A layer of Mappers that moves databetween objects and a databasewhile keeping them independent ofeach other and the mapper itself. » Martin Fowler  
  49. 49. « Man in the Middle »
  50. 50. http://martinfowler.com  
  51. 51. class OrderMapper{ private $conn; public function __construct(Connection $conn) { $this->conn = $conn; } public function store(Order $order) { // Execute the query to persist the object to the DB } public function remove(Order $order) { // Executes the query to remove the object to the DB }}
  52. 52. $order = new Order();$order->setReference(XX12345678);$order->setAmount(300.00);$order->setVatRate(0.196);$order->updateTotal();$conn = new Connection(mysql:host=localhost ...);$mapper = new OrderMapper($conn);$mapper->store($order);
  53. 53. class OrderMapper{ public function findAll() { $objects = array(); $query = SELECT id, reference, vat ... FROM orders; foreach ($this->conn->fetchAll($query) as $data) { $object = new Order($data[id]); $object->load($data); $objects[] = $object; } return $objects; }}
  54. 54. class OrderMapper{ public function find($pk) { $query = SELECT id, vat ... FROM orders WHERE id = ?; $object = false; if (false !== $data = conn->fetch($query, array($pk))) { $object = new Order($data[id]); $object->load($data); } return $object; }}
  55. 55. $conn = new Connection(mysql:host=localhost ...);$mapper = new OrderMapper($conn);$order = $mapper->find(42);$order->setAmount(399.00);$order->updateTotal();$mapper->store($order);
  56. 56. Unit testing
  57. 57. class OrderTest extends PHPUnit_Framework_TestCase{ public function testUpdateTotal() { $order = new Order(); $order->setAmount(299.00); $order->setVatRate(0.196); $order->updateTotal(); $this->assertEquals(58.60, $order->getVat()); $this->assertEquals(357.60, $order->getTotal()); }}
  58. 58. When to use it?
  59. 59. IdentityMap
  60. 60. « Ensures that each object getsloaded only once by keeping everyloaded object in a map. » Martin Fowler  
  61. 61. $conn = new Connection(mysql:host=localhost ...);$mapper = new OrderMapper($conn);$orderA = $mapper->find(42);$orderB = $mapper->find(42);$orderC = $mapper->find(42);// 3 SQL queries for getting the same object
  62. 62. The solution
  63. 63. class IdentityMap implements IdentityMapInterface{ private $entities; public function fetch($class, $pk) { $key = $this->getKey($class, $pk); if (isset($this->entities[$key])) { return $this->entities[$key]; } return false; }}
  64. 64. class IdentityMap implements IdentityMapInterface{ public function store(ValueObjectInterface $entity) { $key = $this->getKey($class, $entity->getId()); $this->entities[$key] = $entity; } private function getKey($class, $pk) { return $class.-.$pk; }}
  65. 65. class Order implements ValueObjectInterface{ private $id; private $reference; private $amount; // ... public function getId() { return $this->id; }}
  66. 66. class OrderMapper extends DatabaseMapper{ private $map; public function __construct(IdentityMap $map, ...) { parent::__construct($conn); $this->map = $map; }}
  67. 67. class OrderMapper extends DatabaseMapper{ public function store(Order $order) { parent::store($order); $this->map->store(Order, $object); }}
  68. 68. class OrderMapper extends DatabaseMapper{ public function find($pk) { if (false !== $object = $this->map->fetch($pk)) { return $object; } if (false !== $object = parent::find($pk)) { $this->map->store(Order, $object); } return $object; }}
  69. 69. $conn = new Connection(mysql:host=localhost ...);$mapper = new OrderMapper(new IdentityMap(), $conn);$orderA = $mapper->find(42); // Query$orderB = $mapper->find(42); // No query$orderB->setAmount(299.00);$orderB->setVatRate(0.196);$orderB->updateTotal();$mapper->store($orderB);$orderC = $mapper->find(42); // No query
  70. 70. Query Object
  71. 71. $query = Query::create() ->select(array(id, reference, amount, status)) ->from(orders) ->where(Criteria::equals(status, paid)) ->where(Criteria::greaterThan(amount, 2000)) ->where(Criteria::like(reference, XX123%)) ->orderBy(amount, desc) ->getSql();// SELECT id, reference, amount, status// WHERE status = ? AND amount > ? AND reference LIKE ?// ORDER BY amount DESC
  72. 72. class Criteria{ private $field; private $operator; private $parameters; public function __construct($field, $operator, $value) { $this->field = $field; $this->operator = $operator; $this->parameters[] = $value; }}
  73. 73. class Criteria{ static public function equal($field, $value, $vars) { return new Criteria($field, =, $vars); } static public function notEqual($field, $value, $vars) { return new Criteria($field, <>, $vars); }}
  74. 74. Custom Queries
  75. 75. class OrderQuery extends Query{ public function filterByPriority($amount) { return $this ->where(Criteria::equal(status, paid)) ->where(Criteria::greaterThan(amount, $amount)) ; } public function filterByReference($like) { return $this->where(Criteria::like(reference, $like)); }}
  76. 76. $query = OrderQuery::create() ->filterByPriority(2000) ->filterByReference(%567%) ->orderByAmount(DESC) ->getSql();
  77. 77. ORM Tools
  78. 78. Zend_DB Propel Doctrine 2.xPomm Doctrine 1.2
  79. 79. QuizzCan you guess the patterns?
  80. 80. $table = new Author();// New empty row$row = $table->createRow();// Insert a new row$row->firstName = Jules;$row->lastName = Verne;$row->save();
  81. 81. $pax1 = new Passenger(Hugo Hamon, 7B);$pax2 = new Passenger(John Smith, 3A);$aircraft = new Plane();$aircraft->setCapacity(120);$aircraft->addPassenger($pax1);$aircraft->addPassenger($pax2);$aircraft->save();$pax2->changeSeat(2C);$pax2->save();$aircraft->isAvailableSeat(3A) ? Yes : No;
  82. 82. $post = new BlogPost();$post->setTitle(My First Blog Post);$post->setBody(Some content...);$author = new Author();$author->setName(Hugo Hamon);$author->addPost($post);$em->persist($user);$em->persist($post);$em->flush();
  83. 83. $data = array( first_name => Jules, last_name => Vernes,);$table = new AuthorTable();$table->insert($data);
  84. 84. Conclusion
  85. 85. By Martin Fowler§  Table Module§  Transaction Script§  Row Data Gateway§  Table Data Gateway§  Active Record§  Data Mapper§  Unit of Work§  Identity Map§  Data Transfer Object§  …
  86. 86. Ques&ons?   92-98, boulevard Victor Hugo 92 115 Clichy Cedex trainings@sensio.com (+33 (0)1 40 99 82 11) sensiolabs.com - symfony.com – trainings.sensiolabs.com
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×