Közvetlen SQL
A Nette adatbázissal kétféleképpen dolgozhat: közvetlenül SQL-lekérdezéseket írhat (közvetlen hozzáférés) vagy hagyhatja, hogy az SQL automatikusan generálódjon(felfedező hozzáférés). A közvetlen hozzáférés lehetővé teszi a lekérdezések biztonságos létrehozását, miközben teljes mértékben ellenőrizheti azok szerkezetét.
A kapcsolat létrehozására és konfigurálására vonatkozó információkat lásd a külön oldalon.
Alapvető lekérdezés
A query()
metódus adatbázis-lekérdezéseket hajt végre, és az eredményt reprezentáló ResultSet objektumot ad vissza. Ha a lekérdezés
sikertelen, a metódus kivételt dob. A lekérdezés eredményét a
foreach
hurok segítségével vagy a segédfüggvények valamelyikének
használatával végighaladhat a lekérdezés eredményén.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
}
Az értékek SQL-lekérdezésekbe történő biztonságos beszúrásához használjon paraméterezett lekérdezéseket. A Nette Database ezt nagyon egyszerűvé teszi: csak egy vesszőt és az értéket kell az SQL-lekérdezéshez csatolni.
$database->query('SELECT * FROM users WHERE name = ?', $name);
Több paraméter esetén az SQL-lekérdezést vagy közbeiktathatja a paraméterekkel:
$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);
Vagy írja meg először a teljes SQL-lekérdezést, majd csatolja az összes paramétert:
$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);
SQL Injection elleni védelem
Miért fontos a paraméterezett lekérdezések használata? Mert megvédik az SQL-injekciós támadásoktól, amelyek során a támadók rosszindulatú SQL-parancsokat juttathatnak be az adatbázis adatainak manipulálása vagy az azokhoz való hozzáférés érdekében.
Soha ne illesszen be változókat közvetlenül egy SQL-lekérdezésbe! Mindig használjon paraméterezett lekérdezéseket, hogy megvédje magát az SQL injekcióval szemben.
// ❌ UNSAFE CODE - SQL injekcióval sebezhető
$database->query("SELECT * FROM users WHERE name = '$name'");
// ✅ Biztonságos paraméteres lekérdezés
$database->query('SELECT * FROM users WHERE name = ?', $name);
Győződjön meg róla, hogy megismerkedett a lehetséges biztonsági kockázatokkal.
Lekérdezési technikák
WHERE feltételek
A WHERE
feltételeket asszociatív tömbként is megírhatja, ahol a kulcsok az oszlopnevek, az értékek pedig az
összehasonlítandó adatok. A Nette Database automatikusan kiválasztja a legmegfelelőbb SQL-operátort az érték típusa
alapján.
$database->query('SELECT * FROM users WHERE', [
'name' => 'John',
'active' => true,
]);
// WHERE `név` = 'John' AND `aktív` = 1
Az operátort kifejezetten is megadhatja a kulcsban:
$database->query('SELECT * FROM users WHERE', [
'age >' => 25, // a > operátort használja
'name LIKE' => '%John%', // a LIKE operátort használja
'email NOT LIKE' => '%example.com%', // a NOT LIKE operátort használja
]);
// WHERE "age" > 25 AND "name" LIKE '%John%' AND "email" NOT LIKE '%example.com%'
Az olyan speciális eseteket, mint a null
értékek vagy tömbök, a rendszer automatikusan kezeli:
$database->query('SELECT * FROM products WHERE', [
'name' => 'Laptop', // az = operátort használja
'category_id' => [1, 2, 3], // IN-t használ
'description' => null, // IS NULL-t használ
]);
// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL
A negatív feltételek esetén használja a NOT
operátort:
$database->query('SELECT * FROM products WHERE', [
'name NOT' => 'Laptop', // a <> operátort használja
'category_id NOT' => [1, 2, 3], // NOT IN-t használ
'description NOT' => null, // IS NOT NULL operátort használ
'id' => [], // kihagyta
]);
// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL
Alapértelmezés szerint a feltételek kombinálása a AND
operátorral történik. Ezt a viselkedést a ?or helyőrzővel változtathatja meg.
ORDER BY szabályok
A ORDER BY
záradék definiálható tömbként, ahol a kulcsok oszlopokat, az értékek pedig növekvő sorrendet
jelző bólusokat jelentenek:
$database->query('SELECT id FROM author ORDER BY', [
'id' => true, // felmenő
'name' => false, // ereszkedő
]);
// SELECT id FROM szerző ORDER BY `id`, `név` DESC
Adatok beszúrása (INSERT)
Rekordok beszúrásához használja az SQL INSERT
utasítást.
$values = [
'name' => 'John Doe',
'email' => 'john@example.com',
];
$database->query('INSERT INTO users ?', $values);
$userId = $database->getInsertId();
A getInsertId()
módszer az utoljára beillesztett sor azonosítóját adja vissza. Bizonyos adatbázisok (pl.
PostgreSQL) esetében a $database->getInsertId($sequenceId)
segítségével meg kell adnia a sorszámnevet.
Speciális értékeket, például fájlokat, DateTime objektumokat vagy enum típusokat is átadhat paraméterként.
Több rekord egyszerre történő beszúrása:
$database->query('INSERT INTO users ?', [
['name' => 'User 1', 'email' => 'user1@mail.com'],
['name' => 'User 2', 'email' => 'user2@mail.com'],
]);
A kötegelt INSERT végrehajtása sokkal gyorsabb, mivel több egyedi lekérdezés helyett csak egyetlen adatbázis-lekérdezés kerül végrehajtásra.
Biztonsági megjegyzés: Soha ne használjon érvénytelenített adatokat a $values
címen. Ismerkedjen meg
a lehetséges kockázatokkal.
Az adatok frissítése (UPDATE)
A rekordok frissítéséhez használja az SQL UPDATE
utasítást.
// Egyetlen rekord frissítése
$values = [
'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);
Az érintett sorok számát a $result->getRowCount()
segítségével ellenőrizheti.
A +=
és a -=
operátorokat a UPDATE
oldalon használhatja:
$database->query('UPDATE users SET ? WHERE id = ?', [
'login_count+=' => 1, // login_count növelése
], 1);
A ON DUPLICATE KEY UPDATE
technikát használhatja egy rekord beszúrásához vagy frissítéséhez, ha az már
létezik:
$values = [
'name' => $name,
'year' => $year,
];
$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?',
$values + ['id' => $id],
$values,
);
// INSERT INTO users (`id`, `név`, `év`) VALUES (123, 'Jim', 1978)
// ON DUPLICATE KEY UPDATE `név` = 'Jim', `év` = 1978
Vegye figyelembe, hogy a Nette Database felismeri az SQL-parancs kontextusát, amelyben egy tömböt tartalmazó paramétert
használ, és ennek megfelelően generálja az SQL-kódot. Például az első tömbből a
(id, name, year) VALUES (123, 'Jim', 1978)
címet konstruálta, míg a másodikat a
name = 'Jim', year = 1978
címmé alakította. Erről részletesebben a Hints
az SQL konstruálásához című részben olvashat.
Adatok törlése (DELETE)
A rekordok törléséhez használja az SQL DELETE
utasítást. Példa a törölt sorok számával:
$count = $database->query('DELETE FROM users WHERE id = ?', 1)
->getRowCount();
SQL építési tippek
Az SQL-helyettesítők lehetővé teszik annak szabályozását, hogy a paraméterértékek hogyan épüljenek be az SQL-kifejezésekbe:
Hint | Leírás | Automatikusan használva a következőkhöz |
---|---|---|
?name |
Tábla- vagy oszlopneveknél használatos | – – |
?values |
Generálja a (key, ...) VALUES (value, ...) |
INSERT ... ? , REPLACE ... ? |
?set |
Hozzárendeléseket generál key = value, ... |
SET ? , KEY UPDATE ? |
?and |
Összekapcsolja a feltételeket egy tömbben a AND |
WHERE ? , HAVING ? |
?or |
Összekapcsolja a feltételeket egy tömbben a OR |
– |
?order |
Generálja a ORDER BY záradékot |
ORDER BY ? , GROUP BY ? |
A táblázat- vagy oszlopnevek dinamikus beillesztéséhez használja a ?name
helyőrzőt. A Nette Database
gondoskodik az adatbázis konvencióinak megfelelő escapingről (pl. a MySQL esetében a backtickekbe zárás).
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
// SELECT `név` FROM `felhasználók` WHERE id = 1 (MySQL-ben)
Figyelmeztetés: Kizárólag a ?name
helyőrzőt használja az érvényesített tábla- és oszlopnevekhez.
Ellenkező esetben biztonsági réseket
kockáztat.
Egyéb tippeket általában nem szükséges megadni, mivel a Nette intelligens automatikus felismerést használ az
SQL-lekérdezések szerkesztésekor (lásd a táblázat harmadik oszlopát). Használhatja őket azonban olyan helyzetekben,
amikor a AND
helyett a OR
használatával kívánja a feltételeket kombinálni:
$database->query('SELECT * FROM users WHERE ?or', [
'name' => 'John',
'email' => 'john@example.com',
]);
// SELECT * FROM users WHERE `név` = 'John' OR `email` = 'john@example.com'
Különleges értékek
A szabványos skalár típusok (pl. string
, int
, bool
) mellett speciális értékeket
is átadhat paraméterként:
- Fájlok: A
fopen('file.png', 'r')
segítségével egy fájl bináris tartalmát illesztheti be. - Dátum és idő: A
DateTime
objektumok automatikusan az adatbázis dátumformátumára konvertálódnak. - Enum értékek: A
enum
példányai a megfelelő értékekké konvertálódnak. - SQL literálok: A
Connection::literal('NOW()')
segítségével létrehozva, ezek közvetlenül a lekérdezésbe kerülnek beillesztésre.
$database->query('INSERT INTO articles ?', [
'title' => 'My Article',
'published_at' => new DateTime,
'content' => fopen('image.png', 'r'),
'state' => Status::Draft,
]);
Az olyan adatbázisok esetében, amelyek nem támogatják natívan a datetime
típust (pl. SQLite és Oracle), a
DateTime
értékek a formatDateTime
konfigurációs opciónak megfelelően kerülnek átalakításra
(alapértelmezett: U
Unix timestamp esetén).
SQL irodalmi karakterek
Bizonyos esetekben előfordulhat, hogy nyers SQL-kódot kell értékként beillesztenie anélkül, hogy azt karakterláncként
kezelné vagy szcenírozná. Ehhez használjuk a Nette\Database\SqlLiteral
osztály objektumait, amelyeket a
Connection::literal()
metódussal hozhatunk létre.
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`név` = 'Jim') AND (`év` > ÉV())
Alternatívaként:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`név` = 'Jim') AND (év > ÉV())
Az SQL literálok paramétereket is tartalmazhatnak:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > ? AND year < ?', $min, $max),
]);
// SELECT * FROM users WHERE `név` = 'Jim' AND (év > 1978 AND év < 2017)
Ez rugalmas kombinációkat tesz lehetővé:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('?or', [
'active' => true,
'role' => $role,
]),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin')
Adatok lekérése
SELECT lekérdezések rövidítései
Az adatok lekérdezésének egyszerűsítése érdekében a Connection
osztály több olyan gyorsbillentyűt
biztosít, amelyek a query()
hívást egy későbbi fetch*()
hívással kombinálják. Ezek a módszerek
ugyanazokat a paramétereket fogadják el, mint a query()
, azaz egy SQL-lekérdezést és opcionális paramétereket.
A fetch*()
metódusok részletes leírása alább található.
fetch($sql, ...$params): ?Row |
A lekérdezés végrehajtása és az első sor lekérdezése Row objektumként. |
fetchAll($sql, ...$params): array |
Végrehajtja a lekérdezést, és az összes sort Row objektumok tömbjeként hozza ki. |
fetchPairs($sql, ...$params): array |
A lekérdezés végrehajtása és egy asszociatív tömb kinyerése, ahol az első oszlop a kulcs, a második pedig az érték. |
fetchField($sql, ...$params): mixed |
A lekérdezés végrehajtása és az első sor első cellájának értékének lekérdezése. |
fetchList($sql, ...$params): ?array |
A lekérdezés végrehajtása és az első sor indexelt tömbként történő lekérdezése. |
Példa:
// fetchField() - az első cella értékét adja vissza
$count = $database->query('SELECT COUNT(*) FROM articles')
->fetchField();
foreach
– Sorokon való ismétlés
A lekérdezés végrehajtása után egy ResultSet objektumot kapunk vissza, amely
lehetővé teszi, hogy különböző módon végigmenjünk az eredményeken. A sorok lekérdezésének legegyszerűbb és
memóriahatékonyabb módja a foreach
ciklusban történő iterálás. Ez a módszer egyesével dolgozza fel a
sorokat, és elkerüli az összes adat egyszerre történő tárolását a memóriában.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
//...
}
A ResultSet
csak egyszer iterálható. Ha többször kell iterálni rajta, akkor először be kell
tölteni az adatokat egy tömbbe, például a fetchAll()
módszerrel.
fetch(): ?Row
Végrehajtja a lekérdezést, és egyetlen sort hív le a Row
objektumként. Ha nem áll rendelkezésre több sor,
akkor a null
objektumot adja vissza. Ez a módszer a belső mutatót a következő sorra továbbítja.
$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // az első sort hozza le
if ($row) {
echo $row->name;
}
fetchAll(): array
A ResultSet
összes fennmaradó sorát a Row
objektumok tömbjeként hívja le.
$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // az összes sort lekérdezi
foreach ($rows as $row) {
echo $row->name;
}
fetchPairs (string|int|null $key = null, string|int|null $value = null): array
Az eredmények asszociatív tömbként való lekérdezése. Az első argumentum a kulcsként használandó oszlopot, a második pedig az értékként használandó oszlopot adja meg:
$result = $database->query('SELECT id, name FROM users');
$names = $result->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]
Ha csak az első paramétert adjuk meg, az érték a teljes sor lesz ( Row
objektumként):
$rows = $result->fetchPairs('id');
// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...]
Ha kulcsként a null
értéket adjuk meg, akkor a tömböt nullától kezdődően numerikusan indexeljük:
$names = $result->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]
fetchPairs (Closure $callback): array
Alternatívaként megadhat egy visszahívást, amely meghatározza az egyes sorok kulcs-érték párosait vagy értékeit.
$result = $database->query('SELECT * FROM users');
$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
// ['1 - John', '2 - Jane', ...]
// A visszahívás egy kulcs- és értékpárral rendelkező tömböt is visszaadhat:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]
fetchField(): mixed
Az aktuális sor első cellájának értékét kéri ki. Ha nincs több sor, akkor a null
értéket adja vissza.
Ez a módszer a belső mutatót a következő sorra továbbítja.
$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // az első sorban szereplő nevet hívja le
fetchList(): ?array
A sort indexelt tömbként hívja le. Ha nem áll rendelkezésre több sor, akkor a null
értéket adja vissza.
Ez a módszer a belső mutatót a következő sorra továbbítja.
$result = $database->query('SELECT name, email FROM users');
$row = $result->fetchList(); // ['John', 'john@example.com']
getRowCount(): ?int
Visszaadja az utolsó UPDATE
vagy DELETE
lekérdezés által érintett sorok számát. A
SELECT
lekérdezések esetén a lekérdezett sorok számát adja vissza, de ez nem mindig ismert – ilyen esetekben
a null
értéket adja vissza.
getColumnCount(): ?int
Visszaadja az oszlopok számát a ResultSet
oldalon.
Lekérdezési információ
A legutóbb végrehajtott lekérdezés részleteinek lekérdezéséhez használja a következőt:
echo $database->getLastQueryString(); // kiadja az SQL lekérdezést
$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString(); // kiadja az SQL-lekérdezést
echo $result->getTime(); // a végrehajtási idő másodpercben kifejezve
Az eredmény HTML-táblázatként való megjelenítéséhez használja a következőt: A lekérdezés eredményének HTML-táblázatként való megjelenítéséhez használja a következőt:
$result = $database->query('SELECT * FROM articles');
$result->dump();
Az oszloptípusokra vonatkozó információkat a ResultSet
oldalon is lekérdezheti:
$result = $database->query('SELECT * FROM articles');
$types = $result->getColumnTypes();
foreach ($types as $column => $type) {
echo "$column is of type $type->type"; // pl.: 'id az int típusú'
}
Lekérdezés naplózása
Egyéni lekérdezésnaplózást is megvalósíthat. A onQuery
esemény egy olyan visszahívásokból álló tömb,
amely minden egyes lekérdezés végrehajtása után meghívásra kerül:
$database->onQuery[] = function ($database, $result) use ($logger) {
$logger->info('Query: ' . $result->getQueryString());
$logger->info('Time: ' . $result->getTime());
if ($result->getRowCount() > 1000) {
$logger->warning('Large result set: ' . $result->getRowCount() . ' rows');
}
};