php - 具有 Eloquent 关系的空对象模式

标签 php laravel laravel-5 eloquent

通常情况下,某个 Eloquent 模型的关系未设置(即在 books 表中,author_id 为 null),因此调用 $model->relation 之类的东西会返回 null。

例如假设一个 Book 模型有一个 author() (hasOne) 关系,我可能想这样做

$author = Book::find(1)->author->name;

如果第 1 本书没有作者集,它将抛出“试图获取非对象的属性”错误。有没有办法避免这种情况并默认为空白 Author 所以我总是能够调用 name 无论是否已为特定设置关系型号?

本质上,我想避免在调用进一步的方法/属性之前检查 $book->author 是否是实际的 Author 的条件。如果未设置关系,它应该默认为一个新的 Author 实例。


我试过类似的方法:

public function getAuthorAttribute($author)
{
    return $author ?: new Author;
}

但是这不起作用; $author 被作为 null 传递,即使它是在模型上设置的。大概是因为它是一种关系而不是一本书的直接属性。我需要类似的东西

public function getAuthorAttribute()
{
    return $this->author()->first() ?: new Author;
}

这看起来很不优雅,而且似乎会覆盖任何导致性能不佳的预加载。

最佳答案

更新

从 Laravel 5.3.23 开始,现在有一个内置的方法来完成这个(至少对于 HasOne 关系)。 withDefault() 方法被添加到 HasOne 关系中。对于您的 Book/Author 示例,您的代码如下所示:

public function author() {
    return $this->hasOne(Author::class)->withDefault();
}

如果在数据库中找不到记录,此关系现在将返回一个相当空的(已设置键)Author 模型。此外,如果你想用一些额外的数据填充你的空模型,你可以传入一个属性数组,或者你可以传入一个返回你想要的默认设置的闭包(没有成为 Author 模特)。

直到有朝一日将其纳入文档,有关更多信息,您可以查看与更改相关的拉取请求:1619816382 .

在撰写本文时,这仅针对 HasOne 关系实现。它最终可能会迁移到 BelongsToMorphOneMorphTo 关系,但我不能肯定地说。


原创

据我所知,没有内置方法可以执行此操作,但有一些解决方法。

使用访问器

正如您所发现的,使用访问器的问题是传递给访问器的 $value 将始终为 null,因为它是从模型上的属性数组。此属性数组不包括关系,无论它们是否已加载。

如果您想尝试使用访问器解决这个问题,您只需忽略传入的任何值,然后自己检查关系。

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;
}

现在,在访问器中执行此操作的问题是您需要为每个模型上的每个 hasOne/belongsTo 关系定义一个访问器。

第二个较小的问题是访问器仅在访问属性时使用。因此,例如,如果您急切加载关系,然后加载 dd()toArray/toJson 模型,它仍然会为关系显示 null,而不是空作者。

覆盖模型方法

第二种选择是覆盖 Model 上的某些方法,而不是使用属性访问器。这解决了使用属性访问器的两个问题。

您可以创建自己的 Model 基类来扩展 Laravel Model 并覆盖这些方法,然后所有其他模型都将扩展您的基 Model 类,而不是 Laravel 的 Model 类。

要处理预加载关系,您需要覆盖 setRelation() 方法。如果使用 Laravel >= 5.2.30,这也将处理延迟加载关系。如果使用 Laravel < 5.2.30,您还需要为延迟加载关系重写 getRelationshipFromMethod() 方法。

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
{
    //
}

关于php - 具有 Eloquent 关系的空对象模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33085339/

相关文章:

php - 播种前无法截断表

php - php中对象/关联数组的解构赋值

php - php Pregmatch在youtube上的视频下载链接

php - fetchColumn() 不将结果保存到变量

Laravel 验证 : difference between numeric and integer?

laravel - Ubuntu Laravel 上的 Gulp 安装错误

php - php中如何从数组中提取准确的数据

javascript - 如何将 Laravel/PHP 函数传递给 JS

php - 如何在 laravel 5.6 中使用关系从 id 显示名称?

php - 创建安全 key 哈希删除链接