Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

DanielRønfeldt's avatar

Filter out unneeded attributes on hasManyThrough relationship query

As the title says, I need to filter out certain attributes on the related model after querying it via a hasManyThrough relationship.

More specifically, when querying all the Photos of the PhotoGallery that belongs to a specific Car, I'm getting a Collection of Photos, each of which with extra attributes that I'd prefer not to retrieve. Here are my models.

<?php
// app/Car.php

namespace App;

use Illuminate\Database\Eloquent;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Car extends Model
{
  protected $fillable = ['make', 'model', 'year', 'price'];
  
  public function photoGallery() : HasOne {
    return $this->hasOne( PhotoGallery::class );
  }
  
  public function photos() : HasManyThrough {
    return $this->hasManyThrough( Photo::class, PhotoGallery::class );
  }
}
<?php
// app/PhotoGallery.php

namespace App;

use Illuminate\Database\Eloquent;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class PhotoGallery extends Model
{
  protected $fillable = ['car_id', 'max_photos'];
  
  public function photos() : HasMany {
    return $this->hasMany( Photo::class );
  }

  public function car() : BelongsTo {
    return $this->belongsTo( Car::class );
  }
}
<?php
// app/Photo.php

namespace App;

use Illuminate\Database\Eloquent;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Photo extends Model
{
  protected $fillable = ['photo_gallery_id', 'title', 'order'];
  
  public function photoGallery() : BelongsTo {
    return $this->belongsTo( PhotoGallery::class );
  }
}

The show() method of the CarController looks like this.

<?php

// app/Http/Controllers/CarController.php
// namespace, imports etc omitted for brevity

class CarController extends Controller
{
  public function show( Car $car ) {
    $make = $car->make;
    $model = $car->model;
    $year = $car->year;
    $price = $car->price;
    
    $photos = $car->photos; // HasManyThrough relationship on Car model
    dd( $photos ); // >>>>>> returns a collection of Photos, as expected, but each Photo has too many attributes
    
    return view( 'cars.show', compact([
      'make',
      'model',
      'year',
      'price',
      'photos',
    ]) );
  }
}

And here's the result of dumping of the $photos Collection.

Illuminate\Database\Eloquent\Collection {#1538 ▼
  #items: array:4 [▼
    0 => App\Photo {#1539 ▼
      #fillable: array:3 [▶]
      #connection: "mysql"
      #table: "photos"
      #primaryKey: "id"
      #keyType: "int"
      +incrementing: true
      #with: []
      #withCount: []
      #perPage: 15
      +exists: true
      +wasRecentlyCreated: false
      #attributes: array:8 [▼
        "id" => 113
        "photo_gallery_id" => 3 // <<<<< This must be gone from the result
        "filename" => "1611828914_HVqrHEKJxtonghqNotcVJFxq2LAxmPXTDSZUa43r.jpg"
        "title" => "A temporary title here. Needs to be handled later."
        "order" => 1
        "created_at" => "2021-01-28 10:15:15" // <<<<< This must be gone from the result
        "updated_at" => "2021-01-28 10:15:15" // <<<<< This must be gone from the result
        "laravel_through_key" => 35 // <<<<< This must be gone from the result
      ]
      #original: array:8 [▶]
      #changes: []
      #casts: []
      #dates: []
      #dateFormat: null
      #appends: []
      #dispatchesEvents: []
      #observables: []
      #relations: []
      #touches: []
      +timestamps: true
      #hidden: []
      #visible: []
      #guarded: array:1 [▶]
    }
    1 => App\Photo {#1540 ▶}
    2 => App\Photo {#1541 ▶}
    3 => App\Photo {#1542 ▶}
  ]
}

Hope I'm making sense. Any thoughts?

0 likes
14 replies
CorvS's avatar

@danielroenfeldt Anything wrong with using select()?

$photos = $car->photos()->select('a', 'b', ...)->get();
// or
$photos = $car->photos()->get(['a', 'b', ...]);
DanielRønfeldt's avatar

@nimrod your solution is almost working. By that I mean:

  1. I'm getting the fields I need, except for each photo's id field, which is also necessary. Adding this field into the array results in the following error:
Illuminate \ Database \ QueryException (23000)
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'id' in field list is ambiguous (SQL: select `id`, `filename`, `title`, `order`, `photo_galleries`.`car_id` as `laravel_through_key` from `photos` inner join `photo_galleries` on `photo_galleries`.`id` = `photos`.`photo_gallery_id` where `photo_galleries`.`car_id` = 35)
  1. First issue aside, I'm still getting the laravel_through_key field, which I find to be a security risk, because it's exposing the Car's id to the outer world. Any thoughts on removing this one?
DanielRønfeldt's avatar

Okay, I'm one step closer :)

Being explicit in specifying the ID column helped. Never knew it was such a simple approach. Thank you!

However, makeHidden() seems to have zero effect on the query result. Moreover, I added this annoying laravel_through_key column to the hidden attributes as per your suggestion to each and all of my models (Car, PhotoGallery, as well as Photo). Yet again, nothing changed. What could be the reason?

CorvS's avatar

@danielroenfeldt makeHidden returns the new collection (without the specified attributes), did you consider that?

DanielRønfeldt's avatar

Maybe I misunderstood your question, but here's my code

$car_photos = $car->photos()->get([  'photos.id as photo_id', 'filename', 'title', 'order' ]);
$car_photos->makeHidden('laravel_through_key');
dd( $car_photos );
CorvS's avatar

@danielroenfeldt It has to be

$car_photos = $car_photos->makeHidden('laravel_through_key');

Additionally you could simplify 'photos.id as photo_id' to 'photos.id as id' if you like.

1 like
DanielRønfeldt's avatar

Same result 🤷‍♂️

$car_photos = $car->photos()->get([  'photos.id as id', 'filename', 'title', 'order' ]);
$car_photos = $car_photos->makeHidden('laravel_through_key');
dd( $car_photos );

Which outputs

Illuminate\Database\Eloquent\Collection {#1520 ▼
  #items: array:4 [▼
    0 => App\Photo {#1521 ▼
      #fillable: array:3 [▶]
      #hidden: array:2 [▶]
      #connection: "mysql"
      #table: "photos"
      #primaryKey: "id"
      #keyType: "int"
      +incrementing: true
      #with: []
      #withCount: []
      #perPage: 15
      +exists: true
      +wasRecentlyCreated: false
      #attributes: array:5 [▼
        "photo_id" => 113
        "filename" => "1611828914_HVqrHEKJxtonghqNotcVJFxq2LAxmPXTDSZUa43r.jpg"
        "title" => "A temporary title here. Needs to be handled later."
        "order" => 1
        "laravel_through_key" => 35
      ]
      #original: array:5 [▶]
      #changes: []
      #casts: []
      #dates: []
      #dateFormat: null
      #appends: []
      #dispatchesEvents: []
      #observables: []
      #relations: []
      #touches: []
      +timestamps: true
      #visible: []
      #guarded: array:1 [▶]
    }
    1 => App\Photo {#1522 ▶}
    2 => App\Photo {#1523 ▶}
    3 => App\Photo {#1524 ▶}
  ]
}
CorvS's avatar

@danielroenfeldt What Laravel version are you using? Either it's related to that or you have a bug in your code somewhere else, because for me both suggestions work just fine and I don't get the laravel_through_key in the first place (car_id instead).

Nvm, since you use dd() it's still visible. It should be hidden in your result tho.

1 like
DanielRønfeldt's avatar

Output from php artisan --version

Laravel Framework 6.18.43

On a side note, I remember reading somewhere that ResourceCollections could be used to filter out unnecessary fields on items belonging to a given Collection. But I can't recall where I read that, nor can I find that article again. Any thoughts on that?

DanielRønfeldt's avatar
Level 10

Oddly enough, using a PhotoResource did not fix the issue. I just ended up "spoofing" the car identifier by simply iterating over the collection's items with the help of the each() method of the Collection class, and simply setting the laravel_through_key field's value to an empty string. Obviously not the most elegant solution, but who cares, as long as it's removing the potentially vulnerable data 😁

$car_photos = $car->photos()->get([  'photos.id as id', 'filename', 'title', 'order' ]);
$car_photos->each(function( $photo ) {
  $photo->laravel_through_key = '';
});
dd( $car_photos );
Illuminate\Support\Collection {#1583 ▼
  #items: array:4 [▼
    0 => App\Photo {#1535 ▼
      #fillable: array:3 [▶]
      #hidden: array:2 [▶]
      #connection: "mysql"
      #table: "photos"
      #primaryKey: "id"
      #keyType: "int"
      +incrementing: true
      #with: []
      #withCount: []
      #perPage: 15
      +exists: true
      +wasRecentlyCreated: false
      #attributes: array:5 [▼
        "id" => 113
        "filename" => "1611828914_HVqrHEKJxtonghqNotcVJFxq2LAxmPXTDSZUa43r.jpg"
        "title" => "A temporary title here. Needs to be handled later."
        "order" => 1
        "laravel_through_key" => "" // <<<<< UNSAFE DATA REMOVED
      ]
      #original: array:5 [▶]
      #changes: []
      #casts: []
      #dates: []
      #dateFormat: null
      #appends: []
      #dispatchesEvents: []
      #observables: []
      #relations: []
      #touches: []
      +timestamps: true
      #visible: []
      #guarded: array:1 [▶]
    }
    1 => App\Photo {#1536 ▶}
    2 => App\Photo {#1537 ▶}
    3 => App\Photo {#1538 ▶}
  ]
}

@nimrod many thanks for your help! It's much appreciated.

chaudigv's avatar

Use makeHidden() method.

$photos = $car->photos->makeHidden(['created_at', 'updated_at']); // these columns will be removed
DanielRønfeldt's avatar

For some reason, this doesn't seem to be affecting the query at all. I'm suspecting the hasManyThrough relationship query might have something to do with it. Here's the result I'm getting (identical to the result of dd-ing $memorialprofile->photos):

Illuminate\Database\Eloquent\Collection {#1524 ▼
  #items: array:4 [▼
    0 => App\Photo {#1525 ▼
      #fillable: array:3 [▶]
      #connection: "mysql"
      #table: "photos"
      #primaryKey: "id"
      #keyType: "int"
      +incrementing: true
      #with: []
      #withCount: []
      #perPage: 15
      +exists: true
      +wasRecentlyCreated: false
      #attributes: array:8 [▼
        "id" => 113
        "photo_gallery_id" => 3
        "filename" => "1611828914_HVqrHEKJxtonghqNotcVJFxq2LAxmPXTDSZUa43r.jpg"
        "title" => "A temporary title here. Needs to be handled later."
        "order" => 1
        "created_at" => "2021-01-28 10:15:15"
        "updated_at" => "2021-01-28 10:15:15"
        "laravel_through_key" => 35
      ]
      #original: array:8 [▶]
      #changes: []
      #casts: []
      #dates: []
      #dateFormat: null
      #appends: []
      #dispatchesEvents: []
      #observables: []
      #relations: []
      #touches: []
      +timestamps: true
      #hidden: array:2 [▶]
      #visible: []
      #guarded: array:1 [▶]
    }
    1 => App\Photo {#1526 ▶}
    2 => App\Photo {#1527 ▶}
    3 => App\Photo {#1528 ▶}
  ]
}

Please or to participate in this conversation.