SlideShare a Scribd company logo
1 of 87
Download to read offline
Enterprise Database Design
      Patterns in PHP
     Hugo Hamon – OSIDays 2011
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
§    …
Table Data Gateway
« An object that acts as a Gateway to

a database table. One instance

handles all the rows in the table. »
                                  Martin Fowler	
  
Same as
Data Access Object
                     Martin Fowler	
  
CRUD
$table = new OrderGateway(new Connection('...'));

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

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

$table->delete(42);
class OrderGateway
{
    private $conn;

    public function __construct(Connection $conn)
    {
        $this->conn = $conn;
    }
}
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();
    }
}
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);
    }
}
class OrderGateway
{
    public function delete($pk)
    {
        return $this->conn->executeQuery(
            'DELETE FROM orders WHERE id = ?',
            array($pk)
        );
    }
}
Finders
$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' => '...'));
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;
    }
}
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));
}
public function findPaidOrders()
{
    return $this->findBy(array('status' => 'paid'));
}



public function findUnpaidOrders()
{
    return $this->findBy(array('status' => 'unpaid'));
}
When to use it?
Row Data Gateway
« An object that acts as a Gateway to

a single record in a data source. There

is one instance per row. »
                                  Martin Fowler	
  
CRUD
class Order
{
    private   $id;
    private   $reference;
    private   $amount;
    private   $vat;
    private   $total;
    private   $createdAt;

    // Getters and setters for each property
    // ...
}
$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);
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();
    }
}
Finders
OrderFinder::setConnection($conn);

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

echo sprintf('%01.2f euros', $order->getTotal());
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;
    }
}
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;
    }
}
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";
}
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;
    }
}
When to use it?
Active Record
« 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	
  
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;
    private   $total;
    private   $createdAt;
    private   $status;
    private   $isPaid;

    // Getters and setters for each property
    // ...
}
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();
    }
}
$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);
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;
    }
}
class Order
{
    public function isPaid()
    {
        return $this->isPaid;
    }

    public function setPaid()
    {
         $this->isPaid = true;
    }
}
class Order
{
    public function isReadyForShipment()
    {
        return $this->isPaid() && 'complete' == $this->status;
    }

    public function ship($address)
    {
        $this->doShipment($address);
        $this->status = 'shipped';
    }
}
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));
    }
}
Refactoring
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
    }
}
class Order extends ActiveRecord
{
    private $amount;

    abstract public function getTableName()
    {
        return 'tbl_orders';
    }

    public function setAmount($amount)
    {
        $this->amount = $amount;
        $this->fields['amount'] = $amount;
    }
}
When to use it?
Data Mapper
« 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	
  
« Man in the Middle »
http://martinfowler.com	
  
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
    }
}
$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);
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;
    }
}
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;
    }
}
$conn = new Connection('mysql:host=localhost ...');

$mapper = new OrderMapper($conn);

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

$order->setAmount(399.00);
$order->updateTotal();

$mapper->store($order);
Unit testing
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());
    }
}
When to use it?
IdentityMap
« Ensures that each object gets
loaded only once by keeping every
loaded object in a map. »
                              Martin Fowler	
  
$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
The solution
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;
    }
}
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;
    }
}
class Order   implements ValueObjectInterface
{
    private   $id;
    private   $reference;
    private   $amount;
    // ...

    public function getId()
    {
        return $this->id;
    }
}
class OrderMapper extends DatabaseMapper
{
    private $map;

    public function __construct(IdentityMap $map, ...)
    {
        parent::__construct($conn);

        $this->map = $map;
    }
}
class OrderMapper extends DatabaseMapper
{
    public function store(Order $order)
    {
        parent::store($order);

        $this->map->store('Order', $object);
    }
}
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;
    }
}
$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
Query Object
$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
class Criteria
{
    private $field;
    private $operator;
    private $parameters;

    public function __construct($field, $operator, $value)
    {
        $this->field = $field;
        $this->operator = $operator;
        $this->parameters[] = $value;
    }
}
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);
    }
}
Custom Queries
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));
    }
}
$query = OrderQuery::create()
    ->filterByPriority(2000)
    ->filterByReference('%567%')
    ->orderByAmount('DESC')
    ->getSql()
;
ORM Tools
Zend_DB            Propel

    Doctrine 2.x

Pomm        Doctrine 1.2
Quizz

Can you guess the patterns?
$table = new Author();

// New empty row
$row = $table->createRow();

// Insert a new row
$row->firstName = 'Jules';
$row->lastName = 'Verne';
$row->save();
$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';
$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();
$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 Record
§    Data Mapper
§    Unit of Work
§    Identity Map
§    Data Transfer Object
§    …
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

More Related Content

What's hot

Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
Bill Chang
 
Dependency Injection IPC 201
Dependency Injection IPC 201Dependency Injection IPC 201
Dependency Injection IPC 201
Fabien Potencier
 
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Masahiro Nagano
 

What's hot (20)

Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
 
The State of Lithium
The State of LithiumThe State of Lithium
The State of Lithium
 
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionLithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
 
Things I Believe Now That I'm Old
Things I Believe Now That I'm OldThings I Believe Now That I'm Old
Things I Believe Now That I'm Old
 
Dependency Injection IPC 201
Dependency Injection IPC 201Dependency Injection IPC 201
Dependency Injection IPC 201
 
Object Calisthenics Applied to PHP
Object Calisthenics Applied to PHPObject Calisthenics Applied to PHP
Object Calisthenics Applied to PHP
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
Object Calisthenics Adapted for PHP
Object Calisthenics Adapted for PHPObject Calisthenics Adapted for PHP
Object Calisthenics Adapted for PHP
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016
 
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
 
PHP for Adults: Clean Code and Object Calisthenics
PHP for Adults: Clean Code and Object CalisthenicsPHP for Adults: Clean Code and Object Calisthenics
PHP for Adults: Clean Code and Object Calisthenics
 
Design how your objects talk through mocking
Design how your objects talk through mockingDesign how your objects talk through mocking
Design how your objects talk through mocking
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix it
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 

Viewers also liked

Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
Hugo Hamon
 
Designing for developers
Designing for developersDesigning for developers
Designing for developers
Kirsten Hunter
 

Viewers also liked (20)

Data Antipatterns
Data AntipatternsData Antipatterns
Data Antipatterns
 
Database Anti Patterns
Database Anti PatternsDatabase Anti Patterns
Database Anti Patterns
 
Architectural Patterns of Resilient Distributed Systems
 Architectural Patterns of Resilient Distributed Systems Architectural Patterns of Resilient Distributed Systems
Architectural Patterns of Resilient Distributed Systems
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
 
This stuff is cool, but...HOW CAN I GET MY COMPANY TO DO IT?
This stuff is cool, but...HOW CAN I GET MY COMPANY TO DO IT?This stuff is cool, but...HOW CAN I GET MY COMPANY TO DO IT?
This stuff is cool, but...HOW CAN I GET MY COMPANY TO DO IT?
 
Quantifying fitness
Quantifying fitnessQuantifying fitness
Quantifying fitness
 
Liberating your data
Liberating your dataLiberating your data
Liberating your data
 
API 101 Workshop from APIStrat Conference
API 101 Workshop from APIStrat ConferenceAPI 101 Workshop from APIStrat Conference
API 101 Workshop from APIStrat Conference
 
Designing for developers
Designing for developersDesigning for developers
Designing for developers
 
Prototyping in the cloud
Prototyping in the cloudPrototyping in the cloud
Prototyping in the cloud
 
Développeurs, cachez-moi ça ! (Paris Web 2011)
Développeurs, cachez-moi ça ! (Paris Web 2011)Développeurs, cachez-moi ça ! (Paris Web 2011)
Développeurs, cachez-moi ça ! (Paris Web 2011)
 
API First
API FirstAPI First
API First
 
Facebook appsincloud
Facebook appsincloudFacebook appsincloud
Facebook appsincloud
 
Monitor the quality of your Symfony projects
Monitor the quality of your Symfony projectsMonitor the quality of your Symfony projects
Monitor the quality of your Symfony projects
 
Liberating your data
Liberating your dataLiberating your data
Liberating your data
 
Symfony2 - Un Framework PHP 5 Performant
Symfony2 - Un Framework PHP 5 PerformantSymfony2 - Un Framework PHP 5 Performant
Symfony2 - Un Framework PHP 5 Performant
 
Symfony2 en pièces détachées
Symfony2 en pièces détachéesSymfony2 en pièces détachées
Symfony2 en pièces détachées
 
Api 101
Api 101Api 101
Api 101
 
Polyglot copy
Polyglot copyPolyglot copy
Polyglot copy
 
Symfony2 - extending the console component
Symfony2 - extending the console componentSymfony2 - extending the console component
Symfony2 - extending the console component
 

Similar to Database Design Patterns

SPL: The Missing Link in Development
SPL: The Missing Link in DevelopmentSPL: The Missing Link in Development
SPL: The Missing Link in Development
jsmith92
 

Similar to Database Design Patterns (20)

Your code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConYour code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnCon
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
 
The Art of Transduction
The Art of TransductionThe Art of Transduction
The Art of Transduction
 
Drupal7 dbtng
Drupal7  dbtngDrupal7  dbtng
Drupal7 dbtng
 
Presentation1
Presentation1Presentation1
Presentation1
 
Database api
Database apiDatabase api
Database api
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
 
Bacbkone js
Bacbkone jsBacbkone js
Bacbkone js
 
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 
[PHPCon 2023] “Kto to pisał?!... a, to ja.”, czyli sposoby żeby znienawidzić ...
[PHPCon 2023] “Kto to pisał?!... a, to ja.”, czyli sposoby żeby znienawidzić ...[PHPCon 2023] “Kto to pisał?!... a, to ja.”, czyli sposoby żeby znienawidzić ...
[PHPCon 2023] “Kto to pisał?!... a, to ja.”, czyli sposoby żeby znienawidzić ...
 
Everything About PowerShell
Everything About PowerShellEverything About PowerShell
Everything About PowerShell
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
 
Oops in php
Oops in phpOops in php
Oops in php
 
Taming Command Bus
Taming Command BusTaming Command Bus
Taming Command Bus
 
Drupal 8 database api
Drupal 8 database apiDrupal 8 database api
Drupal 8 database api
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
 
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 
SPL: The Missing Link in Development
SPL: The Missing Link in DevelopmentSPL: The Missing Link in Development
SPL: The Missing Link in Development
 
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for PerformanceMeet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
 

More from Hugo Hamon

Intégration continue des projets PHP avec Jenkins
Intégration continue des projets PHP avec JenkinsIntégration continue des projets PHP avec Jenkins
Intégration continue des projets PHP avec Jenkins
Hugo Hamon
 
Exposer des services web SOAP et REST avec symfony 1.4 et Zend Framework
Exposer des services web SOAP et REST avec symfony 1.4 et Zend FrameworkExposer des services web SOAP et REST avec symfony 1.4 et Zend Framework
Exposer des services web SOAP et REST avec symfony 1.4 et Zend Framework
Hugo Hamon
 

More from Hugo Hamon (6)

Intégration Continue PHP avec Jenkins CI
Intégration Continue PHP avec Jenkins CIIntégration Continue PHP avec Jenkins CI
Intégration Continue PHP avec Jenkins CI
 
Build powerfull and smart web applications with Symfony2
Build powerfull and smart web applications with Symfony2Build powerfull and smart web applications with Symfony2
Build powerfull and smart web applications with Symfony2
 
Intégration continue des projets PHP avec Jenkins
Intégration continue des projets PHP avec JenkinsIntégration continue des projets PHP avec Jenkins
Intégration continue des projets PHP avec Jenkins
 
Mieux Développer en PHP avec Symfony
Mieux Développer en PHP avec SymfonyMieux Développer en PHP avec Symfony
Mieux Développer en PHP avec Symfony
 
Introduction à Symfony2
Introduction à Symfony2Introduction à Symfony2
Introduction à Symfony2
 
Exposer des services web SOAP et REST avec symfony 1.4 et Zend Framework
Exposer des services web SOAP et REST avec symfony 1.4 et Zend FrameworkExposer des services web SOAP et REST avec symfony 1.4 et Zend Framework
Exposer des services web SOAP et REST avec symfony 1.4 et Zend Framework
 

Recently uploaded

Recently uploaded (20)

Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 

Database Design Patterns

  • 1.
  • 2. Enterprise Database Design Patterns in PHP Hugo Hamon – OSIDays 2011
  • 3. 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 §  …
  • 5. « An object that acts as a Gateway to a database table. One instance handles all the rows in the table. » Martin Fowler  
  • 6. Same as Data Access Object Martin Fowler  
  • 8. $table = new OrderGateway(new Connection('...')); $table->insert('XX123456789', 358.80, 'unpaid'); $table->update(42, 'XX123456789', 358.80, 'paid'); $table->delete(42);
  • 9. class OrderGateway { private $conn; public function __construct(Connection $conn) { $this->conn = $conn; } }
  • 10. 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(); } }
  • 11. 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); } }
  • 12. class OrderGateway { public function delete($pk) { return $this->conn->executeQuery( 'DELETE FROM orders WHERE id = ?', array($pk) ); } }
  • 14. $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' => '...'));
  • 15. 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; } }
  • 16. 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)); }
  • 17. public function findPaidOrders() { return $this->findBy(array('status' => 'paid')); } public function findUnpaidOrders() { return $this->findBy(array('status' => 'unpaid')); }
  • 18. When to use it?
  • 20. « An object that acts as a Gateway to a single record in a data source. There is one instance per row. » Martin Fowler  
  • 21. CRUD
  • 22. class Order { private $id; private $reference; private $amount; private $vat; private $total; private $createdAt; // Getters and setters for each property // ... }
  • 23. $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);
  • 24. 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(); } }
  • 27. 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; } }
  • 28. 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; } }
  • 29. 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"; }
  • 30. 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; } }
  • 31. When to use it?
  • 33. « 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  
  • 34. Active Record = Row Data Gateway + Business Logic
  • 35. Active Record = Data + Behaviors
  • 36. Active Record = Properties + Methods
  • 37. 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 // ... }
  • 38. 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(); } }
  • 39. $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);
  • 40. 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; } }
  • 41. class Order { public function isPaid() { return $this->isPaid; } public function setPaid() { $this->isPaid = true; } }
  • 42. class Order { public function isReadyForShipment() { return $this->isPaid() && 'complete' == $this->status; } public function ship($address) { $this->doShipment($address); $this->status = 'shipped'; } }
  • 43. 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)); } }
  • 45. 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 } }
  • 46. class Order extends ActiveRecord { private $amount; abstract public function getTableName() { return 'tbl_orders'; } public function setAmount($amount) { $this->amount = $amount; $this->fields['amount'] = $amount; } }
  • 47. When to use it?
  • 49. « 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  
  • 50. « Man in the Middle »
  • 52. 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 } }
  • 53. $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);
  • 54. 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; } }
  • 55. 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; } }
  • 56. $conn = new Connection('mysql:host=localhost ...'); $mapper = new OrderMapper($conn); $order = $mapper->find(42); $order->setAmount(399.00); $order->updateTotal(); $mapper->store($order);
  • 58. 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()); } }
  • 59. When to use it?
  • 61. « Ensures that each object gets loaded only once by keeping every loaded object in a map. » Martin Fowler  
  • 62. $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
  • 64. 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; } }
  • 65. 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; } }
  • 66. class Order implements ValueObjectInterface { private $id; private $reference; private $amount; // ... public function getId() { return $this->id; } }
  • 67. class OrderMapper extends DatabaseMapper { private $map; public function __construct(IdentityMap $map, ...) { parent::__construct($conn); $this->map = $map; } }
  • 68. class OrderMapper extends DatabaseMapper { public function store(Order $order) { parent::store($order); $this->map->store('Order', $object); } }
  • 69. 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; } }
  • 70. $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
  • 72. $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
  • 73. class Criteria { private $field; private $operator; private $parameters; public function __construct($field, $operator, $value) { $this->field = $field; $this->operator = $operator; $this->parameters[] = $value; } }
  • 74. 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); } }
  • 76. 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)); } }
  • 77. $query = OrderQuery::create() ->filterByPriority(2000) ->filterByReference('%567%') ->orderByAmount('DESC') ->getSql() ;
  • 79. Zend_DB Propel Doctrine 2.x Pomm Doctrine 1.2
  • 80. Quizz Can you guess the patterns?
  • 81. $table = new Author(); // New empty row $row = $table->createRow(); // Insert a new row $row->firstName = 'Jules'; $row->lastName = 'Verne'; $row->save();
  • 82. $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';
  • 83. $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();
  • 84. $data = array( 'first_name' => 'Jules', 'last_name' => 'Vernes', ); $table = new AuthorTable(); $table->insert($data);
  • 86. 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 §  …
  • 87. 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