Poznajemy tą naprawdę prostą relację w Laravelu. Korzystamy z już istniejącego projektu. Do dzieła.

Ok, tworzymy model Profile, razem z migracją, fabryką, seederem. Migracja:

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('profiles', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->unsignedBigInteger('person_id')->nullable(true);
            $table->string('profile_name')->unique(true);
            $table->text('description')->nullable(true);
            $table->foreign('person_id')
            ->references('id')
            ->on('people')
            ->onDelete('cascade')
            ->onUpdate('cascade');
        });
    }

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

Person już mamy. Ok, migrujemy, tworzymy model Contact, -mfs. Migracja:

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('contacts', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->unsignedBigInteger('profile_id')->nullable(true);
            $table->string('email')->unique(true);
            $table->string('webpage')->nullable(true);
            $table->foreign('profile_id')
            ->references('id')
            ->on('profiles')
            ->onDelete('cascade')
            ->onUpdate('cascade');
        });
    }

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

Ok, migrujemy, tworzymy naszą piramidkę. W person:

 public function profile(){
        return $this->hasOne(Profile::class);
    }

Pamiętajmy o importach, teraz w Profile:

class Profile extends Model
{
    use HasFactory;

    protected $fillable = ['profile_name', 'description'];

    public function contact(){
        return $this->hasOne(Contact::class);
    }

    public function person(){
        return $this->belongsTo(Person::class);
    }
}

Fillable też trzeba wypełniać, wiemy dlaczego. Ok, teraz Contact:

class Contact extends Model
{
    use HasFactory;

    protected $fillable = ['email', 'webpage'];

    public function profile(){
        return $this->belongsTo(Profile::class);
    }
}

Ok, spróbujemy zrobić testowego gościa z profilem i kontaktem zapisanym. Komenda:

Artisan::command('test1234', function () {

    $person = new Person();
    $person->firstName = "Blabla";
    $person->lastName = "Bla bla bla";
    $person->age = 23;
    $person->save();

    $profile = new Profile();
    $profile->profile_name = "Blabla profile";
    $profile->description = "bla bla bla";

    $person->profile()->save($profile);

    $contact = new Contact();
    $contact->email = "blabla@blabla.com";
    $contact->webpage = "www.blabla.com";

    $profile->contact()->save($contact);

    $this->comment("done");

    
    
});

Pamiętamy o importach. Ok, rzućmy okiem na bazę danych czy wszystko działa jak należy.

Jest ok? To teraz czas na relację hasOneThrough (w Person):

public function profileContact()
    {
        return $this->hasOneThrough(Contact::class, Profile::class);
    }

I teraz cała magia:

Artisan::command('person-profileContact {id}', function (int $id) {

    $p = Person::with('profileContact')->findOrFail($id);
    $this->comment($p->full_name);
    $this->comment($p->profileContact->email);
    
});

Czyli Person ma Profil, profil ma Contact, ale Person ma Contact przez Profil. Tak ta układanka działa. Podobnie z relacjami jeden do wielu.

My raczej będziemy pamiętać manyToManyThrough i dzikie SQLe (group concat i wiele innych!), które pisaliśmy, aby dostać się do tagów bez powtórzeń, które występują pod recenzjami danego filmu, zaś recenzje i tagi spina nie klucz obcy, ale pivot table review_tag.

Cóż, tu jest łatwiej. Jeżeli has one of many też pamiętamy, to dobrze, coraz więcej umiemy.