Laravel 将 1 列值作为数组加载关系

Laravel load relationship with 1 column values as array

Laravel 中有一种方法可以加载只有 1 列值的关系并将其作为数组吗?

我有 belongsToMany 关系,从 Page 模特到 CountryState 模特。

public function countries(){
   return $this->belongsToMany(Country::class, 'page_country', 'page_id', 'country_id');
}

public function states(){
   return $this->belongsToMany(State::class, 'page_state', 'page_id', 'state_id');
}

当我加载这些关系时,我需要将结果作为值数组 [1,2,3,4](相关模型的 ID)获取。换句话说,我只需要为我的 Page

加载 statescountries 中的 id

所以不是键值对:

[
   'states' => ['id' => 1, 'id' => 2, ...],
   'countries' => ['id' => 1, 'id' => 2, ...]
]

我想实现这个:

[
   'states' => [1, 2, ...],
   'countries' => [1, 2, ...]
]

我目前在做的是:

加载关系:

public function view(Page $page){
    
    $page->load([
        'countries:id',
        'states:id'
    ]);

    //Transform to get array of ids for each relation
    $page->countries->transform(function($item){
        return $item->id;
    });

    $page->states->transform(function($item){
        return $item->id;
    });

    //and return as json
    return response()->json($page)
}

也许有更好的方法来实现这一点?因为当我需要加载 10 个关系时,我需要 运行 10 个转换函数。

据我所知,这对于急切加载关系是不可能的。由于预先加载已经为每个关系执行了额外的数据库查询,因此数据库上没有额外的负载来执行此操作:

public function view(Page $page)
{    
    $result = $page->toArray();
    $result['countries'] = $page->countries()->pluck('id');
    $result['states'] = $page->states()->pluck('id');

    //and return as json
    return response()->json($result)
}

您似乎很担心必须执行此操作 10 次,因此如果您愿意,可以将其放入循环中。

public function view(Page $page)
{    
    $result = $page->toArray();
    $relationships = ['countries', 'states', ];
    foreach ($relationship as $rel) {
        $result[$rel] = $page->{$rel}()->pluck('id');
    }

    //and return as json
    return response()->json($result)
}

但实际上,您只编写一次代码,那么编写 10 次有什么问题?


您可能还想看看 Eloquent resources and collections;根据您的用例,它们可能会有所帮助。


最后一个建议是远离 Eloquent 并使用查询构建器:

public function view(int $page)
{
    $result = Page::select('pages.*')
        ->selectRaw('GROUP_CONCAT(countries.id) AS countries')
        ->selectRaw('GROUP_CONCAT(states.id) AS states')
        ->leftJoin('countries', 'countries.page_id', 'pages.id')
        ->leftJoin('states', 'states.page_id', 'pages.id')
        ->where('pages.id', $page)
        ->groupBy('pages.id')
        ->get();

    //and return as json
    return response()->json($result)
}

这样做的好处是可以大大减少对数据库的访问次数,但必须熟悉一些丑陋的 SQL 语法。它将 return 外国 ID 作为逗号分隔列表,您可以在客户端处理,或在 return 响应之前处理。

您可以为此使用 Accessor。将这些函数添加到您的 Page.php 模型中:

public function getCountryIdsAttribute() {
  return $this->countries->pluck('id');
}

public function getStateIdsAttribute() {
  return $this->states->pluck('id');
}

此外,如果您想在 JSON 响应中自动包含这些内容,请同时添加以下内容:

protected $appends = ['countryIds', 'stateIds'];

现在,您的代码将基本相同:

public function view(Page $page) {    
  $page->load([
    'countries:id',
    'states:id'
  ]);

  return response()->json($page);
}

您仍然希望为 $this->countries->pluck('id')$this->states->pluck('id') 加载 countriesstates 以在不调用 2 个额外查询的情况下运行,但是您的响应看起来像这个:

[
   'states' => ['id' => 1, 'id' => 2, ...],
   'countries' => ['id' => 1, 'id' => 2, ...],
   'country_ids' => [1, 2, ...],
   'state_ids' => [1, 2, ...]
]

如果您不想包含 statescountries,您只需将它们添加到 Page.php 模型中的 hidden

protected $hidden = ['countries', 'states'];`

这将导致:

[
   'country_ids' => [1, 2, ...],
   'state_ids' => [1, 2, ...]
]

您可以在此处查看有关 appendshidden 模型属性的更多信息:

https://laravel.com/docs/8.x/eloquent-serialization

这样做的主要缺点是您的字段在作为 JSON 对象返回时不再命名为 countriesstates,但这确实消除了额外迭代的需要,同时映射、手动调用覆盖等