moonwalker
moonwalker

Reputation: 1177

Laravel 5.6: Invoking eloquent relationships change the collection data

Is there a way to invoke eloquent relationship methods without changing the original eloquent collection that the method runs on? Currently I have to employ a temporary collection to run the method immutable and to prevent adding entire related record to the response return:

$result = Item::find($id);
$array = array_values($result->toArray());
$temp = Item::find($id);
$title = $temp->article->title;

dd($temp); //This prints entire article record added to the temp collection data.

array_push($array, $title);

return response()->json($array);

Upvotes: 0

Views: 1466

Answers (3)

Namoshek
Namoshek

Reputation: 6544

You are not dealing with collections here but with models. Item::find($id) will get you an object of class Item (or null if not found).

As far as I know, there is no way to load a relation without storing it in the relation accessor. But you can always unset the accessor again to delete the loaded relation (from memory).

For your example, this process yields:

$result = Item::find($id);
$title = $result->article->title;
unset($result->article);

return response()->json(array_merge($result->toArray(), [$title]));

The above works but is no very nice code. Instead, you could do one of the following three things:

  1. Use attributesToArray() instead of toArray() (which merges attributes and relations):

    $result = Item::find($id);
    return response()->json(array_merge($result->attributesToArray(), [$result->article->title]));
    
  2. Add your own getter method on the Item class that will return all the data you want. Then use it in the controller:

    class Item
    {
        public function getMyData(): array
        {
            return array_merge($this->attributesToArray(), [$this->article->title]);
        }
    }
    

    Controller:

    $result = Item::find($id);
    return response()->json($result->getMyData());
    
  3. Create your own response resource:

    <?php
    
    namespace App\Http\Resources;
    
    use Illuminate\Http\Resources\Json\JsonResource;
    
    class ItemResource extends JsonResource
    {
        public function toArray($request)
        {
            return [
                'id' => $this->id,
                'name' => $this->name,
                'title' => $this->article->title,
                'author' => $this->article->author,
                'created_at' => $this->created_at,
                'updated_at' => $this->updated_at,
            ];
        }
    }
    

    Which can then be used like this:

    return new ItemResource(Item::find($id));
    

The cleanest approach is option 3. Of course you could also use $this->attributesToArray() instead of enumerating the fields, but enumerating them will yield you security in future considering you might extend the model and do not want to expose the new fields.

Upvotes: 1

Jorge Rodr&#237;guez
Jorge Rodr&#237;guez

Reputation: 176

There is not as far as I know. When dealing with Model outputs, I usually construct them manually like this:

$item = Item::find($id);

$result = $item->only('id', 'name', 'description', ...);
$result['title'] = $item->article->title;

return $result; 

Should you need more power or a reusable solution, Resources are your best bet.

https://laravel.com/docs/5.6/eloquent-resources#concept-overview

Upvotes: 1

Anthony Aslangul
Anthony Aslangul

Reputation: 3847

I see two ways you can achieve that.

First, you can use an eloquent Resource. Basically it'll allow you to return exactly what you want from the model, so in your case, you'll be able to exclude the article. You can find the documentation here.

The second way is pretty new and is still undocumented (as fas i know), but it actually works well. You can use the unsetRelation method. So in your case, you just have to do:

$article = $result->article; // The article is loaded
$result->unsetRelation('article'); // It is unloaded and will not appear in the response

You can find the unsetRelation documentation here

Upvotes: 1

Related Questions