接下來要講的是Eloquent的Has One Through/Has Many Through的關係,這兩個關係相對來說少用,但是在三個Model的狀況時,兩個不連結的Model可以透過中間Model而連結起來,提供便利的效果。
我們用官方文件(Laravel 8)提供的例子來試做Has One Through/Has Many Through的關係:
class CreateMechanicsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('mechanics', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
class CreateCarsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('cars', function (Blueprint $table) {
$table->id();
$table->foreignId('mechanic_id')->constrained('mechanics')->onDelete('cascade');
$table->string('model');
$table->timestamps();
});
}
class CreateOwnersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('owners', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('car_id')->constrained('cars')->onDelete('cascade');
$table->timestamps();
});
}
class Mechanic extends Model
{
use HasFactory;
public function car()
{
return $this->hasOne('App\Models\Car');
}
public function carOwner()
{
return $this->hasOneThrough('App\Models\Owner', 'App\Models\Car');
}
}
class Car extends Model
{
use HasFactory;
public function owner()
{
return $this->hasOne('App\Models\Owner');
}
public function mechanic()
{
return $this->belongsTo('App\Models\Mechanic');
}
}
class Owner extends Model
{
use HasFactory;
public function car()
{
return $this->belongsTo('App\Models\Car');
}
}
class HaveThroughTest extends TestCase
{
use RefreshDatabase;
/**
* A basic feature test example.
*
* @test
*/
public function mechanic_have_one_owner_through_car()
{
$car = Car::factory()->hasOwner(1)->create();
$mechanic = $car->mechanic;
$owner = $car->owner;
$this->assertTrue($owner->is($mechanic->carOwner));
}
...
}
class CreateCountriesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('countries', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
class CreateVisitorsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('visitors', function (Blueprint $table) {
$table->id();
$table->foreignId('country_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->timestamps();
});
}
class CreatePostsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('visitor_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->timestamps();
});
}
class Country extends Model
{
use HasFactory;
public function visitors()
{
return $this->hasMany('App\Models\Visitor');
}
public function posts()
{
return $this->hasManyThrough('App\Models\Post','App\Models\Visitor');
}
}
class Visitor extends Model
{
use HasFactory;
public function country()
{
return $this->belongsTo('App\Models\Country');
}
public function posts()
{
return $this->hasMany('App\Models\Post');
}
}
class Post extends Model
{
use HasFactory;
public function visitor()
{
return $this->belongsTo('App\Models\Visitor');
}
}
class HaveThroughTest extends TestCase
{
...
public function country_has_many_postst_hrough_visitors()
{
$country = Country::factory()->hasVisitors(4)->create();
$visitors = $country->visitors;
foreach($visitors as $visitor){
Post::factory()->count(2)->create(['visitor_id'=>$visitor->id]);
}
$this->assertCount(8,$country->posts);
}
}