Poznajemy relację polimorficzną jeden do jednego i podstawowe sposoby przeszukiwania takich modeli. Do dzieła.

Ok, tworzymy model Image, z flagą -mfs. Oto migracja:

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('images', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('path')->unique(true);
            $table->morphs('imageable');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('images');
    }
};

Wszystko stanie się jasne, jak już zaczniemy tego używać. Migrujemy i ustawiamy model Image:

class Image extends Model
{
    use HasFactory;

    protected $fillable = ['path'];

    public function imageable()
    {
        return $this->morphTo();
    }
}

Teraz pamiętając o importach w Movie i Review dodajemy:

public function image()
        {
        return $this->morphOne(Image::class, 'imageable');
        }

Ok, zróbmy sobie komendę aby dodać Image do filmu i użyjmy jej:

Artisan::command('img-for-movie {id}', function (int $id) {

    $m = Movie::findOrFail($id);
    $img = new Image();
    $img->path = "/public/movieimg{$id}.jpg";
    $m->image()->save($img);

    $this->comment("done");

});

Teraz do bazy danych. Zobaczmy tam path, imageable_id (id naszego filmu) oraz imageable_type, którym jest pełen namespace klasy Movie (App\Models\Movie)

Tak ten cały mechanizm działa. Ok następna komenda:

Artisan::command('movie-with-img {id}', function (int $id) {

    $m = Movie::with('image')->findOrFail($id);
    
    $this->comment("Movie title: {$m->title} ");
    $this->comment("Image: {$m->image->path}");
  
});

Widzimy, że Eloquent daje radę zaciągnąć. Dobra, teraz dla Review, mam nadzieję, że tam w modelu też dodaliśmy morphOne (czyli hasOne polimorficzne):

Artisan::command('img-for-review {id}', function (int $id) {

    $r = Review::findOrFail($id);
    $img = new Image();
    $img->path = "/public/reviewimg{$id}.jpg";
    $r->image()->save($img);

    $this->comment("done");
 
});

My tych komend mamy używać (i sprawdzać w bazie danych), nie tylko je wklejać, mam nadzieję, że to oczywiste. Ok, teraz próba Eloquenta:

Artisan::command('review-with-img {id}', function (int $id) {

    $r = Review::with('image')->findOrFail($id);
    
    $this->comment("Review author: {$r->author} ");
    $this->comment("Image: {$r->image->path}");

});

Teraz proszę się tym pobawić, pododawać, a następnie pomyśleć jak z fasadą DB zaciągnąć wszystkie obrazki, ale tylko te, które należą do recenzji. Zobaczenie bazy danych w phpMyAdmin powinno nas naprowadzić na dobry trop.

Artisan::command('review-images', function () {

    $images = DB::table('images')->where("imageable_type", Review::class)->get();
    
    foreach($images as $i){
        $this->comment("Image path: {$i->path}");
    }
    
});

Ok, skoro wiemy jak to działa pod spodem, to możemy zacząć się uczyć Eloquenta, który ma świetne metody i robi wiele za nas. Zobaczmy na tę komendę:

Artisan::command('review-images2', function () {

    $images = Image::whereHasMorph(
        'imageable',
        [Review::class]
    )->get();
    
    foreach($images as $i){
        $this->comment("Image path: {$i->path}");
    }
    
});

A teraz obrazki dla filmów:

Artisan::command('movie-images', function () {

    $images = Image::whereHasMorph(
        'imageable',
        [Movie::class]
    )->get();
    
    foreach($images as $i){
        $this->comment("Image path: {$i->path}");
    }
    
});

A teraz wszystkie, pokazuję jak można użyć tablicy jako drugiego argumentu:

Artisan::command('all-images', function () {

    $images = Image::whereHasMorph(
        'imageable',
        [Movie::class, Review::class]
    )->get();
    
    foreach($images as $i){
        $this->comment("Image path: {$i->path}");
    }
    
});

To i tak uproszczona wersja whereHasMorph, tam w trzecim argumencie może być callback przyjmujący query builder. Przykład z dokumentacji:

// Retrieve comments associated to posts or videos with a title like code%...

$comments = Comment::whereHasMorph(
    'commentable',
    [Post::class, Video::class],
    function (Builder $query) {
        $query->where('title', 'like', 'code%');
    }
)->get();