Eloquent是laravel中的orm迄损,采取的是active record的設(shè)計(jì)模式,里面的對(duì)象不僅包括領(lǐng)域邏輯,還包括了數(shù)據(jù)庫(kù)操作,但是大家平時(shí)使用的時(shí)候可能沒(méi)有探究eloquent是怎么設(shè)計(jì)的汉矿,active record這種模式的優(yōu)缺點(diǎn)等問(wèn)題,下面我會(huì)帶領(lǐng)大家從頭開(kāi)始看看Eloquent是如何設(shè)計(jì)并實(shí)現(xiàn)的副编。
本文是orm系列的第二篇负甸,也是Eloquent演化的第一篇流强,Eloquent系列會(huì)嘗試著講清楚Eloquent是如何一步一步演化到目前功能強(qiáng)大的版本的痹届,但是畢竟個(gè)人能力有限呻待,不可能分析的非常完善,總會(huì)有不懂的地方队腐,所以講的不錯(cuò)誤的地方蚕捉,懇請(qǐng)大牛們能指出,或者如果你有什么地方是沒(méi)看懂的柴淘,也請(qǐng)指出問(wèn)題來(lái)迫淹,因?yàn)榭赡苣堑胤骄褪俏易约簺](méi)看懂,所以沒(méi)講明白为严,也請(qǐng)?zhí)岢鰜?lái)敛熬,然后我們一起討論的,讓我們能共同的進(jìn)步的第股。
初始化
Eloquent首先要對(duì)數(shù)據(jù)庫(kù)連接做抽象应民,于是有了Connection
類(lèi),內(nèi)部主要是對(duì)PDO
的一個(gè)封裝夕吻,但是如果只有Connection的話诲锹,一個(gè)問(wèn)題是,我們需要直面sql涉馅,于是就有了Builder
類(lèi)归园,其功能就是屏蔽sql,讓我們能用面向?qū)ο蟮姆绞絹?lái)完成sql的查詢功能稚矿,Builder
應(yīng)該是sql builder庸诱,此時(shí)Eloquent的主要的類(lèi)就如下:
其中Builder負(fù)責(zé)sql的組裝,Connection負(fù)責(zé)具體的數(shù)據(jù)庫(kù)交互晤揣,其中多出來(lái)一個(gè)Grammar偶翅,其負(fù)責(zé)主要是負(fù)責(zé)將Builder里面存儲(chǔ)的數(shù)據(jù)轉(zhuǎn)化為sql。
note:此處版本是54d73c6碉渡,通過(guò) git co 54d73c6 可以查看
model引入
接著我們繼續(xù)演化聚谁,要引進(jìn)Model,要實(shí)現(xiàn)Active Record模式滞诺,在46966ec中首次加入了Eloquent/Model
類(lèi)形导,有興趣的同學(xué)可以git co 46966ec
查看,剛提交上來(lái)的時(shí)候习霹,Model類(lèi)中大概如下:
可以看到屬性通過(guò)定義table,connection朵耕,將具體的數(shù)據(jù)庫(kù)操作是委托給了connection
類(lèi),然后Model自己是負(fù)責(zé)領(lǐng)域邏輯淋叶,同時(shí)會(huì)定義一些靜態(tài)方法阎曹,如create,find,save
,充當(dāng)了Row Data Gateway角色,此時(shí)的類(lèi)圖如下:
此時(shí)新增的Model類(lèi)直接依賴于Connection和Builder处嫌,帶來(lái)的問(wèn)題是耦合栅贴,于是就有了一個(gè)改動(dòng),在Model同一層級(jí)上引入了一新的Builder熏迹,具體通過(guò)git co c420bd8
查看檐薯。
use Illuminate\Database\Query\Builder as BaseBuilder;
class Builder extends BaseBuilder {
/**
* The model being queried.
*
* @var Illuminate\Database\Eloquent\Model
*/
protected $model;
....
}
里面具體就是在基礎(chǔ)BaseBuilder
上通過(guò)Model
來(lái)獲取一些信息設(shè)置,譬如$this->from($model->getTable())
這種操作注暗,還有一個(gè)好處是保持了BaseBuilder
的純凈坛缕,沒(méi)有形成Model和BaseBuilder之間的雙向依賴,通過(guò)Model同層的Builder來(lái)去耦合捆昏,如下圖所示:
relation進(jìn)入
下一步是要引入1-1赚楚,1-N,N-N的關(guān)系了骗卜,可以通過(guò)git co 912de03
查看宠页,此時(shí)一個(gè)新增的類(lèi)的情況如下:
├── Builder.php
├── Model.php
└── Relations
├── BelongsTo.php
├── BelongsToMany.php
├── HasMany.php
├── HasOne.php
├── HasOneOrMany.php
└── Relation.php
其中Relation
是基類(lèi),然后其他的幾個(gè)都繼承它膨俐。
此時(shí)關(guān)系處理上主要的邏輯是調(diào)用Model的HasOne等表關(guān)系的方法勇皇,返回Relation的子類(lèi),然后通過(guò)Relation來(lái)處理進(jìn)而返回?cái)?shù)據(jù)焚刺,這么說(shuō)可能有點(diǎn)繞敛摘,我們下面具體介紹下每個(gè)關(guān)系的實(shí)現(xiàn),大家可能就理解了乳愉。
先看HasOne兄淫,即OneToOne的關(guān)系,看代碼
public function hasOne($related, $foreignKey = null)
{
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
return new HasOne($instance->newQuery(), $this, $foreignKey);
}
我們看到當(dāng)調(diào)用Model的hasOne
方法后蔓姚,返回是一個(gè)HasOne捕虽,即Relation,當(dāng)我們調(diào)用Relation的方法時(shí)坡脐,是怎么處理的呢泄私?通過(guò)魔術(shù)方法__call
,將其委托給了Eloquent\Builder
备闲,
public function __call($method, $parameters)
{
if (method_exists($this->query, $method))
{
return call_user_func_array(array($this->query, $method), $parameters);
}
throw new \BadMethodCallException("Method [$method] does not exist.");
}
即其實(shí)Relation是對(duì)Eloquent\Builder
的一個(gè)封裝晌端,支持面向?qū)ο笫降膕ql操作,我們下面來(lái)看下當(dāng)我們使用HasOne的時(shí)候發(fā)生了什么恬砂。
假設(shè)我們有個(gè)User,Phone咧纠,然后User和Phone的關(guān)系是HasOne,在User聲明上就會(huì)有
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
此時(shí)HasOne的構(gòu)造函數(shù)如下:
// builder是Eloquent\Builder, parent是Uer,$foreign_key是user_id
$relation = new HasOne($builder, $parent, $foreign_key);
當(dāng)使用User::with('phone')->get()
的時(shí)候,就會(huì)去eager load進(jìn)phone了泻骤,具體的過(guò)程中漆羔,在調(diào)用Eloquent\Builder
的get的時(shí)候梧奢,里面有個(gè)邏輯是:
if (count($models) > 0)
{
$models = $this->eagerLoadRelations($models);
}
獲取has one關(guān)系,我們跟著看到代碼演痒,會(huì)調(diào)用到函數(shù)eagerLoadRelation
亲轨,具體看代碼:
protected function eagerLoadRelation(array $models, $relation, Closure $constraints)
{
$instance = $this->getRelation($relation);
...
$instance->addEagerConstraints($models);
$models = $instance->initializeRelation($models, $relation);
$results = $instance->get();
return $instance->match($models, $results, $relation);
}
其中getRelation
會(huì)調(diào)用到User()->phone()
,即此處$instance
是HasOne
嫡霞,接著調(diào)用HasOne->addEagerConstraints()
和HasOne->initializeRelation()
瓶埋,具體的代碼是:
// class HasOne
public function addEagerConstraints(array $models)
{
// 新增foreignKey的條件
$this->query->whereIn($this->foreignKey, $this->getKeys($models));
}
public function initRelation(array $models, $relation)
{
foreach ($models as $model)
{
$model->setRelation($relation, null);
}
return $models;
}
// class Model
public function setRelation($relation, $value)
{
$this->relations[$relation] = $value;
}
最后調(diào)用match方法希柿,就是正確的給每個(gè)model設(shè)置好relation關(guān)系诊沪。
以上就是我們分析的HasOne的實(shí)現(xiàn),其他的關(guān)系都類(lèi)似曾撤,此處不再重復(fù)端姚,然后eager load的含義是指,當(dāng)我們要加載多個(gè)數(shù)據(jù)的時(shí)候挤悉,我們盡可能用一條sql解決渐裸,而不是多條sql,具體來(lái)說(shuō)如果我們有多個(gè)Users装悲,需要加載Phones的昏鹃,如果不采用eager,在每個(gè)sql就是where user_id=?
诀诊,而eager模式則是where user_id in (?,?,?)
洞渤,這樣差異就很明顯了.
note:以上分析的代碼是:git co f6e2170
講到這,我們列舉下對(duì)象之間的關(guān)系
One-To-One
User 和 Phone的1對(duì)1的關(guān)系属瓣,
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
// 逆向定義
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
sql的查詢類(lèi)似于下面
select id from phone where user_id in (1)
select id from user where id in (phone.user_id)
One-To-Many
以Post和Comment為例载迄,一個(gè)Post會(huì)有多個(gè)Comment
class Post extends Model
{
/**
* Get the comments for the blog post.
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
}
// reverse
class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
此處的sql和HasOne一致
select id from comment where post_id in (1)
select id from post where id in (comment.post_id)
Many To Many
以u(píng)ser和role為例,一個(gè)用戶會(huì)有不同的角色抡蛙,一個(gè)角色也會(huì)有不同的人护昧,這個(gè)時(shí)候就需要一張中間表role_user,代碼聲明上如下:
class User extends Model
{
/**
* The roles that belong to the user.
*/
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
這個(gè)關(guān)系我們稍微具體講下粗截,我們?cè)谑褂蒙峡赡軙?huì)是下面這樣子的
return $this->belongsToMany('App\Role', 'user_roles', 'user_id', 'role_id');
在構(gòu)造函數(shù)中惋耙,會(huì)調(diào)用addConstraints
方法,如下
// class belongsToMany
public function addConstraints()
{
$this->setSelect()->setJoin()->setWhere();
}
此處會(huì)預(yù)先設(shè)置setSelect()->setJoin()->setWhere()
熊昌,作用分別是:
setSelect() : 在select的字段中新增 role.*,user_role.id as pivot_id
setJoin():新增join绽榛, join user_role on role.id = user_role.role_id,聯(lián)合查詢
setWhere():新增 user_id = ?
查詢的表是role浴捆,join表user_role
在get的時(shí)候蒜田,其邏輯和HasOne等關(guān)系也所有不同,代碼如下:
// class belongsToMany
public function get($columns = array('*'))
{
$models = $this->query->getModels($columns);
$this->hydratePivotRelation($models);
if (count($models) > 0)
{
$models = $this->query->eagerLoadRelations($models);
}
return new Collection($models);
}
此處有個(gè)方法叫hydratePivotRelation
选泻,我們進(jìn)入看下到底是怎么回事
// class belongsToMany
protected function hydratePivotRelation(array $models)
{
// 將中間記錄取出來(lái)冲粤,設(shè)置屬性pivot為Model pivot
foreach ($models as $model)
{
$values = $this->cleanPivotAttributes($model);
$pivot = $this->newExistingPivot($values);
$model->setRelation('pivot', $pivot);
}
}
其實(shí)做的事情就是設(shè)置了Role的pivot屬性美莫。
到這,我們就分析完了eloquent在f6e2170版本上具有的功能了梯捕,到目前為止厢呵,eloquent的類(lèi)圖如下:
總結(jié)
目前,我們分析到的版本是f6e2170傀顾,已經(jīng)具備了一個(gè)orm該需要的功能了襟铭,Connection
負(fù)責(zé)數(shù)據(jù)庫(kù)操作,Builder
負(fù)責(zé)面向?qū)ο蟮膕ql操作短曾,Grammar
負(fù)責(zé)sql的拼裝寒砖,Eloquent/Model
是Active Record模式的核心Model,同時(shí)具備領(lǐng)域邏輯和數(shù)據(jù)庫(kù)操作功能嫉拐,其中數(shù)據(jù)庫(kù)操作功能是委托給了Eloquent/Builder
哩都,同時(shí)我們也定義了對(duì)象的3種關(guān)系,1-1婉徘,1-N漠嵌,N-N,下一階段盖呼,Eloquent將會(huì)實(shí)現(xiàn)migrations or database modification logic的功能儒鹿,盡情期待。
這是orm的第二篇几晤,你的鼓勵(lì)是我繼續(xù)寫(xiě)下去的動(dòng)力约炎,期待我們共同進(jìn)步。