下面的函数允许我检索给定模型类型的相关条目,这些条目具有例如与其相关联的重叠标签.

<?php

public function getRelated($entity, $fields = [], $relations = []) {
    $related = new EloquentCollection();

    // get models of same entity type that have relations in common

    if (count($relations)) {
        foreach ($relations as $relation) {
            // check if the current model in use has any of the currently iterated relationship at all
            if ($entity->{$relation}()->exists()) {
                // if yes, get all the ids of the related models, for instance tags
                $relationIds = $entity->{$relation}()->get()->pluck('id')->toArray();

                // now, get all same type models except the current one
                $relationEntities = $this->entity::whereNot('id', $entity->id)
                    // also except the ones we already found
                    ->whereNotIn('id', $related->modelKeys())
                    // and only get those which also have current relationship (e.g. tags)
                    ->whereHas($relation, function (Builder $query) use($relationIds, $relation) {
                        $query->whereIn($relation . '.id', $relationIds);
                    })
                    ->get()
                    ->sortKeysDesc()
                    ->take(10);

                $related = $related->concat($relationEntities->except($related->modelKeys()));
            }
        }
    }
}

这样做效果很好.例如,我可以加载一篇博客文章,并通过调用以下命令来显示具有一些相同标签的相关博客文章:

$post = Post::find(1);
$related = $this->getRelated($post, [], ['tags']);

现在,我还需要尊重除标记之外的其他类型,因此$relations是一个数组:"向我展示具有任何这些标记、类别等共同之处的相关博客文章."

我的第一个问题是在子查询中.I had to hardcode the id in the relation table:

$query->whereIn('id', $relationIds);
had to be
$query->whereIn($relation . '.id', $relationIds);

所以现在我的问题是,$relation . '.id'并不是适用于所有情况.关系fooBars()期望foo_bar.id,但看起来是fooBar.id.

没有自动的方法让Laravel根据我当前查询的关系自动知道列名吗?如果不是,我必须为我要查询的每个关系定义关系表的列名(Id).我是不是漏掉了什么?

或者我可以以某种方式加载关系并查看数据库方案?这里的最佳实践是什么?

一种方法是使用蛇形shell ($query->whereIn(Str::of($relation)->snake() . '.id', $relationIds);),但这感觉既模糊又肮脏.

所有这些都是多态的MoryToMany/MorphedByMany.


编辑:为了更好地理解,我添加了一个我的模型看起来像什么的例子.它们都使用默认命名约定.同样,我想获得相关的帖子,要么有一些共同的标签,要么有共同的FooBars.

<?php

class Post extends Entity
{
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }

    public function fooBars()
    {
        return $this->morphToMany(FooBar::class, 'foo_barable');
    }    
}

class Tag extends Entity
{
    public function posts()
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }
}

class FooBar extends Entity
{
    public function posts()
    {
        return $this->morphedByMany(Post::class, 'foo_barable');
    }
}

感谢@KGG的回答,我想出了以下几点:

<?php

if (count($relations)) {
    foreach ($relations as $relation) {
        if ($entity->{$relation}()->exists()) {
            $relationIds = $entity->{$relation}()->get()->modelKeys();
            $relatedClass = get_class($entity->$relation()->getRelated());
            $relationPrimaryKey = ($instance = new $relatedClass)->getQualifiedKeyName();

            $relationEntities = $this->entity::whereNot('id', $entity->id)
                ->whereNotIn('id', $related->modelKeys())
                ->whereHas($relation, function (Builder $query) use($relationIds, $relationPrimaryKey) {
                    $query->whereIn($relationPrimaryKey, $relationIds);
                })
                ->get()
                ->sortKeysDesc()
                ->take(10)
                ;

            $related = $related->concat($relationEntities->except($related->modelKeys()));
        }
    }
}

推荐答案

I made a mini version of your code to show concept:您可以在此基础上进行构建,以加载内部关系或对加载的关系进行更改.

$post = Post::first();
$related = $this->getRelated($post, ['tags', 'tags', 'tags', 'brands', 'somethingStrange']);

The getRelated function:

public function getRelated($entity, $relations = []) {
    if (count($relations)) {
        array_unique($relations);
    
        $verifiedRelationships = [];
        $verifiedRelationshipsPrimaryKey = [];
    
        foreach ($relations as $relation) {
            //If this method exists in the parent class continue
            if(method_exists($entity, $relation)) {
                //add the relation
                array_push($verifiedRelationships, $relation);
                //add the relation primary_id
                array_push($verifiedRelationshipsPrimaryKey, Str::lower(Str::singular(Str::snake(Str::studly($relation)))).'_id');
            }
        }
        
        $entity->load($verifiedRelationships);
    }
}

这将自动加载在该类中找到的所有相关方法.

Few notes:

  1. $verifiedRelationships将返回任何找到的方法['tags', 'brands', 'somethingStrange']
  2. $verifiedRelationshipsPrimaryKey将返回['tag_id', 'brand_id', 'something_strange_id']
  3. Laravel在模型上有一个函数,它使用getKeyName();来获得密钥
  4. Laravel在模型上有一个函数,它使用getQualifiedKeyName()返回限定的关系ID

Example of $model Primary Key inside the foreach loop:

$relatedClass = get_class($entity->$relation()->getRelated());
                    
$primaryKey = ($instance = new $relatedClass)->getKeyName();

//returns "id"
            

Example of the $relationship Primary key inside the foreach loop:

$relatedClass = get_class($entity->$relation()->getRelated());
                    
$primaryKey = ($instance = new $relatedClass)->getQualifiedKeyName();

//returns "post_brands.id" 

请测试这一点,如果你想要改变或有其他关系或澄清的 case ,我很乐意调整代码,直到它为你工作,我真的不知道你的关系,如果我可以得到一个数组示例,它会更容易.

Laravel相关问答推荐

如何使用 laravel 更新单个值

Laravel Livewire 组件属性在 div 类中为空

未找到 apiResource 404 中的变量

RuntimeException 未安装 Zip PHP 扩展

如何防止 Laravel 路由被直接访问(即非 ajax 请求)

Blade引擎:打印三重花括号

Laravel - 加载常见的页眉和页脚以查看

如何在 NetBeans 中添加带有点 (blade.php) 的自定义文件扩展名?

Laravel Session 总是改变 Laravel 5.4 中的每个刷新/请求

控制器中的 Laravel Auth 用户 ID

如何仅从 laravel FormRequest 中获取经过验证的数据?

Laravel 说 Auth guard [] 没有定义

命令未定义异常

在 Laravel 应用程序中在哪里指定应用程序版本?

laravel 5.1 在没有 VM 重启的情况下看不到作业(job)文件的更改

Laravel - DecryptException:'MAC 无效'

在 vue.js 中使用 Laravel 的Gate/Authorization

为什么`catch (Exception $e)` 不处理这个`ErrorException`?

Laravel 验证用户名不允许有空格

Laravel:Redis 无法建立连接:[tcp://127.0.0.1:6379]