所有的數(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ù)」,多個單詞情況下使用「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 兩個唯一字段,其他模型關(guān)聯(lián) User ?必須
? 使用 id 字段。也就是在其他模型的數(shù)據(jù)表里,使用 ?user_id
? 字段。
模型間,相同的模型邏輯,例如 User 和 Topic 都有一個 settings JSON 字段,用來實(shí)現(xiàn)單個模型的設(shè)置功能,應(yīng)該 利用 Trait 來實(shí)現(xiàn)邏輯代碼。
所有模型 Traits 必須存放于 app/Models/Traits
目錄下。
注意: 業(yè)務(wù)邏輯請使用 ModelService 模式來組織代碼。
絕不 使用 Repository,因?yàn)槲覀儾皇窃趯?JAVA 代碼,太多封裝就成了「過度設(shè)計(jì)(Over Designed)」,極大降低了編碼愉悅感,使用 MVC 夠傻夠簡單。
代碼的可讀性,維護(hù)和開發(fā)的便捷性,直接關(guān)系到程序員開發(fā)時的愉悅感,直接影響到項(xiàng)目推進(jìn)效率和程序 Debug 速度。
Laravel 的 Model 全局作用域 允許我們?yōu)榻o定模型的所有查詢添加默認(rèn)的條件約束。
所有的全局作用域都 必須 統(tǒng)一使用 閉包定義全局作用域
,如下:
/**
* 數(shù)據(jù)模型的啟動方法
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('age', function(Builder $builder) {
$builder->where('age', '>', 200);
});
}
先看一段代碼,以下是 Post 模型里創(chuà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 用戶請求下執(zhí)行的。
然而事實(shí)并非如此,有時候你可能會在命令行下觸發(fā)調(diào)用這個 createComment()
方法,有時候是管理員在后臺觸發(fā),有時候是隊(duì)列里觸發(fā)。
一個最佳實(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ù)傳入:
Post::createComment($content, Auth::user())
命令行書寫某些特殊邏輯時,例如使用 1 號用戶的身份創(chuàng)建評論:
Post::createComment($content, User::find(1))
數(shù)據(jù)層,也就是模型里,不能跟用戶的登錄狀態(tài)掛鉤。
如果是一個長期維護(hù)的項(xiàng)目,必須 為模型文件按業(yè)務(wù)邏輯做分層。
一個長期維護(hù)的項(xiàng)目,很容易就會出現(xiàn)幾十上百的表,每個表對應(yīng)一個 Model 文件。筆者曾維護(hù)過一個項(xiàng)目,兩百多個 Model 文件,app/models 目錄完全沒法看。
如果你能預(yù)期到 Model 文件會很多,那就 必須 做好目錄劃分,按照業(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ù)邏輯都能寫到模型事件中。
而我們在項(xiàng)目中,業(yè)務(wù)邏輯代碼規(guī)都封裝到 Service 層,開發(fā)者在書寫業(yè)務(wù)邏輯代碼時,就會面臨兩種選擇。
例如說 ReplyService 類的 create 方法,將創(chuàng)建評論時需要的邏輯,如發(fā)送通知給話題的作者,或者增加話題的評論數(shù)等操作,放置于此方法中,效果跟放在 ReplyObserver 中是一樣的。
不一樣的是, ReplyService 是顯示地書寫業(yè)務(wù)邏輯,代碼可讀性比模型事件更高。
模型事件另一個缺點(diǎn)就是,模型操作,附帶太多邏輯,有太多的 Side Effect,并且是隱性的。模型操作是一個使用頻率很高的功能,在有些場景中,你就想創(chuàng)建一個 Reply,但是不想通知到用戶,例如說 Seed 時。雖然 Laravel 有提供模型方法讓你暫時關(guān)閉模型事件,但這在實(shí)踐中,我見過太多開發(fā)者經(jīng)常會忘記此操作。
更多建議: