【问题标题】:Laravel - Eloquent - Dynamically defined relationshipLaravel - Eloquent - 动态定义的关系
【发布时间】:2017-01-19 18:12:24
【问题描述】:

是否可以动态设置模型的关系?例如,我有模型Page,我想在不实际更改其文件的情况下向其添加关系banners()?这样的事情是否存在:

Page::createRelationship('banners', function(){
    $this->hasMany('banners');
});

或者类似的东西?由于它们是使用魔术方法获取的,也许我可以动态添加关系?

谢谢!

【问题讨论】:

  • 使用多态关系和特征来完成你想要的,看看 laravel 文档中的内容
  • 如果我没记错的话,您还需要在原始Page 模型中定义它们吗?我正在寻找一种无需编辑文件即可添加这种关系的方法
  • 我可以看到Model 类上有一个setRelation 方法,但是,它不是静态的,因此只能在实例上调用。有没有类似的东西我可以使用,所以它会自动设置所有创建实例的关系?
  • 是的,我可以举个例子。但是,我需要在整个模型的范围内定义关系,所以每当创建一个新实例时,$model->banners() 就已经可以访问了。到目前为止,我还没有找到解决方案,所以我自己正在研究解决方案。

标签: php laravel orm eloquent


【解决方案1】:

您必须记住一些事情,Eloquent 关系是关系数据库关系的模型(即 MySQL)。 所以,我提出了两种方法。

好的

如果你想用数据库中的索引和外键实现功能齐全的 Eloquent 关系,你可能想动态地改变 SQL 表。

例如,假设您已经创建了所有模型并且不想动态创建它们,您只需更改 Page 表,添加一个名为“banner_id”的新字段,对其进行索引并引用“banner_id”字段在横幅表上。

然后您必须写下并支持您将使用的 RDBMS。 之后,您可能希望包括对迁移的支持。如果是这种情况,您可以将这些表更改存储在数据库中以供进一步回滚。

现在,对于 Eloquent 支持部分,您可以查看 Eloquent Model Class

请注意,对于每种关系,您都有一个 subyacent 模型(都可以找到 here,这实际上是您在关系方法中返回的内容:

public function hasMany($related, $foreignKey = null, $localKey = null)
{
    $foreignKey = $foreignKey ?: $this->getForeignKey();
    $instance = new $related;
    $localKey = $localKey ?: $this->getKeyName();
    return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
}

因此,您必须在模型中定义一个接受关系类型和模型的方法,创建一个新的 HasMany(如果 hasMany 是所需的关系)实例,然后返回它。

这有点复杂,所以你可以使用:

简单

您可以创建一个中间模型(即 PageRelationship)来存储 Page 和其他模型之间的所有关系。可能的表架构可能是:

+-------------+---------+------------------+-------------+
| relation_id | page_id | foreign_model_id | model_class |
+-------------+---------+------------------+-------------+
|           1 |       2 |              225 | Banner      |
|           2 |       2 |              223 | Banner      |
|           3 |       2 |               12 | Button      |
+-------------+---------+------------------+-------------+

然后您可以检索到给定页面的所有动态相关模型。这里的问题是您实际上在模型和页面之间没有任何真正的 RDBMS 关系,因此您可能必须进行多次繁重的查询来加载相关的模型,而且更糟糕的是,您必须管理自己的数据库一致性(即,删除或更新“225”横幅也应删除或更新 page_relationship_table 中的行)。逆向关系也会很头疼。

结论

如果项目很大,这取决于它,并且您无法通过继承来实现其他模型的模型,您应该使用好的方法。否则,您应该重新考虑您的应用设计,然后决定是否选择第二种方法。

【讨论】:

  • 感谢您的广泛回答。我可以看到如何使用这两种解决方案,但如果我没记错的话,两者都需要编辑 Page 模型类?由于我的系统是完全模块化的,我试图找到一种模块扩展其他模块的方法,而不会使模型本身过于复杂。
  • 您必须对其进行修改以使其能够处理动态关系功能,因为它不是本机实现的,但您以后不必更改它。模块化系统的正确做法是具有继承模型结构,因此 Banner 模型扩展了 PageElement 模型,并构建了 Page 和 PageElement 之间的关系。不管怎样,做模块化的东西will complicate your models
  • 我将拥有自己的 Model 类扩展,由所有模型扩展以具有自定义功能,或者可能将动态关系功能添加到 laravel 框架并提交拉取请求,以防更多人会发现这很有用。一旦找到明确的解决方案,将发布答案。
  • 好的。你能告诉我更多关于你想要的模块化功能以及你为什么需要它的信息吗?我正在考虑几种情况,这些情况可以满足您的设计需求,并且所有这些情况都通过版本更新或代码编辑获得了解决方案。
  • 基本上系统上的所有功能都将通过模块实现。我们部署的每个系统实例都将启用不同的模块,以及对某些模块的一些更改。所有模块都将在主系统上进行版本控制,但是,每个实例都会有选项来编辑它们。这些模块将由多个开发人员开发,因此我正在尝试找到一个解决方案,该解决方案允许我在不同系统下安装不同的模块,同时对系统其余部分的影响最小。
【解决方案2】:

您可以使用macro 调用您的动态关系,如下所示:

您应该在您的服务提供商启动方法中编写此代码。

\Illuminate\Database\Eloquent\Builder::macro('yourRelation', function () {  
     return $this->getModel()->belongsTo('class'); 
});

【讨论】:

  • 这很棒。但是,您确实失去了查询与访问者的关系的能力。即Model::first()->yourRelation你必须使用Model::first()->yourRelation()->get()
【解决方案3】:

我已经为这个i-rocky/eloquent-dynamic-relation添加了一个包

如果有人仍在寻找解决方案,这里有一个。如果您认为这是一个坏主意,请告诉我。

trait HasDynamicRelation
{
    /**
     * Store the relations
     *
     * @var array
     */
    private static $dynamic_relations = [];

    /**
     * Add a new relation
     *
     * @param $name
     * @param $closure
     */
    public static function addDynamicRelation($name, $closure)
    {
        static::$dynamic_relations[$name] = $closure;
    }

    /**
     * Determine if a relation exists in dynamic relationships list
     *
     * @param $name
     *
     * @return bool
     */
    public static function hasDynamicRelation($name)
    {
        return array_key_exists($name, static::$dynamic_relations);
    }

    /**
     * If the key exists in relations then
     * return call to relation or else
     * return the call to the parent
     *
     * @param $name
     *
     * @return mixed
     */
    public function __get($name)
    {
        if (static::hasDynamicRelation($name)) {
            // check the cache first
            if ($this->relationLoaded($name)) {
                return $this->relations[$name];
            }

            // load the relationship
            return $this->getRelationshipFromMethod($name);
        }

        return parent::__get($name);
    }

    /**
     * If the method exists in relations then
     * return the relation or else
     * return the call to the parent
     *
     * @param $name
     * @param $arguments
     *
     * @return mixed
     */
    public function __call($name, $arguments)
    {
        if (static::hasDynamicRelation($name)) {
            return call_user_func(static::$dynamic_relations[$name], $this);
        }

        return parent::__call($name, $arguments);
    }
}

在您的模型中添加此特征,如下所示

class MyModel extends Model {
    use HasDynamicRelation;
}

现在您可以使用以下方法添加新的关系

MyModel::addDynamicRelation('some_relation', function(MyModel $model) {
    return $model->hasMany(SomeRelatedModel::class);
});

【讨论】:

  • 我最终为我的项目做了同样的事情,效果很好!干得好,谢谢。
【解决方案4】:

以防万一有人在寻找 Laravel 8 的答案:

假设我在模型的单一方法中定义我的关系:

public function relationships()
{
    return [
        'user' => $this->belongsTo(User::class, 'user_id'),
    ];
}

现在,在我的应用服务提供商中,我可以使用resolveRelationUsing 方法。我通过遍历模型文件夹并检查包含上述方法的所有模型来完成此操作:

    foreach ((new Filesystem)->allFiles(app_path('Models')) as $file) {
        $namespace = 'App\\Models\\' . str_replace(['/', '.php'], ['\\', ''], $file->getRelativePathname());
        $class = app($namespace);

        if (method_exists($class, 'relationships')) {
            foreach ($class->relationships() as $key => $relationship) {
                $class->resolveRelationUsing($key, function () use ($class, $key) {
                    return $class->relationships()[$key];
                });
            }
        }
    }

【讨论】:

    【解决方案5】:

    从 laravel 7 开始,正式支持动态关系。您可以使用 Model::resolveRelationUsing() 方法。

    https://laravel.com/docs/7.x/eloquent-relationships#dynamic-relationships

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-24
      • 2018-06-18
      • 2017-04-02
      • 2023-04-01
      • 2020-12-14
      • 1970-01-01
      • 2018-01-21
      • 1970-01-01
      相关资源
      最近更新 更多