前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >orm 系列 之 常用设计模式 The Repository Pattern

orm 系列 之 常用设计模式 The Repository Pattern

作者头像
zhuanxu
发布2018-08-23 12:54:48
1.8K0
发布2018-08-23 12:54:48
举报
文章被收录于专栏:进击的程序猿进击的程序猿

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)

每个对象对应一行

代码语言:javascript
复制
!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;
    }
}

使用
代码语言:javascript
复制
!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);

底层实现
代码语言:javascript
复制
!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.

代码语言:javascript
复制
!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


实现
代码语言:javascript
复制
!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操作
代码语言:javascript
复制
!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操作
代码语言:javascript
复制
!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操作
代码语言:javascript
复制
!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方法
代码语言:javascript
复制
!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
代码语言:javascript
复制
!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);

代码语言:javascript
复制
!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".

代码语言:javascript
复制
!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
    }
}

使用
代码语言:javascript
复制
!php
$banana = new Banana();
$banana->setName('Fantastic Banana');

$con    = new Connection('...');
$mapper = new BananaMapper($con);
Persist = Save or Update
代码语言:javascript
复制
!php
$mapper->persist($banana);
Remove
代码语言:javascript
复制
!php
$mapper->remove($banana);

Identity Map


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

代码语言:javascript
复制
!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
代码语言:javascript
复制
!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?

代码语言:javascript
复制
!php
class Connection extends PDO
{
}
Usage
代码语言:javascript
复制
!php
$con = new Connection($dsn, $user, $password);

Refactoring
代码语言:javascript
复制
!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
代码语言:javascript
复制
!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

代码语言:javascript
复制
!php
$profile = $banana->getProfile();

One-To-Many (1-N)

Code Snippet

代码语言:javascript
复制
!php
$bananas = $bananaTree->getBananas();

Many-To-Many (N-N)

Many-To-Many

Code Snippet

代码语言:javascript
复制
!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:

代码语言:javascript
复制
!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:

代码语言:javascript
复制
!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)的角色。

代码语言:javascript
复制
!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()来回答:

代码语言:javascript
复制
!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:

代码语言:javascript
复制
!php
interface CustomerRepository
{
    ...

    /**
     * @return Customer[]
     */
    public function findSatisfying(CustomerSpecification $specification);
}

Usage

代码语言:javascript
复制
!php
$specification = new CustomerIsPremium();
$customers     = $repository->findSatisfying($specification);

Combine Them!
代码语言:javascript
复制
!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);
    }
}

代码语言:javascript
复制
!php
class NotSpecification implements CustomerSpecification
{
    public function __construct(CustomerSpecification $s)
    {
        $this->s = $s;
    }

    public function isSatisfiedBy(Customer $c)
    {
        return !$this->s->isSatisfiedBy($c);
    }
}

Usage

代码语言:javascript
复制
!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

代码语言:javascript
复制
!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的第一篇,你的鼓励是我继续写下去的动力,期待我们共同进步。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.11.24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据库
  • Quick note
  • 数据库设计模式
    • Row Data Gateway
      • Table Data Gateway
        • Active Record
          • Data Mapper
            • Identity Map
              • Data Access Layer
                • Object Relational Mapping
                  • 存在的组件
                    • A Note About Domain-Driven Design
                      • The Repository Pattern
                      • The Repository Pattern
                        • The Specification Pattern
                          • Repository ♥ Specification
                            • Usage
                              • Usage
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档