Proiectarea schemei
Lectură estimată: 4 minute
Estimare pentru text, la un ritm de 180 de cuvinte pe minut. Timpul pentru analizarea codului, exerciții și proiecte se adaugă separat și depinde de ritmul tău.
Problema pe care o vei rezolva: Preț istoric și raport fără N+1
Produsul P costă acum 2.500 bani. Comanda veche conține 2 bucăți la 2.000 bani fiecare. Raportul listează 20 de comenzi și produsele lor.
Rezultatul urmărit
Modelează prețul unitar în poziția comenzii. Construiește o listare cu relații încărcate explicit, apoi măsoară numărul de interogări pentru 1 și 20 de comenzi.
Cum recunoști o soluție corectă
Comanda veche rămâne la 4.000 bani, indiferent de prețul actual. Numărul de interogări nu trebuie să crească cu fiecare rând din listare. Măsurarea se face pe aceeași structură de date.
Parcurge lecțiile în ordine și păstrează exercițiile în registrul descărcabil. Ultima lecție include atelierul de 40 de minute în care combini conceptele și testezi o variație a situației inițiale. Exemplele comerciale sunt fictive; nu reprezintă rezultate ale unor clienți.
O schemă de bază de date bine gândită îți economisește luni de suferință. Prost gândită, te obligă la workaround-uri la fiecare funcție nouă. Lecția asta e despre principiile de proiectare: ce e o entitate, cum alegi tipurile de coloane, ce înseamnă normalizare și când o încalci intenționat.
Entități și relații
Pornești de la substantivele din domeniu: „un magazin are produse, produsele aparțin unor categorii, clienții plasează comenzi, o comandă are mai multe produse". Fiecare substantiv „important" devine un tabel; verbele dintre ele devin relații.
Tipuri de coloane
- Text scurt (nume, titlu, slug): `string` (VARCHAR, implicit 255).
- Text lung (descriere, conținut): `text`.
- Numere întregi: `integer`, `unsignedBigInteger` pentru chei străine.
- Bani: `decimal(10, 2)` — niciodată `float` pentru sume.
- Adevărat/fals: `boolean`.
- Momente: `timestamp` sau `datetime`; `date` pentru doar dată.
Normalizare
Regula de bază: fiecare informație trăiește într-un singur loc. Nu pui numele categoriei în tabelul de produse — pui `category_id` și numele stă în tabelul de categorii. Așa, când redenumești o categorie, o faci o dată. Excepția (denormalizarea) e când performanța chiar cere să duplici ceva — dar o faci conștient, nu din lene.
Convenții Laravel
- Nume de tabel la plural, snake_case: `produse`, `comenzi`.
- Cheie primară `id`.
- Cheie străină: `
_id`, de ex. `categorie_id`. - Tabel pivot: numele celor două tabele la singular, alfabetic: `categorie_produs`.
- Coloane `created_at` / `updated_at` (adăugate de `$table->timestamps()`).
Schema unui mini-blog
users: id, name, email, password, timestamps categorii: id, nume, slug, timestamps articole: id, user_id (FK), categorie_id (FK), titlu, slug, continut, publicat, publicat_la, timestamps comentarii: id, articol_id (FK), autor_nume, continut, timestamps
Exercițiu
Pentru un domeniu la alegere (o bibliotecă, un cabinet, un magazin), listează entitățile, relațiile dintre ele și, pentru fiecare entitate, coloanele cu tipul potrivit. Respectă convențiile de denumire Laravel.
Greșeli frecvente
- Folosești float pentru bani și acumulezi erori de rotunjire.
- Duplici informație (numele categoriei în fiecare produs) fără motiv.
- Pui totul într-un singur tabel „date" gigant.
- Ignori convențiile de denumire și apoi lupți cu Eloquent la fiecare relație.
De reținut
- Entitățile = substantivele domeniului; relațiile = verbele.
- Tipuri corecte: decimal pentru bani, text pentru conținut lung.
- Normalizare: fiecare informație într-un singur loc.
- Respectă convențiile Laravel de denumire.
Pe scurt
O schemă bună modelează domeniul curat: entități clare, tipuri corecte, informație nedublicată, denumiri convenționale.
Schema pornită de la relații și constrângeri
Proiectează tabelul după întrebările aplicației și invariante, nu numai după formularul curent. O cerere aparține unui utilizator, iar un utilizator poate avea mai multe cereri. Cheia străină protejează legătura, dar regula la ștergere trebuie aleasă deliberat. Nu șterge în cascadă date care trebuie păstrate fără să verifici politica produsului. Pentru laborator, compară restrict și nullability înainte de a decide.
Exemplu de lucru
users: id, email unic.
service_requests: id, user_id, service, status, created_at.
Migrare exemplu:
$table->foreignId('user_id')->constrained()->restrictOnDelete();
$table->index(['user_id', 'status']);
Întrebări: cine deține cererea; ce se întâmplă la ștergerea userului; cum căutăm cererile active?Interpretarea rezultatului
Indexul compus urmărește o interogare concretă, nu se adaugă doar pentru că două câmpuri există. restrictOnDelete poate bloca ștergerea utilizatorului cu cereri; aplicația trebuie să trateze acest rezultat. Verifică motorul DB și constrângerile efective în laborator.
Exercițiu de consolidare
Propune o strategie de anonimizare pentru istoricul păstrat, fără a pretinde că simpla schimbare a emailului rezolvă toate obligațiile.
Aplicație practică
Sarcina
Proiectează tabelele orders și order_items pentru un magazin demonstrativ.
Rezolvare orientativă și verificare
Prețul unitar se salvează în poziția comenzii, în bani întregi, ca istoricul să nu depindă de prețul curent al produsului.
Greșeală de evitat
O comandă veche nu trebuie recalculată după modificarea catalogului.
Documentație și surse
Materiale pentru această lecție
Ți-a plăcut lecția? Continuă cursul complet
Ai citit o lecție gratuită. Celelalte lecții, aplicațiile practice și materialele se deblochează odată cu cursul.
149 lei
Plată unică, acces pe viață la toate cele 6 lecții.
Vezi ce conține cursul →