問題描述
經(jīng)常有這樣的情況,某個 eloquent 模型的關系未設置(即在書籍表中,author_id
為空),因此調(diào)用諸如 $model->relation 之類的東西會返回空.
There is often the case where an certain eloquent model's relation is unset (i.e. in a books table, author_id
is null) and thus calling something like $model->relation returns null.
例如假設 Book 模型有一個我可能想要做的 author() (hasOne) 關系
E.g. say a Book model has an author() (hasOne) relation I might want to do
$author = Book::find(1)->author->name;
如果 Book 1 沒有設置作者,它將拋出試圖獲取非對象的屬性"錯誤.有沒有辦法避免這種情況并默認為空白的 Author
所以我總是能夠在它上面調(diào)用 name
,無論是否為特定設置了關系模型?
If Book 1 has no author set it will throw a "trying to get property of non object" error. Is there a way to avoid this and default to a blank Author
so I'll always be able to call name
on it regardless of whether the relation has been set for the specific model?
本質(zhì)上,我想避免在調(diào)用進一步的方法/屬性之前檢查 $book->author
是否是實際的 Author
的條件.如果沒有設置關系,它應該默認為一個新的 Author 實例.
Essentially I want to avoid conditionals to check if $book->author
is an actual Author
before calling further methods/properties on it. It should default to a new Author instance if the relation isn't set.
我嘗試了類似的東西:
public function getAuthorAttribute($author)
{
return $author ?: new Author;
}
然而這是行不通的;$author
被作為 null 傳入,即使它是在模型上設置的.大概是因為它是一種關系,而不是一本書的直接屬性.我需要像
however this doesn't work; $author
is being passed in as null, even if it's set on the model. Presumably because it's a relation rather than a direct property of a book. I'd need something like
public function getAuthorAttribute()
{
return $this->author()->first() ?: new Author;
}
這看起來很不雅觀,而且似乎會覆蓋任何導致性能不佳的急切加載.
which seems pretty inelegant and seems like it would override any eager loading resulting in poor performance.
推薦答案
更新
從 Laravel 5.3.23 開始,現(xiàn)在有一個內(nèi)置的方法來實現(xiàn)這一點(至少對于 HasOne
關系).withDefault()
方法已添加到 HasOne
關系中.對于您的 Book
/Author
示例,您的代碼如下所示:
Update
As of Laravel 5.3.23, there is now a built in way to accomplish this (at least for HasOne
relationships). A withDefault()
method was added to the HasOne
relationship. In the case of your Book
/Author
example, your code would look like:
public function author() {
return $this->hasOne(Author::class)->withDefault();
}
如果在數(shù)據(jù)庫中找不到記錄,此關系現(xiàn)在將返回一個相當空的(設置了鍵)Author
模型.此外,如果你想用一些額外的數(shù)據(jù)填充你的空模型,你可以傳入一個屬性數(shù)組,或者你可以傳入一個閉包,它返回你想要的默認設置(沒有成為作者
模型).
This relationship will now return a fairly empty (keys are set) Author
model if no record is found in the database. Additionally, you can pass in an array of attributes if you'd like to populate your empty model with some extra data, or you can pass in a Closure that returns what you'd like to have your default set to (doesn't have to be an Author
model).
直到有一天它成為文檔,有關更多信息,您可以查看與更改相關的拉取請求:16198 和 16382.
Until this makes it into the documentation one day, for more information you can check out the pull requests related to the change: 16198 and 16382.
在撰寫本文時,這僅針對 HasOne
關系實施.它最終可能會遷移到 BelongsTo
、MorphOne
和 MorphTo
關系,但我不能肯定.
At the time of this writing, this has only been implemented for the HasOne
relationship. It may eventually migrate to the BelongsTo
, MorphOne
, and MorphTo
relationships, but I can't say for sure.
據(jù)我所知,沒有內(nèi)置的方法可以做到這一點,但有幾種解決方法.
There's no built in way that I know of to do this, but there are a couple workarounds.
正如您所發(fā)現(xiàn)的,使用訪問器的問題在于傳遞給訪問器的 $value
將始終為 null
,因為它是從模型上的屬性數(shù)組.這個屬性數(shù)組不包括關系,無論它們是否已經(jīng)加載.
The problem with using an accessor, as you've found out, is that the $value
passed to the accessor will always be null
, since it is populated from the array of attributes on the model. This array of attributes does not include relationships, whether they're already loaded or not.
如果您想嘗試使用訪問器解決此問題,您只需忽略傳入的任何值,并自行檢查關系.
If you want to attempt to solve this with an accessor, you would just ignore whatever value is passed in, and check the relationship yourself.
public function getAuthorAttribute($value)
{
$key = 'author';
/**
* If the relationship is already loaded, get the value. Otherwise, attempt
* to load the value from the relationship method. This will also set the
* key in $this->relations so that subsequent calls will find the key.
*/
if (array_key_exists($key, $this->relations)) {
$value = $this->relations[$key];
} elseif (method_exists($this, $key)) {
$value = $this->getRelationshipFromMethod($key);
}
$value = $value ?: new Author();
/**
* This line is optional. Do you want to set the relationship value to be
* the new Author, or do you want to keep it null? Think of what you'd
* want in your toArray/toJson output...
*/
$this->setRelation($key, $value);
return $value;
}
現(xiàn)在,在訪問器中執(zhí)行此操作的問題在于您需要為每個模型上的每個 hasOne/belongsTo 關系定義一個訪問器.
Now, the problem with doing this in the accessor is that you need to define an accessor for every hasOne/belongsTo relationship on every model.
第二個較小的問題是訪問器僅在訪問屬性時使用.因此,例如,如果您要預先加載關系,然后 dd()
或 toArray
/toJson
模型,它仍然會顯示 null
表示關系,而不是空的作者.
A second, smaller, issue is that the accessor is only used when accessing the attribute. So, for example, if you were to eager load the relationship, and then dd()
or toArray
/toJson
the model, it would still show null
for the relatioinship, instead of an empty Author.
第二個選項是覆蓋 Model
上的一些方法,而不是使用屬性訪問器.這解決了使用屬性訪問器的兩個問題.
A second option, instead of using attribute accessors, would be to override some methods on the Model
. This solves both of the problems with using an attribute accessor.
您可以創(chuàng)建自己的基礎 Model
類來擴展 Laravel Model
并覆蓋這些方法,然后所有其他模型將擴展您的基礎 Model
類,而不是 Laravel 的 Model
類.
You can create your own base Model
class that extends the Laravel Model
and overrides these methods, and then all of your other models will extend your base Model
class, instead of Laravel's Model
class.
要處理預先加載的關系,您需要覆蓋 setRelation()
方法.如果使用 Laravel >= 5.2.30,這也將處理延遲加載關系.如果使用 Laravel <5.2.30,您還需要覆蓋延遲加載關系的 getRelationshipFromMethod()
方法.
To handle eager loaded relationships, you would need to override the setRelation()
method. If using Laravel >= 5.2.30, this will also handle lazy loaded relationships. If using Laravel < 5.2.30, you will also need to override the getRelationshipFromMethod()
method for lazy loaded relationships.
MyModel.php
class MyModel extends Model
{
/**
* Handle eager loaded relationships. Call chain:
* Model::with() => Builder::with(): sets builder eager loads
* Model::get() => Builder::get() => Builder::eagerLoadRelations() => Builder::loadRelation()
* =>Relation::initRelation() => Model::setRelation()
* =>Relation::match() =>Relation::matchOneOrMany() => Model::setRelation()
*/
public function setRelation($relation, $value)
{
/**
* Relationships to many records will always be a Collection, even when empty.
* Relationships to one record will either be a Model or null. When attempting
* to set to null, override with a new instance of the expected model.
*/
if (is_null($value)) {
// set the value to a new instance of the related model
$value = $this->$relation()->getRelated()->newInstance();
}
$this->relations[$relation] = $value;
return $this;
}
/**
* This override is only needed in Laravel < 5.2.30. In Laravel
* >= 5.2.30, this method calls the setRelation method, which
* is already overridden and contains our logic above.
*
* Handle lazy loaded relationships. Call chain:
* Model::__get() => Model::getAttribute() => Model::getRelationshipFromMethod();
*/
protected function getRelationshipFromMethod($method)
{
$results = parent::getRelationshipFromMethod($method);
/**
* Relationships to many records will always be a Collection, even when empty.
* Relationships to one record will either be a Model or null. When the
* result is null, override with a new instance of the related model.
*/
if (is_null($results)) {
$results = $this->$method()->getRelated()->newInstance();
}
return $this->relations[$method] = $results;
}
}
Book.php
class Book extends MyModel
{
//
}
這篇關于具有 Eloquent 關系的空對象模式的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!