摘要:為關聯關系設置約束子模型的等于父模型的上面設置的字段的值子類實現這個抽象方法通過上面代碼看到創建實例時主要是做了一些配置相關的操作,設置了子模型父模型兩個模型的關聯字段和關聯的約束。不過當查詢父模型時,可以預加載關聯數據。
Database 模型關聯
上篇文章我們主要講了Eloquent Model關于基礎的CRUD方法的實現,Eloquent Model中除了基礎的CRUD外還有一個很重要的部分叫模型關聯,它通過面向對象的方式優雅地把數據表之間的關聯關系抽象到了Eloquent Model中讓應用依然能用Fluent Api的方式訪問和設置主體數據的關聯數據。使用模型關聯給應用開發帶來的收益我認為有以下幾點
主體數據和關聯數據之間的關系在代碼表現上更明顯易懂讓人一眼就能明白數據間的關系。
模型關聯在底層幫我們解決好了數據關聯和匹配,應用程序中不需要再去寫join語句和子查詢,應用代碼的可讀性和易維護性更高。
使用模型關聯預加載后,在效率上高于開發者自己寫join和子查詢,模型關聯底層是通過分別查詢主體和關聯數據再將它們關聯匹配到一起。
按照Laravel設定好的模式來寫關聯模型每個人都能寫出高效和優雅的代碼 (這點我認為適用于所有的Laravel特性)。
說了這么多下面我們就通過實際示例出發深入到底層看看模型關聯是如何解決數據關聯匹配和加載關聯數據的。
在開發中我們經常遇到的關聯大致有三種:一對一,一對多和多對多,其中一對一是一種特殊的一對多關聯。我們通過官方文檔里的例子來看一下Laravel是怎么定義這兩種關聯的。
一對多class Post extends Model { /** * 獲得此博客文章的評論。 */ public function comments() { return $this->hasMany("AppComment"); } } /** * 定義一個一對多關聯關系,返回值是一個HasMany實例 * * @param string $related * @param string $foreignKey * @param string $localKey * @return IlluminateDatabaseEloquentRelationsHasMany */ public function hasMany($related, $foreignKey = null, $localKey = null) { //創建一個關聯表模型的實例 $instance = $this->newRelatedInstance($related); //關聯表的外鍵名 $foreignKey = $foreignKey ?: $this->getForeignKey(); //主體表的主鍵名 $localKey = $localKey ?: $this->getKeyName(); return new HasMany( $instance->newQuery(), $this, $instance->getTable().".".$foreignKey, $localKey ); } /** * 創建一個關聯表模型的實例 */ protected function newRelatedInstance($class) { return tap(new $class, function ($instance) { if (! $instance->getConnectionName()) { $instance->setConnection($this->connection); } }); }
在定義一對多關聯時返回了一個IlluminateDatabaseEloquentRelationsHasMany 類的實例,Eloquent封裝了一組類來處理各種關聯,其中HasMany是繼承自HasOneOrMany抽象類, 這也正印證了上面說的一對一是一種特殊的一對多關聯,Eloquent定義的所有這些關聯類又都是繼承自Relation這個抽象類, Relation里定義里一些模型關聯基礎的方法和一些必須讓子類實現的抽象方法,各種關聯根據自己的需求來實現這些抽象方法。
為了閱讀方便我們把這幾個有繼承關系類的構造方法放在一起,看看定義一對多關返回的HasMany實例時都做了什么。
class HasMany extends HasOneOrMany { ...... } abstract class HasOneOrMany extends Relation { ...... public function __construct(Builder $query, Model $parent, $foreignKey, $localKey) { $this->localKey = $localKey; $this->foreignKey = $foreignKey; parent::__construct($query, $parent); } //為關聯關系設置約束 子模型的foreign key等于父模型的 上面設置的$localKey字段的值 public function addConstraints() { if (static::$constraints) { $this->query->where($this->foreignKey, "=", $this->getParentKey()); $this->query->whereNotNull($this->foreignKey); } } public function getParentKey() { return $this->parent->getAttribute($this->localKey); } ...... } abstract class Relation { public function __construct(Builder $query, Model $parent) { $this->query = $query; $this->parent = $parent; $this->related = $query->getModel(); //子類實現這個抽象方法 $this->addConstraints(); } }
通過上面代碼看到創建HasMany實例時主要是做了一些配置相關的操作,設置了子模型、父模型、兩個模型的關聯字段、和關聯的約束。
Eloquent里把主體數據的Model稱為父模型,關聯數據的Model稱為子模型,為了方便下面所以下文我們用它們來指代主體和關聯模型。
定義完父模型到子模型的關聯后我們還需要定義子模型到父模型的反向關聯才算完整, 還是之前的例子我們在子模型里通過belongsTo方法定義子模型到父模型的反向關聯。
class Comment extends Model { /** * 獲得此評論所屬的文章。 */ public function post() { return $this->belongsTo("AppPost"); } public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null) { //如果沒有指定$relation參數,這里通過debug backtrace方法獲取調用者的方法名稱,在我們的例子里是post if (is_null($relation)) { $relation = $this->guessBelongsToRelation(); } $instance = $this->newRelatedInstance($related); //如果沒有指定子模型的外鍵名稱則使用調用者的方法名加主鍵名的snake命名方式來作為默認的外鍵名(post_id) if (is_null($foreignKey)) { $foreignKey = Str::snake($relation)."_".$instance->getKeyName(); } // 設置父模型的主鍵字段 $ownerKey = $ownerKey ?: $instance->getKeyName(); return new BelongsTo( $instance->newQuery(), $this, $foreignKey, $ownerKey, $relation ); } protected function guessBelongsToRelation() { list($one, $two, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); return $caller["function"]; } } class BelongsTo extends Relation { public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relation) { $this->ownerKey = $ownerKey; $this->relation = $relation; $this->foreignKey = $foreignKey; $this->child = $child; parent::__construct($query, $child); } public function addConstraints() { if (static::$constraints) { $table = $this->related->getTable(); //設置約束 父模型的主鍵值等于子模型的外鍵值 $this->query->where($table.".".$this->ownerKey, "=", $this->child->{$this->foreignKey}); } } }
定義一對多的反向關聯時也是一樣設置了父模型、子模型、兩個模型的關聯字段和約束,此外還設置了關聯名稱,在Model的belongsTo方法里如果未提供后面的參數會通過debug_backtrace 獲取調用者的方法名作為關聯名稱進而猜測出子模型的外鍵名稱的,按照約定Eloquent 默認使用父級模型名的「snake case」形式、加上 _id 后綴名作為外鍵字段。
多對多多對多關聯不同于一對一和一對多關聯它需要一張中間表來記錄兩端數據的關聯關系,官方文檔里以用戶角色為例子闡述了多對多關聯的使用方法,我們也以這個例子來看一下底層是怎么來定義多對多關聯的。
class User extends Model { /** * 獲得此用戶的角色。 */ public function roles() { return $this->belongsToMany("AppRole"); } } class Role extends Model { /** * 獲得此角色下的用戶。 */ public function users() { return $this->belongsToMany("AppUser"); } } /** * 定義一個多對多關聯, 返回一個BelongsToMany關聯關系實例 * * @return IlluminateDatabaseEloquentRelationsBelongsToMany */ public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null) { //沒有提供$relation參數 則通過debug_backtrace獲取調用者方法名作為relation name if (is_null($relation)) { $relation = $this->guessBelongsToManyRelation(); } $instance = $this->newRelatedInstance($related); $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey(); //如果沒有提供中間表的名稱,則會按照字母順序合并兩個關聯模型的名稱作為中間表名 if (is_null($table)) { $table = $this->joiningTable($related); } return new BelongsToMany( $instance->newQuery(), $this, $table, $foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(), $relatedKey ?: $instance->getKeyName(), $relation ); } /** * 獲取多對多關聯中默認的中間表名 */ public function joiningTable($related) { $models = [ Str::snake(class_basename($related)), Str::snake(class_basename($this)), ]; sort($models); return strtolower(implode("_", $models)); } class BelongsToMany extends Relation { public function __construct(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName = null) { $this->table = $table;//中間表名 $this->parentKey = $parentKey;//父模型User的主鍵 $this->relatedKey = $relatedKey;//關聯模型Role的主鍵 $this->relationName = $relationName;//關聯名稱 $this->relatedPivotKey = $relatedPivotKey;//關聯模型Role的主鍵在中間表中的外鍵role_id $this->foreignPivotKey = $foreignPivotKey;//父模型Role的主鍵在中間表中的外鍵user_id parent::__construct($query, $parent); } public function addConstraints() { $this->performJoin(); if (static::$constraints) { $this->addWhereConstraints(); } } protected function performJoin($query = null) { $query = $query ?: $this->query; $baseTable = $this->related->getTable(); $key = $baseTable.".".$this->relatedKey; //$query->join("role_user", "role.id", "=", "role_user.role_id") $query->join($this->table, $key, "=", $this->getQualifiedRelatedPivotKeyName()); return $this; } /** * Set the where clause for the relation query. * * @return $this */ protected function addWhereConstraints() { //$this->query->where("role_user.user_id", "=", 1) $this->query->where( $this->getQualifiedForeignPivotKeyName(), "=", $this->parent->{$this->parentKey} ); return $this; } }
定義多對多關聯后返回一個IlluminateDatabaseEloquentRelationsBelongsToMany類的實例,與定義一對多關聯時一樣,實例化BelongsToMany時定義里與關聯相關的配置:中間表名、關聯的模型、父模型在中間表中的外鍵名、關聯模型在中間表中的外鍵名、父模型的主鍵、關聯模型的主鍵、關聯關系名稱。與此同時給關聯關系設置了join和where約束,以User類里的多對多關聯舉例,performJoin方法為其添加的join約束如下:
$query->join("role_user", "roles.id", "=", "role_user.role_id")
然后addWhereConstraints為其添加的where約束為:
//假設User對象的id是1 $query->where("role_user.user_id", "=", 1)
這兩個的約束就是對應的SQL語句就是
SELECT * FROM roles INNER JOIN role_users ON roles.id = role_user.role_id WHERE role_user.user_id = 1遠層一對多
Laravel還提供了遠層一對多關聯,提供了方便、簡短的方式通過中間的關聯來獲得遠層的關聯。還是以官方文檔的例子說起,一個 Country 模型可以通過中間的 User 模型獲得多個 Post 模型。在這個例子中,您可以輕易地收集給定國家的所有博客文章。讓我們來看看定義這種關聯所需的數據表:
countries id - integer name - string users id - integer country_id - integer name - string posts id - integer user_id - integer title - string
class Country extends Model { public function posts() { return $this->hasManyThrough( "AppPost", "AppUser", "country_id", // 用戶表外鍵... "user_id", // 文章表外鍵... "id", // 國家表本地鍵... "id" // 用戶表本地鍵... ); } } /** * 定義一個遠層一對多關聯,返回HasManyThrough實例 * @return IlluminateDatabaseEloquentRelationsHasManyThrough */ public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null) { $through = new $through; $firstKey = $firstKey ?: $this->getForeignKey(); $secondKey = $secondKey ?: $through->getForeignKey(); $localKey = $localKey ?: $this->getKeyName(); $secondLocalKey = $secondLocalKey ?: $through->getKeyName(); $instance = $this->newRelatedInstance($related); return new HasManyThrough($instance->newQuery(), $this, $through, $firstKey, $secondKey, $localKey, $secondLocalKey); } class HasManyThrough extends Relation { public function __construct(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey) { $this->localKey = $localKey;//國家表本地鍵id $this->firstKey = $firstKey;//用戶表中的外鍵country_id $this->secondKey = $secondKey;//文章表中的外鍵user_id $this->farParent = $farParent;//Country Model $this->throughParent = $throughParent;//中間 User Model $this->secondLocalKey = $secondLocalKey;//用戶表本地鍵id parent::__construct($query, $throughParent); } public function addConstraints() { //country的id值 $localValue = $this->farParent[$this->localKey]; $this->performJoin(); if (static::$constraints) { //$this->query->where("users.country_id", "=", 1) 假設country_id是1 $this->query->where($this->getQualifiedFirstKeyName(), "=", $localValue); } } protected function performJoin(Builder $query = null) { $query = $query ?: $this->query; $farKey = $this->getQualifiedFarKeyName(); //query->join("users", "users.id", "=", "posts.user_id") $query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), "=", $farKey); if ($this->throughParentSoftDeletes()) { $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn()); } } }
定義遠層一對多關聯會返回一個IlluminateDatabaseEloquentRelationshasManyThrough類的實例,實例化hasManyThrough時的操作跟實例化BelongsToMany時做的操作非常類似。
針對這個例子performJoin為關聯添加的join約束為:
query->join("users", "users.id", "=", "posts.user_id")
添加的where約束為:
$this->query->where("users.country_id", "=", 1) 假設country_id是1
對應的SQL查詢是:
SELECT * FROM posts INNER JOIN users ON users.id = posts.user_id WHERE users.country_id = 1
從SQL查詢我們也可以看到遠層一對多跟多對多生成的語句非常類似,唯一的區別就是它的中間表對應的是一個已定義的模型。
動態屬性加載關聯模型上面我們定義了三種使用頻次比較高的模型關聯,下面我們再來看一下在使用它們時關聯模型時如何加載出來的。我們可以像訪問屬性一樣訪問定義好的關聯的模型,例如,我們剛剛的 User 和 Post 模型例子中,我們可以這樣訪問用戶的所有文章:
$user = AppUser::find(1); foreach ($user->posts as $post) { // }
還記得我們上一篇文章里將獲取模型屬性時提到過的嗎,如果模型的$attributes屬性里沒有這個字段,那么會嘗試獲取模型關聯的值:
abstract class Model implements ... { public function __get($key) { return $this->getAttribute($key); } public function getAttribute($key) { if (! $key) { return; } //如果attributes數組的index里有$key或者$key對應一個屬性訪問器`"get" . $key` 則從這里取出$key對應的值 //否則就嘗試去獲取模型關聯的值 if (array_key_exists($key, $this->attributes) || $this->hasGetMutator($key)) { return $this->getAttributeValue($key); } if (method_exists(self::class, $key)) { return; } //獲取模型關聯的值 return $this->getRelationValue($key); } public function getRelationValue($key) { //取出已經加載的關聯中,避免重復獲取模型關聯數據 if ($this->relationLoaded($key)) { return $this->relations[$key]; } // 調用我們定義的模型關聯 $key 為posts if (method_exists($this, $key)) { return $this->getRelationshipFromMethod($key); } } protected function getRelationshipFromMethod($method) { $relation = $this->$method(); if (! $relation instanceof Relation) { throw new LogicException(get_class($this)."::".$method." must return a relationship instance."); } //通過getResults方法獲取數據,并緩存到$relations數組中去 return tap($relation->getResults(), function ($results) use ($method) { $this->setRelation($method, $results); }); } }
在通過動態屬性獲取模型關聯的值時,會調用與屬性名相同的關聯方法,拿到關聯實例后會去調用關聯實例的getResults方法返回關聯的模型數據。 getResults也是每個Relation子類需要實現的方法,這樣每種關聯都可以根據自己情況去執行查詢獲取關聯模型,現在這個例子用的是一對多關聯,在hasMany類中我們可以看到這個方法的定義如下:
class HasMany extends HasOneOrMany { public function getResults() { return $this->query->get(); } } class BelongsToMany extends Relation { public function getResults() { return $this->get(); } public function get($columns = ["*"]) { $columns = $this->query->getQuery()->columns ? [] : $columns; $builder = $this->query->applyScopes(); $models = $builder->addSelect( $this->shouldSelect($columns) )->getModels(); $this->hydratePivotRelation($models); if (count($models) > 0) { $models = $builder->eagerLoadRelations($models); } return $this->related->newCollection($models); } }關聯方法
出了用動態屬性加載關聯數據外還可以在定義關聯方法的基礎上再給關聯的子模型添加更多的where條件等的約束,比如:
$user->posts()->where("created_at", ">", "2018-01-01");
Relation實例會將這些調用通過__call轉發給子模型的Eloquent Builder去執行。
abstract class Relation { /** * Handle dynamic method calls to the relationship. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); } $result = $this->query->{$method}(...$parameters); if ($result === $this->query) { return $this; } return $result; } }預加載關聯模型
當作為屬性訪問 Eloquent 關聯時,關聯數據是「懶加載」的。意味著在你第一次訪問該屬性時,才會加載關聯數據。不過當查詢父模型時,Eloquent 可以「預加載」關聯數據。預加載避免了 N + 1 查詢問題。看一下文檔里給出的例子:
class Book extends Model { /** * 獲得此書的作者。 */ public function author() { return $this->belongsTo("AppAuthor"); } } //獲取所有的書和作者信息 $books = AppBook::all(); foreach ($books as $book) { echo $book->author->name; }
上面這樣使用關聯在訪問每本書的作者時都會執行查詢加載關聯數據,這樣顯然會影響應用的性能,那么通過預加載能夠把查詢降低到兩次:
$books = AppBook::with("author")->get(); foreach ($books as $book) { echo $book->author->name; }
我們來看一下底層時怎么實現預加載關聯模型的
abstract class Model implements ArrayAccess, Arrayable,...... { public static function with($relations) { return (new static)->newQuery()->with( is_string($relations) ? func_get_args() : $relations ); } } //Eloquent Builder class Builder { public function with($relations) { $eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations); $this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad); return $this; } protected function parseWithRelations(array $relations) { $results = []; foreach ($relations as $name => $constraints) { //如果$name是數字索引,證明沒有為預加載關聯模型添加約束條件,為了統一把它的約束條件設置為一個空的閉包 if (is_numeric($name)) { $name = $constraints; list($name, $constraints) = Str::contains($name, ":") ? $this->createSelectWithConstraint($name) : [$name, function () { // }]; } //設置這種用Book::with("author.contacts")這種嵌套預加載的約束條件 $results = $this->addNestedWiths($name, $results); $results[$name] = $constraints; } return $results; } public function get($columns = ["*"]) { $builder = $this->applyScopes(); //獲取模型時會去加載要預加載的關聯模型 if (count($models = $builder->getModels($columns)) > 0) { $models = $builder->eagerLoadRelations($models); } return $builder->getModel()->newCollection($models); } public function eagerLoadRelations(array $models) { foreach ($this->eagerLoad as $name => $constraints) { if (strpos($name, ".") === false) { $models = $this->eagerLoadRelation($models, $name, $constraints); } } return $models; } protected function eagerLoadRelation(array $models, $name, Closure $constraints) { //獲取關聯實例 $relation = $this->getRelation($name); $relation->addEagerConstraints($models); $constraints($relation); return $relation->match( $relation->initRelation($models, $name), $relation->getEager(), $name ); } }
上面的代碼可以看到with方法會把要預加載的關聯模型放到$eagarLoad屬性里,針對我們這個例子他的值類似下面這樣:
$eagarLoad = [ "author" => function() {} ]; //如果有約束則會是 $eagarLoad = [ "author" => function($query) { $query->where(....) } ];
這樣在通過Model 的get方法獲取模型時會預加載的關聯模型,在獲取關聯模型時給關系應用約束的addEagerConstraints方法是在具體的關聯類中定義的,我們可以看下HasMany類的這個方法。
*注: 下面的代碼為了閱讀方便我把一些在父類里定義的方法拿到了HasMany中,自己閱讀時如果找不到請去父類中找一下。
class HasMany extends ... { // where book_id in (...) public function addEagerConstraints(array $models) { $this->query->whereIn( $this->foreignKey, $this->getKeys($models, $this->localKey) ); } }
他給關聯應用了一個where book_id in (...)的約束,接下來通過getEager方法獲取所有的關聯模型組成的集合,再通過關聯類里定義的match方法把外鍵值等于父模型主鍵值的關聯模型組織成集合設置到父模型的$relations屬性中接下來用到了這些預加載的關聯模型時都是從$relations屬性中取出來的不會再去做數據庫查詢
class HasMany extends ... { //初始化model的relations屬性 public function initRelation(array $models, $relation) { foreach ($models as $model) { $model->setRelation($relation, $this->related->newCollection()); } return $models; } //預加載出關聯模型 public function getEager() { return $this->get(); } public function get($columns = ["*"]) { return $this->query->get($columns); } //在子類HasMany public function match(array $models, Collection $results, $relation) { return $this->matchMany($models, $results, $relation); } protected function matchOneOrMany(array $models, Collection $results, $relation, $type) { //組成[父模型ID => [子模型1, ...]]的字典 $dictionary = $this->buildDictionary($results); //將子模型設置到父模型的$relations屬性中去 foreach ($models as $model) { if (isset($dictionary[$key = $model->getAttribute($this->localKey)])) { $model->setRelation( $relation, $this->getRelationValue($dictionary, $key, $type) ); } } return $models; } }
預加載關聯模型后沒個Book Model的$relations屬性里都有了以關聯名author為key的數據, 類似下面
$relations = [ "author" => Collection(Author)//Author Model組成的集合 ];
這樣再使用動態屬性引用已經預加載關聯模型時就會直接從這里取出數據而不用再去做數據庫查詢了。
模型關聯常用的一些功能的底層實現到這里梳理完了,Laravel把我們平常用的join, where in 和子查詢都隱藏在了底層實現中并且幫我們把相互關聯的數據做好了匹配。還有一些我認為使用場景沒那么多的多態關聯、嵌套預加載那些我并沒有梳理,并且它們的底層實現都差不多,區別就是每個關聯類型有自己的關聯約束、匹配規則,有興趣的讀者自己去看一下吧。
本文已經收錄在系列文章Laravel源碼學習里,歡迎訪問閱讀。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/30716.html
摘要:過去一年時間寫了多篇文章來探討了我認為的框架最核心部分的設計思路代碼實現。為了大家閱讀方便,我把這些源碼學習的文章匯總到這里。數據庫算法和數據結構這些都是編程的內功,只有內功深厚了才能解決遇到的復雜問題。 過去一年時間寫了20多篇文章來探討了我認為的Larave框架最核心部分的設計思路、代碼實現。通過更新文章自己在軟件設計、文字表達方面都有所提高,在剛開始決定寫Laravel源碼分析地...
摘要:第三步注冊工廠啟動數據庫服務數據庫服務的啟動主要設置的連接分析器,讓能夠用服務連接數據庫。 在我們學習和使用一個開發框架時,無論使用什么框架,如何連接數據庫、對數據庫進行增刪改查都是學習的重點,在Laravel中我們可以通過兩種方式與數據庫進行交互: DB, DB是與PHP底層的PDO直接進行交互的,通過查詢構建器提供了一個方便的接口來創建及運行數據庫查詢語句。 Eloquent...
摘要:請求未通過的驗證時會拋出此異常。異常處理是非常重要但又容易讓開發者忽略的功能,這篇文章簡單解釋了內部異常處理的機制以及擴展異常處理的方式方法。 異常處理是編程中十分重要但也最容易被人忽視的語言特性,它為開發者提供了處理程序運行時錯誤的機制,對于程序設計來說正確的異常處理能夠防止泄露程序自身細節給用戶,給開發者提供完整的錯誤回溯堆棧,同時也能提高程序的健壯性。 這篇文章我們來簡單梳理一下...
摘要:本節將實現文章評論與用戶關聯的功能。關系定義首先修改與表,增加字段增加全部回滾并重新執行遷移添加用戶表與文章表評論表的一對多關系添加文章評論表與用戶表的多對一關系同時,評論表的字段增加。同時,我們還自定義了返回的錯誤信息。 本節將實現文章、評論與用戶關聯的功能。 關系定義 首先修改 posts 與 comments 表,增加 user_id 字段 /database/migratio...
摘要:模式定義觀察者模式定義對象間的一種一對多依賴關系,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知并被自動更新。 觀察者模式 Laravel的Event事件系統提供了一個簡單的觀察者模式實現,能夠訂閱和監聽應用中發生的各種事件,在PHP的標準庫(SPL)里甚至提供了三個接口SplSubject, SplObserver, SplObjectStorage來讓開發者更容易地實現觀...
閱讀 3364·2021-11-04 16:10
閱讀 3861·2021-09-29 09:43
閱讀 2704·2021-09-24 10:24
閱讀 3354·2021-09-01 10:46
閱讀 2508·2019-08-30 15:54
閱讀 591·2019-08-30 13:19
閱讀 3238·2019-08-29 17:19
閱讀 1060·2019-08-29 16:40