2

There is a way in Laravel to load a relationship with only 1 column value and to have it as an array?

I have a belongsToMany relationship from Page Model to Country, State models.

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

When I load these relationships I need to get the result as an array of values [1,2,3,4] (ids of the related model). In other words, I need to load only id of states and countries for my Page

So instead of key-value pair:

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

I want to achieve this:

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

What I'm doing currently is:

Load the relationship:

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

Maybe there is a better way to achieve this? Because when I need to load 10 relationships, I need to run 10 transforms functions.

7
  • 4
    There is also $page->countries->pluck('id')->toArray() or $page->countries->modelKeys() for collections laraveldaily.com/… Commented Nov 17, 2021 at 16:14
  • Hi @SuperDJ, I will still need to write this 10 times, if I load 10 relationships. Plus is not modifying the relationship, but instead, returns a new value, so I will have to marge it back with the parent. Commented Nov 19, 2021 at 7:45
  • 1
    You're going to need to clarify what your problem is; there's a reason nobody's answered the question yet. Sticking a bounty on without improving the question isn't going to help you get a good answer. Commented Dec 3, 2021 at 15:21
  • Hi @miken32 what exactly is not clear? Commented Dec 3, 2021 at 15:21
  • 1
    Hi, @miken32 added more descriptions, thank you for assisting. Commented Dec 3, 2021 at 15:37

3 Answers 3

2
+50

That's not possible with eager loading relationships, to the best of my knowledge. Since eager loading already does an extra database query for each relationship, there's no additional load on the database to do this:

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

You seem rather concerned about having to do this 10 times, so you could put it in a loop if you like.

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

But really, you only write the code once so what's the problem with doing it 10 times?


You may also want to look at Eloquent resources and collections; depending on your use case they might be helpful.


One last suggestion is to get away from Eloquent and use query builder:

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

This has the advantage of greatly reducing the number of hits to your database at the expense of having to be familiar with some ugly SQL syntax. It will return the foreign IDs as a comma separated list, which you can deal with on the client side, or before returning the response.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you Miken32, I guess "ugly" SQL is the best choice.
1

You could use an Accessor for this. Add these functions to your Page.php model:

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

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

Additionally, if you want to automatically include these in a JSON response, add the following as well:

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

Now, your code would be mostly the same:

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

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

You would still want to eager load countries and states for $this->countries->pluck('id') and $this->states->pluck('id') to function without calling 2 additional queries, but your Response would then look like this:

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

If you didn't want to include states and countries, you'd simply add them to hidden in your Page.php Model:

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

Which would result in:

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

You can see more about appends and hidden Model properties here:

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

The main drawback to this is that your fields are no longer named countries and states when returned as a JSON object, but this does remove the need for additional iterations while mapping, manually calling overrides, etc.

Comments

0

//i am in Model where states are listed in array

public function countries() 
{
 return Countries::find($this->states);
}

// I am in blade

@foreach($item->countries() as $countries)
    {{ $countries->name.', '}}
@endforeach

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.