Database Explorer
Explorer oferă o modalitate intuitivă și eficientă de a lucra cu baza de date. Se ocupă automat de legăturile dintre tabele și de optimizarea interogărilor, astfel încât să vă puteți concentra pe aplicația dvs. Funcționează imediat fără configurare. Dacă aveți nevoie de control total asupra interogărilor SQL, puteți utiliza abordarea SQL.
- Lucrul cu datele este natural și ușor de înțeles
- Generează interogări SQL optimizate, care încarcă doar datele necesare
- Permite accesul facil la datele conexe fără a fi nevoie să scrieți interogări JOIN
- Funcționează imediat fără nicio configurare sau generare de entități
Cu Explorer începeți prin apelarea metodei table()
a obiectului Nette\Database\Explorer (detalii despre conectare
găsiți în capitolul Conectare și configurare):
Metoda returnează obiectul Selection,
care reprezintă o interogare SQL. Pe acest obiect putem înlănțui alte metode pentru filtrarea și sortarea rezultatelor.
Interogarea se construiește și se execută abia în momentul în care începem să solicităm date. De exemplu, prin parcurgerea
cu ciclul foreach
. Fiecare rând este reprezentat de obiectul ActiveRow:
Explorer facilitează în mod fundamental lucrul cu legăturile dintre tabele. Următorul exemplu arată cât de ușor putem afișa date din tabele legate (cărți și autorii lor). Observați că nu trebuie să scriem nicio interogare JOIN, Nette le creează pentru noi:
Nette Database Explorer optimizează interogările pentru a fi cât mai eficiente. Exemplul de mai sus execută doar două interogări SELECT, indiferent dacă procesăm 10 sau 10 000 de cărți.
În plus, Explorer urmărește ce coloane sunt utilizate în cod și încarcă din baza de date doar acelea, economisind astfel performanță suplimentară. Acest comportament este complet automat și adaptiv. Dacă modificați ulterior codul și începeți să utilizați alte coloane, Explorer ajustează automat interogările. Nu trebuie să setați nimic, nici să vă gândiți ce coloane veți avea nevoie – lăsați asta pe seama Nette.
Filtrare și sortare
Clasa Selection
oferă metode pentru filtrarea și sortarea selecției de date.
where($condition, ...$params) |
Adaugă condiția WHERE. Mai multe condiții sunt legate cu operatorul AND |
whereOr(array $conditions) |
Adaugă un grup de condiții WHERE legate cu operatorul OR |
wherePrimary($value) |
Adaugă condiția WHERE după cheia primară |
order($columns, ...$params) |
Setează sortarea ORDER BY |
select($columns, ...$params) |
Specifică coloanele care trebuie încărcate |
limit($limit, $offset = null) |
Limitează numărul de rânduri (LIMIT) și opțional setează OFFSET |
page($page, $itemsPerPage, &$total = null) |
Setează paginarea |
group($columns, ...$params) |
Grupează rândurile (GROUP BY) |
having($condition, ...$params) |
Adaugă condiția HAVING pentru filtrarea rândurilor grupate |
Metodele pot fi înlănțuite (așa-numitul fluent interface):
$table->where(...)->order(...)->limit(...)
.
În aceste metode puteți utiliza și notația specială pentru accesarea datelor din tabelele conexe.
Escapare și identificatori
Metodele escapează automat parametrii și încadrează identificatorii (numele tabelelor și coloanelor) în ghilimele, prevenind astfel SQL injection. Pentru funcționarea corectă este necesar să respectați câteva reguli:
- Cuvintele cheie, numele funcțiilor, procedurilor etc. scrieți-le cu majuscule.
- Numele coloanelor și tabelelor scrieți-le cu litere mici.
- Șirurile de caractere introduceți-le întotdeauna prin parametri.
where(string|array $condition, …$parameters): static
Filtrează rezultatele folosind condiții WHERE. Punctul său forte este lucrul inteligent cu diferite tipuri de valori și alegerea automată a operatorilor SQL.
Utilizare de bază:
Datorită detectării automate a operatorilor potriviți, nu trebuie să ne ocupăm de diverse cazuri speciale. Nette le rezolvă pentru noi:
Metoda procesează corect și condițiile negative și array-urile goale:
Ca parametru putem transmite și rezultatul dintr-o altă tabelă – se va crea o subinterogare:
Condițiile le putem transmite și ca array, ale cărui elemente se vor uni cu AND:
În array putem folosi perechi cheie ⇒ valoare și Nette alege din nou automat operatorii corecți:
În array putem combina expresii SQL cu semne de întrebare substituente și mai mulți parametri. Acest lucru este potrivit pentru condiții complexe cu operatori definiți precis:
Apelurile multiple ale where()
leagă automat condițiile cu AND.
whereOr(array $parameters): static
Similar cu where()
, adaugă condiții, dar cu diferența că le leagă cu OR:
Și aici putem folosi expresii mai complexe:
wherePrimary(mixed $key): static
Adaugă condiția pentru cheia primară a tabelei:
Dacă tabela are o cheie primară compozită (de ex. foo_id
, bar_id
), o transmitem ca array:
order(string $columns, …$parameters): static
Determină ordinea în care vor fi returnate rândurile. Putem sorta după una sau mai multe coloane, în ordine descrescătoare sau crescătoare, sau după o expresie proprie:
select(string $columns, …$parameters): static
Specifică coloanele care trebuie returnate din baza de date. În mod implicit, Nette Database Explorer returnează doar acele
coloane care sunt utilizate efectiv în cod. Metoda select()
o folosim deci în cazurile în care avem nevoie să
returnăm expresii specifice:
Aliasurile definite cu AS
sunt apoi disponibile ca proprietăți ale obiectului ActiveRow:
limit(?int $limit, ?int $offset = null): static
Limitează numărul de rânduri returnate (LIMIT) și opțional permite setarea unui offset:
Pentru paginare este mai potrivită utilizarea metodei page()
.
page(int $page, int $itemsPerPage, &$numOfPages = null): static
Facilitează paginarea rezultatelor. Acceptă numărul paginii (numărat de la 1) și numărul de elemente pe pagină. Opțional, se poate transmite o referință la o variabilă în care se va stoca numărul total de pagini:
group(string $columns, …$parameters): static
Grupează rândurile după coloanele specificate (GROUP BY). Se utilizează de obicei în combinație cu funcții de agregare:
having(string $having, …$parameters): static
Setează condiția pentru filtrarea rândurilor grupate (HAVING). Poate fi utilizată în combinație cu metoda
group()
și funcții de agregare:
Citirea datelor
Pentru citirea datelor din baza de date avem la dispoziție câteva metode utile:
foreach ($table as $key => $row) |
Iterează peste toate rândurile, $key este valoarea cheii primare, $row este obiectul
ActiveRow |
$row = $table->get($key) |
Returnează un rând după cheia primară |
$row = $table->fetch() |
Returnează rândul curent și mută pointerul la următorul |
$array = $table->fetchPairs() |
Creează un array asociativ din rezultate |
$array = $table->fetchAll() |
Returnează toate rândurile ca array |
count($table) |
Returnează numărul de rânduri din obiectul Selection |
Obiectul ActiveRow este destinat doar citirii. Acest lucru înseamnă că nu se pot modifica valorile proprietăților sale. Această limitare asigură consistența datelor și previne efectele secundare neașteptate. Datele sunt încărcate din baza de date și orice modificare ar trebui efectuată explicit și controlat.
foreach
– iterare peste toate rândurile
Cel mai simplu mod de a executa o interogare și de a obține rândurile este iterarea într-un ciclu foreach
.
Lansează automat interogarea SQL.
get($key): ?ActiveRow
Execută interogarea SQL și returnează rândul după cheia primară, sau null
, dacă nu există.
fetch(): ?ActiveRow
Returnează rândul și mută pointerul intern la următorul. Dacă nu mai există alte rânduri, returnează
null
.
fetchPairs(string|int|null $key = null, string|int|null $value = null): array
Returnează rezultatele ca array asociativ. Primul argument specifică numele coloanei care se va utiliza ca cheie în array, al doilea argument specifică numele coloanei care se va utiliza ca valoare:
Dacă specificăm doar primul parametru, valoarea va fi întregul rând, adică obiectul ActiveRow
:
În cazul cheilor duplicate, se va utiliza valoarea din ultimul rând. La utilizarea null
ca cheie, array-ul va fi
indexat numeric de la zero (atunci nu apar coliziuni):
fetchPairs(Closure $callback): array
Alternativ, puteți specifica ca parametru un callback, care va returna pentru fiecare rând fie valoarea însăși, fie perechea cheie-valoare.
fetchAll(): array
Returnează toate rândurile ca array asociativ de obiecte ActiveRow
, unde cheile sunt valorile cheilor
primare.
count(): int
Metoda count()
fără parametru returnează numărul de rânduri din obiectul Selection
:
Atenție, count()
cu parametru execută funcția de agregare COUNT în baza de date.
ActiveRow::toArray(): array
Convertește obiectul ActiveRow
într-un array asociativ, unde cheile sunt numele coloanelor și valorile sunt
datele corespunzătoare.
Agregace
Clasa Selection
oferă metode pentru executarea ușoară a funcțiilor de agregare (COUNT, SUM, MIN, MAX,
AVG etc.).
count($expr) |
Numără numărul de rânduri |
min($expr) |
Returnează valoarea minimă din coloană |
max($expr) |
Returnează valoarea maximă din coloană |
sum($expr) |
Returnează suma valorilor din coloană |
aggregation($function) |
Permite executarea oricărei funcții de agregare. De ex. AVG() , GROUP_CONCAT() |
count(string $expr): int
Execută interogarea SQL cu funcția COUNT și returnează rezultatul. Metoda se utilizează pentru a afla câte rânduri corespund unei anumite condiții:
Atenție, count() fără parametru returnează doar numărul de rânduri din obiectul
Selection
.
min(string $expr) și max(string $expr)
Metodele min()
și max()
returnează valoarea minimă și maximă din coloana sau expresia
specificată:
sum(string $expr)
Returnează suma valorilor din coloana sau expresia specificată:
aggregation(string $function, ?string $groupFunction = null)
Permite executarea oricărei funcții de agregare.
Dacă avem nevoie să agregăm rezultate care deja provin dintr-o funcție de agregare și grupare (de ex.
SUM(valoare)
peste rândurile grupate), ca al doilea argument specificăm funcția de agregare care trebuie aplicată
acestor rezultate intermediare:
În acest exemplu, mai întâi calculăm prețul total al produselor din fiecare categorie
(SUM(price * stock) AS category_total
) și grupăm rezultatele după category_id
. Apoi folosim
aggregation('SUM(category_total)', 'SUM')
pentru a aduna aceste sume intermediare category_total
. Al
doilea argument 'SUM'
spune că funcția SUM trebuie aplicată rezultatelor intermediare.
Insert, Update & Delete
Nette Database Explorer simplifică inserarea, actualizarea și ștergerea datelor. Toate metodele menționate aruncă
excepția Nette\Database\DriverException
în caz de eroare.
Selection::insert(iterable $data)
Inserează înregistrări noi în tabelă.
Inserarea unei singure înregistrări:
Transmitem noua înregistrare ca array asociativ sau obiect iterabil (de exemplu, ArrayHash utilizat în formulare), unde cheile corespund numelor coloanelor din tabelă.
Dacă tabela are o cheie primară definită, metoda returnează un obiect ActiveRow
, care este reîncărcat din
baza de date pentru a reflecta eventualele modificări efectuate la nivelul bazei de date (triggere, valori implicite ale
coloanelor, calcule ale coloanelor auto-increment). Astfel se asigură consistența datelor și obiectul conține întotdeauna
datele actuale din baza de date. Dacă nu are o cheie primară unică, returnează datele transmise sub formă de array.
Inserarea mai multor înregistrări deodată:
Metoda insert()
permite inserarea mai multor înregistrări printr-o singură interogare SQL. În acest caz,
returnează numărul de rânduri inserate.
Ca parametru se poate transmite și un obiect Selection
cu selecția de date.
Inserarea valorilor speciale:
Ca valori putem transmite și fișiere, obiecte DateTime sau literali SQL:
Selection::update(iterable $data): int
Actualizează rândurile din tabelă conform filtrului specificat. Returnează numărul de rânduri efectiv modificate.
Coloanele modificate le transmitem ca array asociativ sau obiect iterabil (de exemplu, ArrayHash utilizat în formulare), unde cheile corespund numelor coloanelor din tabelă:
Pentru modificarea valorilor numerice putem folosi operatorii +=
și -=
:
Selection::delete(): int
Șterge rândurile din tabelă conform filtrului specificat. Returnează numărul de rânduri șterse.
La apelarea update()
și delete()
, nu uitați să specificați rândurile care
trebuie modificate/șterse folosind where()
. Dacă nu utilizați where()
, operația se va efectua pe
întreaga tabelă!
ActiveRow::update(iterable $data): bool
Actualizează datele din rândul bazei de date reprezentat de obiectul ActiveRow
. Ca parametru acceptă un
iterabil cu datele care trebuie actualizate (cheile sunt numele coloanelor). Pentru modificarea valorilor numerice putem folosi
operatorii +=
și -=
:
După efectuarea actualizării, ActiveRow
se reîncarcă automat din baza de date pentru a reflecta eventualele
modificări efectuate la nivelul bazei de date (de ex. triggere). Metoda returnează true doar dacă a avut loc o modificare
efectivă a datelor.
Această metodă actualizează doar un singur rând specific din baza de date. Pentru actualizarea în masă a mai multor rânduri, utilizați metoda Selection::update().
ActiveRow::delete()
Șterge rândul din baza de date, care este reprezentat de obiectul ActiveRow
.
Această metodă șterge doar un singur rând specific din baza de date. Pentru ștergerea în masă a mai multor rânduri, utilizați metoda Selection::delete().
Relații între tabele
În bazele de date relaționale, datele sunt împărțite în mai multe tabele și interconectate prin chei străine. Nette Database Explorer aduce o modalitate revoluționară de a lucra cu aceste legături – fără a scrie interogări JOIN și fără a fi nevoie să configurați sau să generați ceva.
Pentru a ilustra lucrul cu legăturile, vom folosi exemplul bazei de date de cărți (îl găsiți pe GitHub). În baza de date avem tabelele:
author
– scriitori și traducători (coloaneid
,name
,web
,born
)book
– cărți (coloaneid
,author_id
,translator_id
,title
,sequel_id
)tag
– etichete (coloaneid
,name
)book_tag
– tabelă de legătură între cărți și etichete (coloanebook_id
,tag_id
)

Structura bazei de date folosită în exemple
În exemplul nostru de bază de date de cărți găsim mai multe tipuri de relații (deși modelul este simplificat față de realitate):
- One-to-many 1:N – fiecare carte are un autor, autorul poate scrie mai multe cărți
- Zero-to-many 0:N – cartea poate avea un traducător, traducătorul poate traduce mai multe cărți
- Zero-to-one 0:1 – cartea poate avea o continuare
- Many-to-many M:N – cartea poate avea mai multe etichete și o etichetă poate fi atribuită mai multor cărți
În aceste relații există întotdeauna o tabelă părinte și una copil. De exemplu, în relația dintre autor și carte,
tabela author
este părinte și book
este copil – ne putem imagina că o carte „aparține“
întotdeauna unui autor. Acest lucru se reflectă și în structura bazei de date: tabela copil book
conține cheia
străină author_id
, care face referire la tabela părinte author
.
Dacă avem nevoie să afișăm cărțile inclusiv numele autorilor lor, avem două opțiuni. Fie obținem datele printr-o singură interogare SQL folosind JOIN:
Fie încărcăm datele în doi pași – mai întâi cărțile și apoi autorii lor – și apoi le asamblăm în PHP:
A doua abordare este de fapt mai eficientă, deși poate fi surprinzător. Datele sunt încărcate o singură dată și pot fi utilizate mai bine în cache. Exact în acest mod lucrează Nette Database Explorer – rezolvă totul sub capotă și vă oferă o API elegantă:
Accesul la tabela părinte
Accesul la tabela părinte este direct. Este vorba despre relații precum cartea are un autor sau cartea poate
avea un traducător. Obținem înregistrarea asociată prin proprietatea obiectului ActiveRow – numele său corespunde
numelui coloanei cu cheia străină fără _id
:
Când accesăm proprietatea $book->author
, Explorer caută în tabela book
o coloană al cărei
nume conține șirul author
(adică author_id
). După valoarea din această coloană, încarcă
înregistrarea corespunzătoare din tabela author
și o returnează ca ActiveRow
. Similar
funcționează și $book->translator
, care utilizează coloana translator_id
. Deoarece coloana
translator_id
poate conține null
, folosim în cod operatorul ?->
.
O cale alternativă o oferă metoda ref()
, care acceptă doi argumente, numele tabelei țintă și numele
coloanei de legătură, și returnează o instanță ActiveRow
sau null
:
Metoda ref()
este utilă dacă nu se poate utiliza accesul prin proprietate, deoarece tabela conține o coloană
cu același nume (adică author
). În celelalte cazuri, se recomandă utilizarea accesului prin proprietate, care
este mai lizibil.
Explorer optimizează automat interogările bazei de date. Când parcurgem cărțile într-un ciclu și accesăm înregistrările lor asociate (autori, traducători), Explorer nu generează o interogare pentru fiecare carte în parte. În schimb, execută doar un singur SELECT pentru fiecare tip de legătură, reducând semnificativ sarcina bazei de date. De exemplu:
Acest cod apelează doar aceste trei interogări fulgerătoare în baza de date:
Logica de căutare a coloanei de legătură este dată de implementarea Conventions. Recomandăm utilizarea DiscoveredConventions, care analizează cheile străine și permite lucrul simplu cu relațiile existente între tabele.
Accesul la tabela copil
Accesul la tabela copil funcționează în direcția opusă. Acum întrebăm ce cărți a scris acest autor sau a
tradus acest traducător. Pentru acest tip de interogare folosim metoda related()
, care returnează
Selection
cu înregistrările asociate. Să vedem un exemplu:
Metoda related()
acceptă descrierea legăturii ca un singur argument cu notație cu punct sau ca doi argumente
separate:
Explorer poate detecta automat coloana de legătură corectă pe baza numelui tabelei părinte. În acest caz, se leagă prin
coloana book.author_id
, deoarece numele tabelei sursă este author
:
Dacă ar exista mai multe legături posibile, Explorer ar arunca excepția AmbiguousReferenceKeyException.
Metoda related()
o putem folosi, desigur, și la parcurgerea mai multor înregistrări într-un ciclu și
Explorer optimizează automat interogările și în acest caz:
Acest cod generează doar două interogări SQL fulgerătoare:
Legătura Many-to-many
Pentru legătura many-to-many (M:N) este necesară existența unei tabele de legătură (în cazul nostru
book_tag
), care conține două coloane cu chei străine (book_id
, tag_id
). Fiecare dintre
aceste coloane face referire la cheia primară a uneia dintre tabelele legate. Pentru a obține datele asociate, mai întâi
obținem înregistrările din tabela de legătură folosind related('book_tag')
și apoi continuăm către
datele țintă:
Explorer optimizează din nou interogările SQL într-o formă eficientă:
Interogarea prin tabele asociate
În metodele where()
, select()
, order()
și group()
putem folosi notații
speciale pentru accesarea coloanelor din alte tabele. Explorer creează automat JOIN-urile necesare.
Notația cu punct (tabela_parinte.coloana
) se utilizează pentru relația 1:N din perspectiva
tabelei copil:
Notația cu două puncte (:tabela_copil.coloana
) se utilizează pentru relația 1:N din perspectiva tabelei
părinte:
În exemplul de mai sus cu notația cu două puncte (:book.title
), nu este specificată coloana cu cheia
străină. Explorer detectează automat coloana corectă pe baza numelui tabelei părinte. În acest caz, se leagă prin coloana
book.author_id
, deoarece numele tabelei sursă este author
. Dacă ar exista mai multe legături
posibile, Explorer ar arunca excepția AmbiguousReferenceKeyException.
Coloana de legătură poate fi specificată explicit în paranteză:
Notațiile pot fi înlănțuite pentru accesul prin mai multe tabele:
Extinderea condițiilor pentru JOIN
Metoda joinWhere()
extinde condițiile care se specifică la legarea tabelelor în SQL după cuvântul cheie
ON
.
Să presupunem că dorim să găsim cărțile traduse de un anumit traducător:
În condiția joinWhere()
putem folosi aceleași construcții ca în metoda where()
– operatori,
semne de întrebare substituente, array-uri de valori sau expresii SQL.
Pentru interogări mai complexe cu mai multe JOIN-uri, putem defini aliasuri pentru tabele:
Observați că, în timp ce metoda where()
adaugă condiții în clauza WHERE
, metoda
joinWhere()
extinde condițiile în clauza ON
la legarea tabelelor.