本文是orm系列的第一篇,內(nèi)容來(lái)自github上的一個(gè)Markdown,清晰的講述了一些數(shù)據(jù)庫(kù)設(shè)計(jì)上常用的設(shè)計(jì)模式,并且闡述了orm是什么?
數(shù)據(jù)庫(kù)
主要分一下幾部分:
- 數(shù)據(jù)庫(kù)設(shè)計(jì)模式
- 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
orcollections
; - grouped by
databases
.
數(shù)據(jù)庫(kù)設(shè)計(jì)模式
- Row Data Gateway
- Table Data Gateway
- Active Record
- Data Mapper
- Identity Map
- etc.
定義和插圖來(lái)自 Catalog of Patterns of Enterprise Application Architecture
作者Martin Fowler.
Don't forget his name! Read his books!
Row Data Gateway
一個(gè)對(duì)象扮演的角色就像是數(shù)據(jù)庫(kù)中單行記錄的網(wǎng)關(guān)(Gateway)
每個(gè)對(duì)象對(duì)應(yīng)一行
!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);
底層實(shí)現(xiàn)
!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
扮演著數(shù)據(jù)庫(kù)表的網(wǎng)關(guān)角色(Gateway)
一個(gè)對(duì)象處理了表中所有的行記錄
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實(shí)現(xiàn)了CURD操作
讀操作會(huì)比較負(fù)責(zé),是一系列Finders
實(shí)現(xiàn)
!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 %');
使用魔術(shù)方法
__call()
來(lái)實(shí)現(xiàn)這些魔術(shù)般的finders
http://www.php.net/manual/en/language.oop5.overloading.php#object.call.
Active Record
封裝了表中的單行記錄,除此之外加上了領(lǐng)域邏輯
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
將內(nèi)存中的數(shù)據(jù)映射到數(shù)據(jù)庫(kù)中,同時(shí)保持著彼此之間的解耦
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
保證每個(gè)對(duì)象只會(huì)從數(shù)據(jù)庫(kù)中加載一次,一旦加載進(jìn)來(lái),將其保存到一個(gè)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) 是標(biāo)準(zhǔn)的操作數(shù)據(jù)的api,不管你使用哪個(gè)數(shù)據(jù)庫(kù),都是一樣的
Data Source Name (DSN)則是區(qū)分到底在使用哪種數(shù)據(jù)庫(kù)
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.
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
對(duì)象之間的關(guān)系
介紹3種關(guān)系
- One-To-One;
- One-To-Many;
- Many-To-Many.
ORM一般認(rèn)為是實(shí)現(xiàn)上面各種設(shè)計(jì)模式的一個(gè)工具,并且能很方便的處理對(duì)象之間的關(guān)系
One-To-One (1-1)
Code Snippet
!php
$profile = $banana->getProfile();
One-To-Many (1-N)
Code Snippet
!php
$bananas = $bananaTree->getBananas();
Many-To-Many (N-N)
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
可以通過(guò)id進(jìn)行區(qū)分的對(duì)象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
直接通過(guò)值來(lái)分區(qū),無(wú)id的對(duì)象,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
Repository協(xié)調(diào)了領(lǐng)域?qū)ο蠛蛿?shù)據(jù)映射層的關(guān)系,扮演著內(nèi)存中領(lǐng)域?qū)ο蠹希?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
客戶端通過(guò)構(gòu)造聲明式的query specifications去Repository進(jìn)行查詢。
對(duì)象可以被添加進(jìn)Repository,同樣的也能從Repository中移除,從這個(gè)角度講,Repository有點(diǎn)類似于集合的概念,其內(nèi)部封裝了對(duì)象和數(shù)據(jù)庫(kù)記錄之間的映射關(guān)系,Repository提供了persistence的一個(gè)更面向?qū)ο蟮囊暯恰?/p>
Repository同時(shí)很好的解決了領(lǐng)域?qū)ο蠛蛿?shù)據(jù)映射層之間的耦合關(guān)系,充分的分離的關(guān)注點(diǎn),領(lǐng)域?qū)ο蠛蛿?shù)據(jù)映射層可以獨(dú)自的開發(fā),演化。
The Specification Pattern
Specification pattern可以將業(yè)務(wù)規(guī)則建模為獨(dú)立的對(duì)象,其主要思想是關(guān)注一個(gè)對(duì)象的問(wèn)題,可以通過(guò)isSatisfiedBy()
來(lái)回答:
!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
在業(yè)務(wù)層復(fù)用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的第一篇,你的鼓勵(lì)是我繼續(xù)寫下去的動(dòng)力,期待我們共同進(jìn)步。