Poznajemy soft deletes w Laravelu oraz przypominamy sobie relacje jeden do wielu. Kontynuacja lekcji poprzednich. Do dzieła.

Ok, tworzymy nowy model, migrację, fabrykę i seeder (w sumie seeder niepotrzebny):

php artisan make:model Note -mfs

Ok, rzucamy okiem na migrację i dopisujemy co trzeba:

  public function up(): void
    {
        Schema::create('notes', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->softDeletes();
            $table->unsignedBigInteger('person_id')->nullable(true);
            $table->string('title');
            $table->text('content');
            $table->foreign('person_id')->references('id')->on('people')->onDelete('cascade');
        });
    }

Powinniśmy już mniej więcej ogarniać, że:

  • nazwa notes wygenerowana/wydedukowana z nazwy modelu (tworząc taką komendą i pamiętając o wielkiej literze liczbie pojedynczej zawsze będziemy mieć zgodność z konwencjami nazewniczymi Laravela)
  • Id tworzy pk id
  • timestamps tworzy created_at oraz updated_at, zaś każdy model ma domyślne założenie, że timestamps jest użyte
  • gdyby timestamps nie było, to trzeba by było w modelu public $timestamps ustawić na false, żeby Laravel wiedział
  • gdyby coś nie teges było z nazwą tabeli to też model musi mieć info w protected $table, że np. mamy „my_notes”
  • softDeletes po prostu dodaje nam nowe pole deleted_at, które jest nullable
  • softDeletes wymaga użycia traita useSoftDeletes w modelu
  • unsignedBigInteger na foreign key, person_id to jest konwencja nazewnicza dla tabeli people
  • gdybyśmy zrobili jakąś głupotę typu zamiast person_id mamy author_id to w obu modelach (hasMany i belongsTo) musielibyśmy jako drugi argument podać nazwę klucza 'author_id’
  • string i text to wartości fillable, trzeba będzie je dopisać w modelu do protected $fillable, żeby był mass assignment możliwy
  • klucz obcy na tabelę people i onDelete cascade, czyli jak usuniesz osobę to usuń jej notatki bez foreign key constraint fail albo ręcznego ich wynajdywania i usuwania po naszej stronie
  • skoro onDelete mamy cascade…. to wcale nie musi być klucz obcy nullable, zrobiłem, bo planowałem onDelete 'set null’, tak was sprawdzam
  • onDelete nie ma żadnego związku z softDeletes… On delete usuwa notatki, jeżeli ich twórca zostanie usunięty… softDeletes tworzy deleted_at czyli jak usuwamy coś, to to nie znika tylko znika z queries, w bazie danych zostaje

Ok, teraz model, jeden i drugi:

class Note extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $fillable = ['title', 'content'];
    public function author(){
            return $this->belongsTo(Person::class);
    }
}

Mam nadzieję, że wszystko jasne, teraz model-rodzic:

use App\Models\Address;
use App\Models\Note;
class Person extends Model
{
    protected $fillable = ['firstName', 'lastName', 'age'];
    
    use HasFactory;

    // public function fullname(){
    //     return $this->firstName . " " . $this->lastName;
    // }

    public function makeOlder($by=1){
        $this->age += $by;
        $this->save();
    }

    protected function fullName(): Attribute
    {
        return Attribute::make(
            get: fn () => $this->firstName . " " . $this->lastName,
        );
    }

    public function address() 
    {
        return $this->hasOne(Address::class);
    }

    public function notes(){
        return $this->hasMany(Note::class);
    }

}

Oczywiście z migrate nie musimy czekać aż w modelu wszystko poustawiamy, to nie Symfony, które wnioskuje migracje z Entity!

Natomiast nie chciałem już mieszać, więc zróbmy migrację teraz:

php artisan migrate

Ok, jak widzimy wszystko w bazie danych (to jest tabele), to jest dobrze.

Gdyby coś nie wyszło polecam komendę cofania migracji:

php artisan migrate:rollback

Można też określić krok:

php artisan migrate:rollback --step=5

Można wyczyścić wszystkie migracje:

php artisan migrate:reset

Znam jeszcze jedną sztuczkę na „chamski rollback” jak to nazywam:

  • Przypominamy sobie, która migracja była ostatnią działającą
  • Usuwamy (tak dosłownie, plik po pliku) migracje, od których nie działa
  • W .env zmieniamy nazwę bazy danych na niestniejącą
  • Robimy php artisan migrate
  • Zapytani czy utworzyć nową bazę danych odpowiadamy tak

Na pewno nie jest to dobra praktyka, ale na początku, gdy jesteśmy przytłoczeni wszystkim to może nam to jakoś pomóc. Tym niemniej nie miejmy alergii na terminal, czytanie errorów i spokojną pracę z frameworkiem.