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();