美文网首页代码与远方orm今日看点
orm 系列 之 常用设计模式

orm 系列 之 常用设计模式

作者: 小聪明李良才 | 来源:发表于2016-11-24 11:31 被阅读1557次
    orm

    本文是orm系列的第一篇,内容来自github上的一个Markdown,清晰的讲述了一些数据库设计上常用的设计模式,并且阐述了orm是什么?

    数据库


    主要分一下几部分:

    • 数据库设计模式
    • DAL(Data Access Layer)
    • ORM(Object Relational Mapping)
    • 存在的组件
    • A Note About Domain-Driven Design

    Quick note

    In our context, a database is seen as a server hosting:

    • a set of records;
    • organised through tables or collections;
    • grouped by databases.

    数据库设计模式

    • Row Data Gateway
    • Table Data Gateway
    • Active Record
    • Data Mapper
    • Identity Map
    • etc.

    定义和插图来自 Catalog of Patterns of Enterprise Application Architecture

    作者Martin Fowler.

    Don't forget his name! Read his books!


    Row Data Gateway

    Row Data Gateway

    一个对象扮演的角色就像是数据库中单行记录的网关(Gateway)

    每个对象对应一行

    !php
    // This is the implementation of `BananaGateway`
    class Banana
    {
        private $id;
    
        private $name;
    
        public function getId()
        {
            return $this->id;
        }
    
        public function getName()
        {
            return $this->name;
        }
    
        public function setName($name)
        {
            $this->name = $name;
        }
    }
    

    使用

    !php
    $con = new Connection('...');
    
    $banana = new Banana();
    $banana->setName('Super Banana');
    
    // Save the banana
    $banana->insert($con);
    
    // Update it
    $banana->setName('New name for my banana');
    $banana->update($con);
    
    // Delete it
    $banana->delete($con);
    

    底层实现

    !php
    public function insert(Connection $con)
    {
        // Prepared statement
        $stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)');
    
        $stmt->bindValue(':name', $name);
    
        $stmt->execute();
    
        // Set the id for this banana
        //
        // It becomes easy to know whether the banana is new or not,
        // you just need to check if id is defined.
        $this->id = $this->con->lastInsertId();
    }
    

    Table Data Gateway


    扮演着数据库表的网关角色(Gateway

    一个对象处理了表中所有的行记录

    It's a Data Access Object.

    !php
    $table = new BananaGateway(new Connection('...'));
    
    // Insert a new record
    $id = $table->insert('My favorite banana');
    
    // Update it
    $table->update($id, 'THE banana');
    
    // Delete it
    $table->delete($id);
    

    CRUD

    DAO实现了CURD操作

    读操作会比较负责,是一系列Finders


    实现

    !php
    class BananaGateway
    {
        private $con;
    
        public function __construct(Connection $con)
        {
            $this->con = $con;
        }
    
        public function insert($name) {}
    
        public function update($id, $name) {}
    
        public function delete($id);
    }
    

    insert操作

    !php
    /**
     * @param string $name The name of the banana you want to create
     *
     * @return int The id of the banana
     */
    public function insert($name)
    {
        // Prepared statement
        $stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)');
    
        $stmt->bindValue(':name', $name);
    
        $stmt->execute();
    
        return $this->con->lastInsertId();
    }
    

    update操作

    !php
    /**
     * @param int    $id   The id of the banana to update
     * @param string $name The new name of the banana
     *
     * @return bool Returns `true` on success, `false` otherwise
     */
    public function update($id, $name)
    {
        $stmt = $this->con->prepare(<<<SQL
    UPDATE bananas
    SET name = :name
    WHERE id = :id
    SQL
        );
    
        $stmt->bindValue(':id', $id);
        $stmt->bindValue(':name', $name);
    
        return $stmt->execute();
    }
    

    delete操作

    !php
    /**
     * @param int $id The id of the banana to delete
     *
     * @return bool Returns `true` on success, `false` otherwise
     */
    public function delete($id)
    {
        $stmt = $this->con->prepare('DELETE FROM bananas WHERE id = :id');
    
        $stmt->bindValue(':id', $id);
    
        return $stmt->execute();
    }
    

    Finder方法

    !php
    // Retrieve all bananas
    $bananas = $table->findAll();
    
    // Find bananas by name matching 'THE %'
    $bananas = $table->findByName('THE %');
    
    // Retrieve a given banana using its id
    $banana = $table->find(123);
    
    // Find one banana by name matching 'THE %'
    $banana = $table->findOneByName('THE %');
    

    使用魔术方法 __call() 来实现这些魔术般的finders
    http://www.php.net/manual/en/language.oop5.overloading.php#object.call.


    Active Record


    封装了表中的单行记录,除此之外加上了领域逻辑

    Active Record = Row Data Gateway + Domain Logic

    !php
    $con = new Connection('...');
    
    $banana = new Banana();
    $banana->setName('Another banana');
    $banana->save($con);
    
    // Call a method that is part of the domain logic
    // What can a banana do anyway?
    $banana->grow();
    
    // Smart `save()` method
    // use `isNew()` under the hood
    $banana->save($con);
    

    !php
    class Banana
    {
        private $height = 1;
    
        public function grow()
        {
            $this->height++;
        }
    
        public function save(Connection $con)
        {
            if ($this->isNew()) {
                // issue an INSERT query
            } else {
                // issue an UPDATE query
            }
        }
    
        public function isNew()
        {
            // Yoda style
            return null === $this->id;
        }
    }
    

    Data Mapper

    Data Mapper

    将内存中的数据映射到数据库中,同时保持着彼此之间的解耦

    Sort of "Man in the Middle".

    !php
    class BananaMapper
    {
        private $con;
    
        public function __construct(Connection $con)
        {
            $this->con = $con;
        }
    
        public function persist(Banana $banana)
        {
            // code to save the banana
        }
    
        public function remove(Banana $banana)
        {
            // code to delete the banana
        }
    }
    

    使用

    !php
    $banana = new Banana();
    $banana->setName('Fantastic Banana');
    
    $con    = new Connection('...');
    $mapper = new BananaMapper($con);
    

    Persist = Save or Update

    !php
    $mapper->persist($banana);
    

    Remove

    !php
    $mapper->remove($banana);
    

    Identity Map


    保证每个对象只会从数据库中加载一次,一旦加载进来,将其保存到一个map中

    !php
    class Finder
    {
        private $identityMap = [];
    
        public function find($id)
        {
            if (!isset($this->identityMap[$id])) {
                // fetch the object for the given id
                $this->identityMap[$id] = ...;
            }
    
            return $this->identityMap[$id];
        }
    }
    

    Data Access Layer


    Data Access Layer / Data Source Name

    D**ata Access Layer (DAL) 是标准的操作数据的api,不管你使用哪个数据库,都是一样的

    Data Source Name (DSN)则是区分到底在使用哪种数据库

    PHP Data Object (PDO)

    A DSN in PHP looks like: <database>:host=<host>;dbname=<dbname> where:

    • <database> can be: mysql, sqlite, pgsql, etc;
    • <host> is the IP address of the database server (e.g. localhost);
    • <dbname> is your database name.

    http://www.php.net/manual/en/intro.pdo.php


    PDO usage

    !php
    $dsn = 'mysql:host=localhost;dbname=test';
    
    $con = new PDO($dsn, $user, $password);
    
    // Prepared statement
    $stmt = $con->prepare($query);
    $stmt->execute();
    

    Looks like the Connection class you used before, right?

    !php
    class Connection extends PDO
    {
    }
    

    Usage

    !php
    $con = new Connection($dsn, $user, $password);
    

    Refactoring

    !php
    class Connection extends PDO
    {
        /**
         * @param string $query
         * @param array  $parameters
         *
         * @return bool Returns `true` on success, `false` otherwise
         */
        public function executeQuery($query, array $parameters = [])
        {
            $stmt = $this->prepare($query);
    
            foreach ($parameters as $name => $value) {
                $stmt->bindValue(':' . $name, $value);
            }
    
            return $stmt->execute();
        }
    }
    

    Usage

    !php
    /**
     * @param int    $id   The id of the banana to update
     * @param string $name The new name of the banana
     *
     * @return bool Returns `true` on success, `false` otherwise
     */
    public function update($id, $name)
    {
        $query = 'UPDATE bananas SET name = :name WHERE id = :id';
    
        return $this->con->executeQuery($query, [
            'id'    => $id,
            'name'  => $name,
        ]);
    }
    

    Object Relational Mapping


    对象之间的关系

    介绍3种关系

    • One-To-One;
    • One-To-Many;
    • Many-To-Many.

    ORM一般认为是实现上面各种设计模式的一个工具,并且能很方便的处理对象之间的关系


    One-To-One (1-1)

    One-To-One

    Code Snippet

    !php
    $profile = $banana->getProfile();
    

    One-To-Many (1-N)

    Code Snippet

    !php
    $bananas = $bananaTree->getBananas();
    

    Many-To-Many (N-N)

    Many-To-Many

    Code Snippet

    !php
    $roles = [];
    foreach ($banana->getBananaRoles() as $bananaRole) {
        $roles[] = $bananaRole->getRole();
    }
    
    // Or, better:
    $roles = $banana->getRoles();
    

    存在的组件

    Propel ORM

    An ORM that implements the Table Data Gateway and Row Data Gateway
    patterns, often seen as an Active Record approach.

    Documentation: www.propelorm.org.

    Doctrine2 ORM

    An ORM that implements the Data Mapper pattern.

    Documentation: www.doctrine-project.org.


    A Note About Domain-Driven Design


    Entities

    可以通过id进行区分的对象entity:

    !php
    class Customer
    {
        private $id;
    
        private $name;
    
        public function __construct($id, Name $name)
        {
            $this->id   = $id;
            $this->name = $name;
        }
    
        public function getId()
        {
            return $this->id;
        }
    
        public function getName()
        {
            return $this->name;
        }
    }
    

    Value Objects

    直接通过值来分区,无id的对象,Value Object:

    !php
    class Name
    {
        private $firstName;
    
        private $lastName;
    
        public function __construct($firstName, $lastName)
        {
            $this->firstName = $firstName;
            $this->lastName  = $lastName;
        }
    
        public function getFirstName()
        {
            return $this->firstName;
        }
    
        public function getLastName()
        {
            return $this->lastName;
        }
    }
    

    The Repository Pattern

    The Repository Pattern

    Repository协调了领域对象和数据映射层的关系,扮演着内存中领域对象集合( in-memory domain object collection)的角色。

    !php
    interface CustomerRepository
    {
        /**
         * @return Customer
         */
        public function find($customerId);
    
        /**
         * @return Customer[]
         */
        public function findAll();
    
        public function add(Customer $user);
    
        public function remove(Customer $user);
    }
    

    The Repository Pattern

    客户端通过构造声明式的query specifications去Repository进行查询。

    对象可以被添加进Repository,同样的也能从Repository中移除,从这个角度讲,Repository有点类似于集合的概念,其内部封装了对象和数据库记录之间的映射关系,Repository提供了persistence的一个更面向对象的视角。

    Repository同时很好的解决了领域对象和数据映射层之间的耦合关系,充分的分离的关注点,领域对象和数据映射层可以独自的开发,演化。


    The Specification Pattern

    Specification Pattern

    Specification pattern可以将业务规则建模为独立的对象,其主要思想是关注一个对象的问题,可以通过isSatisfiedBy()来回答:

    !php
    interface CustomerSpecification
    {
        /**
         * @return boolean
         */
        public function isSatisfiedBy(Customer $customer);
    }
    
    !php
    class CustomerIsPremium implements CustomerSpecification
    {
        /**
         * {@inheritDoc}
         */
        public function isSatisfiedBy(Customer $customer)
        {
            // figure out if the customer is indeed premium,
            // and return true or false.
        }
    }
    

    Repository ♥ Specification

    A findSatisfying() method can be added to the CustomerRepository:

    !php
    interface CustomerRepository
    {
        ...
    
        /**
         * @return Customer[]
         */
        public function findSatisfying(CustomerSpecification $specification);
    }
    

    Usage

    !php
    $specification = new CustomerIsPremium();
    $customers     = $repository->findSatisfying($specification);
    

    Combine Them!

    !php
    class OrSpecification implements CustomerSpecification
    {
        public function __construct(
            CustomerSpecification $s1,
            CustomerSpecification $s2
        ) {
            $this->s1 = $s1;
            $this->s2 = $s2;
        }
    
        public function isSatisfiedBy(Customer $c)
        {
            return $this->s1->isSatisfiedBy($c) || $this->s2->isSatisfiedBy($c);
        }
    }
    
    !php
    class AndSpecification implements CustomerSpecification
    {
        ...
    
        public function isSatisfiedBy(Customer $c)
        {
            return $this->s1->isSatisfiedBy($c) && $this->s2->isSatisfiedBy($c);
        }
    }
    

    !php
    class NotSpecification implements CustomerSpecification
    {
        public function __construct(CustomerSpecification $s)
        {
            $this->s = $s;
        }
    
        public function isSatisfiedBy(Customer $c)
        {
            return !$this->s->isSatisfiedBy($c);
        }
    }
    

    Usage

    !php
    // Find customers who have ordered exactly three times,
    // but who are not premium customers (yet?)
    $specification = new AndSpecification(
        new CustomerHasOrderedThreeTimes(),
        new NotSpecification(
            new CustomerIsPremium()
        )
    );
    
    $customers = $repository->findSatisfying($specification);
    

    Specification For Business Rules

    在业务层复用specifications

    !php
    class AwesomeOfferSender
    {
        private $specification;
    
        public function __construct(CustomerIsPremium $specification)
        {
            $this->specification = $specification;
        }
    
        public function sendOffersTo(Customer $customer)
        {
            if ($this->specification->isSatisfiedBy($customer)) {
                // send offers
            }
        }
    }
    

    原文地址是:

    https://github.com/willdurand-edu/php-slides/blob/master/src/common/09_databases.md

    这是orm的第一篇,你的鼓励是我继续写下去的动力,期待我们共同进步。

    相关文章

      网友评论

      本文标题:orm 系列 之 常用设计模式

      本文链接:https://www.haomeiwen.com/subject/nmebpttx.html