所有的數(shù)據(jù)模型文件,都 必須 存放在:app/Models/
文件夾中。
命名空間:
namespace App\Models;
所有的 Eloquent 數(shù)據(jù)模型 都 必須 繼承統(tǒng)一的基類 App\Models\Model
,此基類存放位置為 /app/Models/Model.php
,內(nèi)容參考以下:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class Model extends EloquentModel
{
public function scopeRecent($query)
{
return $query->orderBy('id', 'desc');
}
public function scopeOlder($query)
{
return $query->orderBy('id', 'asc');
}
public function scopeByUser($query, User $user)
{
return $query->where('user_id', $user->id);
}
}
以 Photo 數(shù)據(jù)模型作為例子繼承 Model 基類:
<?php
namespace App\Models;
class Photo extends Model
{
protected $fillable = ['id', 'user_id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
數(shù)據(jù)模型相關(guān)的命名規(guī)范:
必須
為「單數(shù)」, 如:App\Models\Photo
必須
為「單數(shù)」,如:app/Models/Photo.php
必須
為「復(fù)數(shù)」,多個(gè)單詞情況下使用「Snake Case」 如:photos
, my_photos
必須
為「復(fù)數(shù)」,如:2014_08_08_234417_create_photos_table.php
必須
為「復(fù)數(shù)」,如:PhotosTableSeeder.php
必須
為「Snake Case」,如:view_count
, is_vip
必須
為「id」必須
為「resource_id」,如:user_id
, post_id
必須
為「resource_id」,如:$user_id
, $post_id
數(shù)據(jù)關(guān)聯(lián)內(nèi)部 ?必須
? 使用「resource_id」,假如 User 模型有 id 和 UUID 兩個(gè)唯一字段,其他模型關(guān)聯(lián) User ?必須
? 使用 id 字段。也就是在其他模型的數(shù)據(jù)表里,使用 ?user_id
? 字段。
模型間,相同的模型邏輯,例如 User 和 Topic 都有一個(gè) settings JSON 字段,用來實(shí)現(xiàn)單個(gè)模型的設(shè)置功能,應(yīng)該 利用 Trait 來實(shí)現(xiàn)邏輯代碼。
所有模型 Traits 必須存放于 app/Models/Traits
目錄下。
注意: 業(yè)務(wù)邏輯請(qǐng)使用 ModelService 模式來組織代碼。
絕不 使用 Repository,因?yàn)槲覀儾皇窃趯?JAVA 代碼,太多封裝就成了「過度設(shè)計(jì)(Over Designed)」,極大降低了編碼愉悅感,使用 MVC 夠傻夠簡單。
代碼的可讀性,維護(hù)和開發(fā)的便捷性,直接關(guān)系到程序員開發(fā)時(shí)的愉悅感,直接影響到項(xiàng)目推進(jìn)效率和程序 Debug 速度。
Laravel 的 Model 全局作用域 允許我們?yōu)榻o定模型的所有查詢添加默認(rèn)的條件約束。
所有的全局作用域都 必須 統(tǒng)一使用 閉包定義全局作用域
,如下:
/**
* 數(shù)據(jù)模型的啟動(dòng)方法
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('age', function(Builder $builder) {
$builder->where('age', '>', 200);
});
}
先看一段代碼,以下是 Post 模型里創(chuàng)建文章評(píng)論的方法:
public function createComment($content)
{
return $this->comments()->create([
'content' => $content,
'user_id' => Auth::user()->id
]);
}
注意 Auth::user()->id
,在數(shù)據(jù)層里使用當(dāng)前登錄用戶狀態(tài),是默認(rèn)假設(shè)這段代碼永遠(yuǎn)是在 Web 用戶請(qǐng)求下執(zhí)行的。
然而事實(shí)并非如此,有時(shí)候你可能會(huì)在命令行下觸發(fā)調(diào)用這個(gè) createComment()
方法,有時(shí)候是管理員在后臺(tái)觸發(fā),有時(shí)候是隊(duì)列里觸發(fā)。
一個(gè)最佳實(shí)踐的做法是, 絕不 在數(shù)據(jù)層里使用用戶登錄狀態(tài)信息。如果需要用戶信息,必須 將其作為依賴進(jìn)行傳參,如以上代碼可修改為:
public function createComment($content, $user)
{
return $this->comments()->create([
'content' => $content,
'user_id' => $user->id
]);
}
在有需要的地方調(diào)用時(shí),以參數(shù)傳入:
Post::createComment($content, Auth::user())
命令行書寫某些特殊邏輯時(shí),例如使用 1 號(hào)用戶的身份創(chuàng)建評(píng)論:
Post::createComment($content, User::find(1))
數(shù)據(jù)層,也就是模型里,不能跟用戶的登錄狀態(tài)掛鉤。
如果是一個(gè)長期維護(hù)的項(xiàng)目,必須 為模型文件按業(yè)務(wù)邏輯做分層。
一個(gè)長期維護(hù)的項(xiàng)目,很容易就會(huì)出現(xiàn)幾十上百的表,每個(gè)表對(duì)應(yīng)一個(gè) Model 文件。筆者曾維護(hù)過一個(gè)項(xiàng)目,兩百多個(gè) Model 文件,app/models 目錄完全沒法看。
如果你能預(yù)期到 Model 文件會(huì)很多,那就 必須 做好目錄劃分,按照業(yè)務(wù)邏輯來分,以 LearnKu.com 為例,app/models 的目錄結(jié)構(gòu)如下:
├── Book
│ ├── Article.php
│ └── Book.php
├── Community
│ ├── Reply.php
│ └── Topic.php
└── Project
├── ProjectAuthor.php
└── Project.php
應(yīng)該 盡量避免使用 Laravel 的 模型事件。
使用模型事件的問題在于,其職能很難界定,所有的業(yè)務(wù)邏輯都能寫到模型事件中。
而我們?cè)陧?xiàng)目中,業(yè)務(wù)邏輯代碼規(guī)都封裝到 Service 層,開發(fā)者在書寫業(yè)務(wù)邏輯代碼時(shí),就會(huì)面臨兩種選擇。
例如說 ReplyService 類的 create 方法,將創(chuàng)建評(píng)論時(shí)需要的邏輯,如發(fā)送通知給話題的作者,或者增加話題的評(píng)論數(shù)等操作,放置于此方法中,效果跟放在 ReplyObserver 中是一樣的。
不一樣的是, ReplyService 是顯示地書寫業(yè)務(wù)邏輯,代碼可讀性比模型事件更高。
模型事件另一個(gè)缺點(diǎn)就是,模型操作,附帶太多邏輯,有太多的 Side Effect,并且是隱性的。模型操作是一個(gè)使用頻率很高的功能,在有些場景中,你就想創(chuàng)建一個(gè) Reply,但是不想通知到用戶,例如說 Seed 時(shí)。雖然 Laravel 有提供模型方法讓你暫時(shí)關(guān)閉模型事件,但這在實(shí)踐中,我見過太多開發(fā)者經(jīng)常會(huì)忘記此操作。
更多建議: