SQL diretto
È possibile lavorare con Nette Database in due modi: scrivendo direttamente le query SQL (Direct Access) o lasciando che l'SQL venga generato automaticamente(Explorer Access). L'accesso diretto consente di creare query in tutta sicurezza, mantenendo il pieno controllo sulla loro struttura.
Per informazioni sulla creazione di una connessione e sulla sua configurazione, consultare la pagina separata.
Interrogazione di base
Il metodo query()
esegue query al database e restituisce un oggetto ResultSet che rappresenta il risultato. Se la query
fallisce, il metodo lancia un'eccezione. È possibile eseguire il ciclo dei risultati della query
utilizzando un ciclo foreach
o utilizzare una delle funzioni di aiuto.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
}
Per inserire in modo sicuro i valori nelle query SQL, si possono usare le query parametrizzate. Nette Database lo rende molto semplice: basta aggiungere una virgola e il valore alla query SQL.
$database->query('SELECT * FROM users WHERE name = ?', $name);
Per i parametri multipli, è possibile intercalare la query SQL con i parametri:
$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);
Oppure scrivere prima l'intera query SQL e poi aggiungere tutti i parametri:
$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);
Protezione contro l'iniezione SQL
Perché è importante utilizzare query parametrizzate? Perché proteggono da attacchi di tipo SQL injection, in cui gli aggressori possono iniettare comandi SQL dannosi per manipolare o accedere ai dati del database.
**Non inserite mai variabili direttamente in una query SQL! Usate sempre query parametrizzate per proteggervi da SQL injection.
// ❌ CODICE NON SICURO - vulnerabile all'iniezione SQL
$database->query("SELECT * FROM users WHERE name = '$name'");
// ✅ Query parametrica sicura
$database->query('SELECT * FROM users WHERE name = ?', $name);
Assicuratevi di conoscere i potenziali rischi per la sicurezza.
Tecniche di interrogazione
Condizioni WHERE
È possibile scrivere le condizioni di WHERE
come un array associativo, dove le chiavi sono nomi di colonne e
i valori sono i dati da confrontare. Nette Database seleziona automaticamente l'operatore SQL più appropriato in base al tipo
di valore.
$database->query('SELECT * FROM users WHERE', [
'name' => 'John',
'active' => true,
]);
// DOVE `nome` = 'Giovanni' E `attivo` = 1
È anche possibile specificare esplicitamente l'operatore nella chiave:
$database->query('SELECT * FROM users WHERE', [
'age >' => 25, // utilizza l'operatore >
'name LIKE' => '%John%', // utilizza l'operatore LIKE
'email NOT LIKE' => '%example.com%', // utilizza l'operatore NOT LIKE
]);
// DOVE `età` > 25 AND `nome` LIKE '%John%' AND `email` NOT LIKE '%example.com%'
I casi speciali, come i valori null
o gli array, vengono gestiti automaticamente:
$database->query('SELECT * FROM products WHERE', [
'name' => 'Laptop', // utilizza l'operatore =
'category_id' => [1, 2, 3], // utilizza IN
'description' => null, // usi è nullo
]);
// DOVE `nome` = 'Laptop' E `category_id` IN (1, 2, 3) E `description` IS NULL
Per le condizioni negative, utilizzare l'operatore NOT
:
$database->query('SELECT * FROM products WHERE', [
'name NOT' => 'Laptop', // utilizza l'operatore <>
'category_id NOT' => [1, 2, 3], // utilizza NOT IN
'description NOT' => null, // utilizza IS NOT NULL
'id' => [], // saltato
]);
// DOVE `nome` <> 'Laptop' E `categoria_id` NON IN (1, 2, 3) E `descrizione` NON È NULL
Per impostazione predefinita, le condizioni sono combinate utilizzando l'operatore AND
. È possibile modificare
questo comportamento utilizzando il segnaposto ?or.
Ordina per regole
La clausola ORDER BY
può essere definita come un array, dove le chiavi rappresentano le colonne e i valori sono
booleani che indicano l'ordine crescente:
$database->query('SELECT id FROM author ORDER BY', [
'id' => true, // ascendente
'name' => false, // discendente
]);
// SELEZIONARE id DA AUTORE ORDINATO PER `id`, `nome` DESC
Inserimento di dati (INSERT)
Per inserire i record, utilizzare l'istruzione SQL INSERT
.
$values = [
'name' => 'John Doe',
'email' => 'john@example.com',
];
$database->query('INSERT INTO users ?', $values);
$userId = $database->getInsertId();
Il metodo getInsertId()
restituisce l'ID dell'ultima riga inserita. Per alcuni database (ad esempio, PostgreSQL),
è necessario specificare il nome della sequenza utilizzando $database->getInsertId($sequenceId)
.
È anche possibile passare come parametri valori speciali, come file, oggetti DateTime o tipi enum.
Inserimento di più record contemporaneamente:
$database->query('INSERT INTO users ?', [
['name' => 'User 1', 'email' => 'user1@mail.com'],
['name' => 'User 2', 'email' => 'user2@mail.com'],
]);
L'esecuzione di una INSERZIONE in batch è molto più veloce perché viene eseguita una sola query del database invece di più query individuali.
Nota di sicurezza: Non utilizzare mai dati non validati come $values
. Informatevi sui possibili rischi.
Aggiornamento dei dati (UPDATE)
Per aggiornare i record, utilizzare l'istruzione SQL UPDATE
.
// Aggiornare un singolo record
$values = [
'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);
È possibile verificare il numero di righe interessate utilizzando $result->getRowCount()
.
È possibile utilizzare gli operatori +=
e -=
in UPDATE
:
$database->query('UPDATE users SET ? WHERE id = ?', [
'login_count+=' => 1, // incrementare login_count
], 1);
Per inserire o aggiornare un record se esiste già, utilizzare la tecnica ON DUPLICATE KEY UPDATE
:
$values = [
'name' => $name,
'year' => $year,
];
$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?',
$values + ['id' => $id],
$values,
);
// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
// SU CHIAVE DUPLICATA AGGIORNARE `nome` = 'Jim', `anno` = 1978
Si noti che Nette Database riconosce il contesto del comando SQL in cui viene utilizzato un parametro con un array e genera il
codice SQL di conseguenza. Ad esempio, ha costruito (id, name, year) VALUES (123, 'Jim', 1978)
dal primo array,
mentre ha convertito il secondo in name = 'Jim', year = 1978
. Questo aspetto è trattato in modo più dettagliato
nella sezione Suggerimenti per la costruzione di SQL.
Eliminazione di dati (DELETE)
Per cancellare i record, utilizzare l'istruzione SQL DELETE
. Esempio con il numero di righe eliminate:
$count = $database->query('DELETE FROM users WHERE id = ?', 1)
->getRowCount();
Suggerimenti per la costruzione di SQL
I segnaposto SQL consentono di controllare il modo in cui i valori dei parametri vengono incorporati nelle espressioni SQL:
Suggerimento | Descrizione | Usato automaticamente per |
---|---|---|
?name |
Utilizzato per i nomi di tabelle o colonne | |
?values |
Genera (key, ...) VALUES (value, ...) |
INSERT ... ? , REPLACE ... ? |
?set |
Genera assegnazioni key = value, ... |
SET ? , KEY UPDATE ? |
?and |
Unisce le condizioni in un array con AND |
WHERE ? , HAVING ? |
?or |
Unisce le condizioni di una matrice con OR |
– – – |
?order |
Genera la clausola ORDER BY |
ORDER BY ? , GROUP BY ? |
Per inserire dinamicamente nomi di tabelle o colonne, utilizzare il segnaposto ?name
. Nette Database assicura il
corretto escape in base alle convenzioni del database (ad esempio, racchiudendo il tutto in backtick per MySQL).
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
// SELECT `nome` FROM `utenti` WHERE id = 1 (in MySQL)
**Attenzione: ** Usare il segnaposto ?name
solo per nomi di tabelle e colonne convalidati. In caso contrario, si
rischiano vulnerabilità di sicurezza.
Di solito non è necessario specificare altri suggerimenti, poiché Nette utilizza il rilevamento automatico intelligente
quando costruisce le query SQL (vedere la terza colonna della tabella). Tuttavia, è possibile utilizzarli in situazioni in cui si
desidera combinare le condizioni utilizzando OR
invece di AND
:
$database->query('SELECT * FROM users WHERE ?or', [
'name' => 'John',
'email' => 'john@example.com',
]);
// SELEZIONARE * DA UTENTI DOVE `nome` = 'Giovanni' O `email` = 'john@example.com'
Valori speciali
Oltre ai tipi scalari standard (ad esempio, string
, int
, bool
), è possibile passare
come parametri anche valori speciali:
- File: Utilizzare
fopen('file.png', 'r')
per inserire il contenuto binario di un file. - Data e ora: gli oggetti di
DateTime
vengono automaticamente convertiti nel formato di data del database. - Valori enum: Le istanze di
enum
vengono convertite nei valori corrispondenti. - Letterali SQL: Creati con
Connection::literal('NOW()')
, vengono inseriti direttamente nella query.
$database->query('INSERT INTO articles ?', [
'title' => 'My Article',
'published_at' => new DateTime,
'content' => fopen('image.png', 'r'),
'state' => Status::Draft,
]);
Per i database che non supportano in modo nativo il tipo datetime
(ad esempio, SQLite e Oracle), i valori
DateTime
vengono convertiti in base all'opzione di configurazione formatDateTime
(predefinita:
U
per Unix timestamp).
Letterali SQL
In alcuni casi, potrebbe essere necessario inserire codice SQL grezzo come valore, senza trattarlo come una stringa o senza
eseguire l'escape. A tale scopo, si possono utilizzare oggetti della classe Nette\Database\SqlLiteral
, che possono
essere creati con il metodo Connection::literal()
.
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year >' => $database::literal('YEAR()'),
]);
// SELEZIONARE * DA UTENTI DOVE (`nome` = 'Jim') E (`anno` > ANNO())
In alternativa:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > YEAR()'),
]);
// SELECT * FROM utenti DOVE (`nome` = 'Jim') E (anno > ANNO())
I letterali SQL possono anche contenere parametri:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > ? AND year < ?', $min, $max),
]);
// SELECT * FROM utenti DOVE `nome` = 'Jim' E (anno > 1978 E anno < 2017)
In questo modo è possibile ottenere combinazioni flessibili:
$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')
Recupero dei dati
Scorciatoie per le query SELECT
Per semplificare il recupero dei dati, la classe Connection
fornisce diverse scorciatoie che combinano una
chiamata a query()
con una successiva chiamata a fetch*()
. Questi metodi accettano gli stessi parametri
di query()
, cioè una query SQL e parametri opzionali. Una descrizione dettagliata dei metodi di
fetch*()
è riportata di seguito.
fetch($sql, ...$params): ?Row |
Esegue la query e recupera la prima riga come oggetto Row . |
fetchAll($sql, ...$params): array |
Esegue la query e recupera tutte le righe come array di oggetti Row . |
fetchPairs($sql, ...$params): array |
Esegue la query e recupera un array associativo in cui la prima colonna è la chiave e la seconda è il valore. |
fetchField($sql, ...$params): mixed |
Esegue la query e recupera il valore della prima cella della prima riga. |
fetchList($sql, ...$params): ?array |
Esegue la query e recupera la prima riga come array indicizzato. |
Esempio:
// fetchField() - restituisce il valore della prima cella
$count = $database->query('SELECT COUNT(*) FROM articles')
->fetchField();
foreach
– Iterazione su righe
Dopo l'esecuzione di una query, viene restituito un oggetto ResultSet, che consente di iterare sui risultati in
vari modi. Il modo più semplice e più efficiente in termini di memoria per recuperare le righe è quello di iterare in un ciclo
foreach
. Questo metodo elabora le righe una alla volta ed evita di memorizzare tutti i dati in una volta sola.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
//...
}
Il ciclo ResultSet
può essere iterato una sola volta. Se si ha bisogno di iterare più volte, è
necessario caricare prima i dati in un array, ad esempio con il metodo fetchAll()
.
fetch(): ?Row
Esegue la query e recupera una singola riga come oggetto Row
. Se non sono disponibili altre righe, restituisce
null
. Questo metodo fa avanzare il puntatore interno alla riga successiva.
$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // recupera la prima riga
if ($row) {
echo $row->name;
}
fetchAll(): array
Recupera tutte le righe rimanenti da ResultSet
come array di oggetti Row
.
$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // recupera tutte le righe
foreach ($rows as $row) {
echo $row->name;
}
fetchPairs(string|int|null $key = null, string|int|null $value = null): array
Recupera i risultati come array associativo. Il primo argomento specifica la colonna da usare come chiave e il secondo specifica la colonna da usare come valore:
$result = $database->query('SELECT id, name FROM users');
$names = $result->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]
Se viene fornito solo il primo parametro, il valore sarà l'intera riga (come oggetto Row
):
$rows = $result->fetchPairs('id');
// [1 => Row(id: 1, nome: 'John'), 2 => Row(id: 2, nome: 'Jane'), ...]
Se null
viene passato come chiave, l'array sarà indicizzato numericamente a partire da zero:
$names = $result->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]
fetchPairs(Closure $callback): array
In alternativa, è possibile fornire un callback che determina le coppie chiave-valore o i valori per ogni riga.
$result = $database->query('SELECT * FROM users');
$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
// ['1 - John', '2 - Jane', ...]
// Il callback può anche restituire un array con una coppia di chiavi e valori:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]
fetchField(): mixed
Recupera il valore della prima cella della riga corrente. Se non sono disponibili altre righe, restituisce null
.
Questo metodo fa avanzare il puntatore interno alla riga successiva.
$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // recupera il nome dalla prima riga
fetchList(): ?array
Recupera la riga come array indicizzato. Se non sono disponibili altre righe, restituisce null
. Questo metodo fa
avanzare il puntatore interno alla riga successiva.
$result = $database->query('SELECT name, email FROM users');
$row = $result->fetchList(); // ['John', 'john@example.com']
getRowCount(): ?int
Restituisce il numero di righe interessate dall'ultima query UPDATE
o DELETE
. Per le query
SELECT
, restituisce il numero di righe recuperate, ma questo potrebbe non essere sempre noto; in questi casi,
restituisce null
.
getColumnCount(): ?int
Restituisce il numero di colonne di ResultSet
.
Informazioni sulla query
Per recuperare i dettagli della query eseguita più di recente, utilizzare:
echo $database->getLastQueryString(); // produce la query SQL
$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString(); // produce la query SQL
echo $result->getTime(); // fornisce il tempo di esecuzione in secondi
Per visualizzare il risultato come tabella HTML, utilizzare:
$result = $database->query('SELECT * FROM articles');
$result->dump();
È inoltre possibile recuperare informazioni sui tipi di colonna dal sito ResultSet
:
$result = $database->query('SELECT * FROM articles');
$types = $result->getColumnTypes();
foreach ($types as $column => $type) {
echo "$column is of type $type->type"; // Ad esempio, "id è di tipo int".
}
Registrazione delle query
È possibile implementare una registrazione personalizzata delle query. L'evento onQuery
è un array di callback
che vengono invocati dopo ogni esecuzione di query:
$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');
}
};