YOU ARE DOWNLOADING DOCUMENT

Please tick the box to continue:

Transcript
Page 1: Database Design Patterns
Page 2: Database Design Patterns

Enterprise Database Design Patterns in PHP

Hugo Hamon – OSIDays 2011

Page 3: Database Design Patterns

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 §  …

Page 4: Database Design Patterns

Table Data Gateway

Page 5: Database Design Patterns

« An object that acts as a Gateway to

a database table. One instance

handles all the rows in the table. » Martin Fowler  

Page 6: Database Design Patterns

Same as Data Access Object

Martin Fowler  

Page 7: Database Design Patterns

CRUD

Page 8: Database Design Patterns

$table = new OrderGateway(new Connection('...'));

$table->insert('XX123456789', 358.80, 'unpaid');

$table->update(42, 'XX123456789', 358.80, 'paid');

$table->delete(42);

Page 9: Database Design Patterns

class OrderGateway

{

private $conn;

public function __construct(Connection $conn)

{

$this->conn = $conn;

}

}

Page 10: Database Design Patterns

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();

}

}

Page 11: Database Design Patterns

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);

}

}

Page 12: Database Design Patterns

class OrderGateway

{

public function delete($pk)

{

return $this->conn->executeQuery(

'DELETE FROM orders WHERE id = ?',

array($pk)

);

}

}

Page 13: Database Design Patterns

Finders

Page 14: Database Design Patterns

$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' => '...'));

Page 15: Database Design Patterns

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;

}

}

Page 16: Database Design Patterns

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));

}

Page 17: Database Design Patterns

public function findPaidOrders()

{

return $this->findBy(array('status' => 'paid'));

}

public function findUnpaidOrders()

{

return $this->findBy(array('status' => 'unpaid'));

}

Page 18: Database Design Patterns

When to use it?

Page 19: Database Design Patterns

Row Data Gateway

Page 20: Database Design Patterns

« An object that acts as a Gateway to

a single record in a data source. There

is one instance per row. » Martin Fowler  

Page 21: Database Design Patterns

CRUD

Page 22: Database Design Patterns

class Order

{

private $id;

private $reference;

private $amount;

private $vat;

private $total;

private $createdAt;

// Getters and setters for each property

// ...

}

Page 23: Database Design Patterns

$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);

Page 24: Database Design Patterns

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();

}

}

Page 25: Database Design Patterns

Finders

Page 26: Database Design Patterns

OrderFinder::setConnection($conn);

$order = OrderFinder::findByReference('XX12345678');

echo sprintf('%01.2f euros', $order->getTotal());

Page 27: Database Design Patterns

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;

}

}

Page 28: Database Design Patterns

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;

}

}

Page 29: Database Design Patterns

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";

}

Page 30: Database Design Patterns

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;

}

}

Page 31: Database Design Patterns

When to use it?

Page 32: Database Design Patterns

Active Record

Page 33: Database Design Patterns

« An object that wraps a row in a

database table or view, encapsulates

the database access, and adds

domain logic on that data. » Martin Fowler  

Page 34: Database Design Patterns

Active Record =

Row Data Gateway + Business Logic

Page 35: Database Design Patterns

Active Record =

Data + Behaviors

Page 36: Database Design Patterns

Active Record =

Properties + Methods

Page 37: Database Design Patterns

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

// ...

}

Page 38: Database Design Patterns

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();

}

}

Page 39: Database Design Patterns

$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);

Page 40: Database Design Patterns

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; } }

Page 41: Database Design Patterns

class Order

{

public function isPaid()

{

return $this->isPaid;

}

public function setPaid()

{

$this->isPaid = true;

}

}

Page 42: Database Design Patterns

class Order

{

public function isReadyForShipment()

{

return $this->isPaid() && 'complete' == $this->status;

}

public function ship($address)

{

$this->doShipment($address);

$this->status = 'shipped';

}

}

Page 43: Database Design Patterns

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));

}

}

Page 44: Database Design Patterns

Refactoring

Page 45: Database Design Patterns

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

}

}

Page 46: Database Design Patterns

class Order extends ActiveRecord

{

private $amount;

abstract public function getTableName()

{

return 'tbl_orders';

}

public function setAmount($amount)

{

$this->amount = $amount;

$this->fields['amount'] = $amount;

}

}

Page 47: Database Design Patterns

When to use it?

Page 48: Database Design Patterns

Data Mapper

Page 49: Database Design Patterns

« A layer of Mappers that moves data

between objects and a database

while keeping them independent of

each other and the mapper itself. » Martin Fowler  

Page 50: Database Design Patterns

« Man in the Middle »

Page 51: Database Design Patterns

http://martinfowler.com  

Page 52: Database Design Patterns

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

}

}

Page 53: Database Design Patterns

$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);

Page 54: Database Design Patterns

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;

}

}

Page 55: Database Design Patterns

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;

}

}

Page 56: Database Design Patterns

$conn = new Connection('mysql:host=localhost ...');

$mapper = new OrderMapper($conn);

$order = $mapper->find(42);

$order->setAmount(399.00);

$order->updateTotal();

$mapper->store($order);

Page 57: Database Design Patterns

Unit testing

Page 58: Database Design Patterns

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());

}

}

Page 59: Database Design Patterns

When to use it?

Page 60: Database Design Patterns

IdentityMap

Page 61: Database Design Patterns

« Ensures that each object gets

loaded only once by keeping every

loaded object in a map. »

Martin Fowler  

Page 62: Database Design Patterns

$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

Page 63: Database Design Patterns

The solution

Page 64: Database Design Patterns

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;

}

}

Page 65: Database Design Patterns

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;

}

}

Page 66: Database Design Patterns

class Order implements ValueObjectInterface

{

private $id;

private $reference;

private $amount;

// ...

public function getId()

{

return $this->id;

}

}

Page 67: Database Design Patterns

class OrderMapper extends DatabaseMapper

{

private $map;

public function __construct(IdentityMap $map, ...)

{

parent::__construct($conn);

$this->map = $map;

}

}

Page 68: Database Design Patterns

class OrderMapper extends DatabaseMapper

{

public function store(Order $order)

{

parent::store($order);

$this->map->store('Order', $object);

}

}

Page 69: Database Design Patterns

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;

}

}

Page 70: Database Design Patterns

$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

Page 71: Database Design Patterns

Query Object

Page 72: Database Design Patterns

$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

Page 73: Database Design Patterns

class Criteria

{

private $field;

private $operator;

private $parameters;

public function __construct($field, $operator, $value)

{

$this->field = $field;

$this->operator = $operator;

$this->parameters[] = $value;

}

}

Page 74: Database Design Patterns

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);

}

}

Page 75: Database Design Patterns

Custom Queries

Page 76: Database Design Patterns

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));

}

}

Page 77: Database Design Patterns

$query = OrderQuery::create()

->filterByPriority(2000)

->filterByReference('%567%')

->orderByAmount('DESC')

->getSql()

;

Page 78: Database Design Patterns

ORM Tools

Page 79: Database Design Patterns

Zend_DB

Doctrine 1.2

Doctrine 2.x

Propel

Pomm

Page 80: Database Design Patterns

Quizz

Can you guess the patterns?

Page 81: Database Design Patterns

$table = new Author(); // New empty row $row = $table->createRow(); // Insert a new row $row->firstName = 'Jules'; $row->lastName = 'Verne'; $row->save();

Page 82: Database Design Patterns

$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';

Page 83: Database Design Patterns

$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();

Page 84: Database Design Patterns

$data = array( 'first_name' => 'Jules', 'last_name' => 'Vernes', ); $table = new AuthorTable(); $table->insert($data);

Page 85: Database Design Patterns

Conclusion

Page 86: Database Design Patterns

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 §  …

Page 87: Database Design Patterns

Ques&ons?  

92-98, boulevard Victor Hugo

92 115 Clichy Cedex

[email protected] (+33 (0)1 40 99 82 11)

sensiolabs.com - symfony.com – trainings.sensiolabs.com


Related Documents