La via dell'SQL

È possibile lavorare con Nette Database in due modi: scrivendo query SQL (modo SQL) o lasciando che l'SQL venga generato automaticamente(modo Explorer). Il metodo SQL consente di creare query in modo sicuro, mantenendo il pieno controllo sulla loro struttura.

Per maggiori dettagli sulla configurazione della connessione al database, vedere Connessione e configurazione.

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 &lt; ?', $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');
	}
};
versione: 4.0