Laravel多对多 - 意外的结果设置 - > select()

时间:2015-05-13 13:45:19

标签: php laravel eloquent laravel-5

我想知道是否有人可以提供帮助,因为我已经撞墙并仍在学习Laravel ORM。我可以解释为什么,当我跑:

public function locationTags(){
    return $this->hasMany('App\UserHasLocationTags', 'user_id')
        ->join('location_tags AS lt', 'lt.id', '=', 'location_tag_id');
}

我得到这个结果集:(剪断......)

{
    "id": 1,
    "created_at": "2015-05-13 13:04:56",
    "updated_at": "2015-05-13 13:04:56",
    "email": "REMOVED",
    "firstname": "REMOVED",
    "lastname": "REMOVED",
    "location_id": 0,
    "deleted_at": null,
    "permissions": [],
    "location_tags": [
        {
            "user_id": 1,
            "location_tag_id": 1,
            "id": 1,
            "created_at": "2015-05-13 13:06:28",
            "updated_at": "2015-05-13 13:06:28",
            "name": "Test Tag 0",
            "location_id": 1,
            "deleted_at": null
        },
        {
            "user_id": 1,
            "location_tag_id": 2,
            "id": 2,
            "created_at": "2015-05-13 11:40:21",
            "updated_at": "2015-05-13 12:56:13",
            "name": "Test Tag 123",
            "location_id": 1,
            "deleted_at": null
        }
    ]
}

哪个是王牌!但是,当我开始从location_tags join中选择我想要的列时,使用:

public function locationTags(){
    return $this->hasMany('App\UserHasLocationTags', 'user_id')
        ->join('location_tags AS lt', 'lt.id', '=', 'location_tag_id')
        ->select('lt.id', 'lt.name');
}

我最终得到了:

{
    "id": 1,
    "created_at": "2015-05-13 13:04:56",
    "updated_at": "2015-05-13 13:04:56",
    "email": "REMOVED",
    "firstname": "REMOVED",
    "lastname": "REMOVED",
    "location_id": 0,
    "deleted_at": null,
    "permissions": [],
    "location_tags": []
}

有人可以解释发生了什么吗?并且可能指出我正确的方向来限制选择?谢谢!

更新 我也试过了:

        $query = \App\User::with(['permissions', 'locationTags' => function($query){
            $query->select('lt.id', 'lt.name');
        }]);

返回相同的结果:(

2 个答案:

答案 0 :(得分:6)

想出来。这里的关键是您必须包含Laravel可用于映射结果集的至少一个键的select()值。就我而言,它是user_id,就像这样:

public function locationTags(){
    return $this->hasMany('App\UserHasLocationTags', 'user_id')
        ->join('location_tags AS lt', 'lt.id', '=', 'location_tag_id')
        ->select('user_id', 'lt.name', 'location_tag_id');
}

然后返回更好的结果集:

{
    "id": 1,
    "created_at": "2015-05-13 13:04:56",
    "updated_at": "2015-05-13 13:04:56",
    "email": "REMOVED",
    "firstname": "REMOVED",
    "lastname": "REMOVED",
    "location_id": 0,
    "deleted_at": null,
    "permissions": [],
    "location_tags": [
        {
            "user_id": 1,
            "name": "Test Tag 0",
            "location_tag_id": 1
        },
        {
            "user_id": 1,
            "name": "Test Tag 123",
            "location_tag_id": 2
        }
    ]
}

希望将来可以帮助某人,因为它让我猜不到几个小时。

答案 1 :(得分:2)

对不起,你在这里走了一段路。关系定义应该只定义关系。它是一个强大的功能,支持ORM的许多其他方面。你在这里做的或多或少是构建自定义查询,严重限制了关系的有效性。

直到最近,正确的方式看起来像这样。

// class User
public function userHasLocationTags() {
    $this->hasMany('App\UserHasLocationTags', 'user_id');
}

// class UserHasLocationTags
public function locationTags() {
    $this->hasMany('App\LocationTags', 'location_tag_id');
}

你会急切地加载所有结果。

$user = User::where('id', 1)->with('userHasLocationTags.locationTags')->first();

上面的代码产生了3个查询。一个获取User,一个获取所有UserHasLocationTags,一个获取所有LocationTags。这可能看起来很浪费,但请考虑以下几点。

$users = User::take(100)->with('userHasLocationTags.locationTags')->get();

同样,这只是3个查询,但现在您已经为100个用户加载了所有位置标记。

但我可以看到你是一个注重效率的人,加载所有中间关系和整个嵌套层次结构可能并不适合你。好消息! Laravel 5为这种情况添加了另一种关系类型。 hasManyThrough(向下滚动以找到它)。

  

有很多通过

     

“有很多通过”关系为通过中间关系访问远距离关系提供了方便的捷径。例如,Country模型可能有很多Post通过User模型。

所以在你的情况下它可能看起来像这样......

// class User
public function locationTags()
{
    return $this->hasManyThrough('App\LocationTags', 'App\UserHasLocationTags');
}

$users = User::take(100)->with('locationTags')->get();

现在我们归结为两个问题。如果您认为需要进一步优化此选项并仅选择特定列,则可以修改预先加载。

$users = User::take(100)->with(['locationTags' => function ($query)
{
    $query->select('user_id', 'name', 'location_tag_id');
})->get();

如果你经常这样做,你应该把它包裹在scope中。