Nucleo del database

Nette Database Core è il livello di astrazione del database e fornisce le funzionalità principali.

Installazione

Scaricare e installare il pacchetto utilizzando Composer:

composer require nette/database

Connessione e configurazione

Per connettersi al database, è sufficiente creare un'istanza della classe Nette\Database\Connection:

$database = new Nette\Database\Connection($dsn, $user, $password);

Il parametro $dsn (nome dell'origine dati) è lo stesso utilizzato da PDO, ad esempio host=127.0.0.1;dbname=test. In caso di fallimento, viene lanciato Nette\Database\ConnectionException.

Tuttavia, un modo più sofisticato offre la configurazione dell'applicazione. Aggiungiamo una sezione database che crea gli oggetti necessari e un pannello del database nella barra Tracy.

database:
	dsn: 'mysql:host=127.0.0.1;dbname=test'
	user: root
	password: password

L'oggetto connessione che riceviamo come servizio da un contenitore DI, ad esempio:

class Model
{
	// passa Nette\Database\Explorer per lavorare con il livello Database Explorer
	public function __construct(
		private Nette\Database\Connection $database,
	) {
	}
}

Per ulteriori informazioni, vedere Configurazione del database.

Query

Per interrogare il database utilizzare il metodo query() che restituisce ResultSet.

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
}

echo $result->getRowCount(); // restituisce il numero di righe se è conosciuto

Su ResultSet è possibile iterare una sola volta; se abbiamo bisogno di iterare più volte, è necessario convertire il risultato in array con il metodo fetchAll().

È possibile aggiungere facilmente dei parametri alla query, come il punto interrogativo:

$database->query('SELECT * FROM users WHERE name = ?', $name);

$database->query('SELECT * FROM users WHERE name = ? AND active = ?', $name, $active);

$database->query('SELECT * FROM users WHERE id IN (?)', $ids); // $ids è un array

ATTENZIONE, non concatenare mai le stringhe per evitare vulnerabilità da SQL injection!

$db->query('SELECT * FROM users WHERE name = ' . $name); // WRONG!!!

In caso di fallimento, query() lancia Nette\Database\DriverException o uno dei suoi discendenti:

Oltre a query(), esistono altri metodi utili:

// restituisce l'array associativo id => nome
$pairs = $database->fetchPairs('SELECT id, name FROM users');

// restituisce tutte le righe come array
$rows = $database->fetchAll('SELECT * FROM users');

// restituisce una singola riga
$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id);

// restituisce un singolo campo
$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id);

In caso di fallimento, tutti questi metodi lanciano Nette\Database\DriverException.

Inserire, aggiornare e cancellare

Il parametro che inseriamo nella query SQL può anche essere un array (in questo caso è possibile saltare l'istruzione jolly ?), which may be useful for the INSERT ):

$database->query('INSERT INTO users ?', [ // qui si può omettere il punto interrogativo
	'name' => $name,
	'year' => $year,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978)

$id = $database->getInsertId(); // restituisce l'autoincremento della riga inserita

$id = $database->getInsertId($sequence); // o il valore della sequenza

Inserimento multiplo:

$database->query('INSERT INTO users', [
	[
		'name' => 'Jim',
		'year' => 1978,
	], [
		'name' => 'Jack',
		'year' => 1987,
	],
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987)

Si possono passare anche file, oggetti DateTime o enumerazioni:

$database->query('INSERT INTO users', [
	'name' => $name,
	'created' => new DateTime, // o $database::literal('NOW()')
	'avatar' => fopen('image.gif', 'r'), // inserisce il contenuto del file
	'status' => State::New, // enum State
]);

Aggiornamento delle righe:

$result = $database->query('UPDATE users SET', [
	'name' => $name,
	'year' => $year,
], 'WHERE id = ?', $id);
// UPDATE users SET `name` = 'Jim', `year` = 1978 WHERE id = 123

echo $result->getRowCount(); // restituisce il numero delle righe interessate

Per l'UPDATE si possono utilizzare gli operatori += e -=:

$database->query('UPDATE users SET', [
	'age+=' => 1, // note +=
], 'WHERE id = ?', $id);
// UPDATE users SET `age` = `age` + 1

Eliminazione:

$result = $database->query('DELETE FROM users WHERE id = ?', $id);
echo $result->getRowCount(); // restituisce il numero di righe interessate

Query avanzate

Inserire o aggiornare, se esiste già:

$database->query('INSERT INTO users', [
	'id' => $id,
	'name' => $name,
	'year' => $year,
], 'ON DUPLICATE KEY UPDATE', [
	'name' => $name,
	'year' => $year,
]);
// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
//   ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978

Si noti che Nette Database riconosce il contesto SQL in cui è inserito il parametro dell'array e costruisce il codice SQL di conseguenza. Quindi, dal primo array genera (id, name, year) VALUES (123, 'Jim', 1978), mentre il secondo si converte in name = 'Jim', year = 1978.

Possiamo anche descrivere l'ordinamento utilizzando un array, in cui le chiavi sono nomi di colonne e i valori sono booleani che determinano se ordinare in ordine crescente:

$database->query('SELECT id FROM author ORDER BY', [
	'id' => true, // crescente
	'name' => false, // discendente
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC

Se il rilevamento non funziona, è possibile specificare la forma dell'insieme con un carattere jolly ? seguito da un suggerimento. Sono supportati questi suggerimenti:

?values (key1, key2, …) VALUES (value1, value2, …)
?set key1 = valore1, key2 = valore2, …
?and key1 = valore1 AND key2 = valore2 …
?or key1 = valore1 OR key2 = valore2 …
?order key1 ASC, key2 DESC

La clausola WHERE utilizza l'operatore ?and, quindi le condizioni sono collegate da AND:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	'year' => $year,
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND `year` = 1978

Che può essere facilmente modificato in OR utilizzando il carattere jolly ?or:

$result = $database->query('SELECT * FROM users WHERE ?or', [
	'name' => $name,
	'year' => $year,
]);
// SELECT * FROM users WHERE `name` = 'Jim' OR `year` = 1978

Possiamo usare gli operatori nelle condizioni:

$result = $database->query('SELECT * FROM users WHERE', [
	'name <>' => $name,
	'year >' => $year,
]);
// SELECT * FROM users WHERE `name` <> 'Jim' AND `year` > 1978

E anche le enumerazioni:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => ['Jim', 'Jack'],
	'role NOT IN' => ['admin', 'owner'], // enumeration + operator NOT IN
]);
// SELECT * FROM users WHERE
//   `name` IN ('Jim', 'Jack') AND `role` NOT IN ('admin', 'owner')

Possiamo anche includere un pezzo di codice SQL personalizzato utilizzando il cosiddetto letterale SQL:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())

In alternativa:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())

Anche i letterali SQL possono avere i loro parametri:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > ? AND year < ?', $min, $max),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017)

Grazie a questi si possono creare interessanti combinazioni:

$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')

Nome della variabile

Esiste un carattere jolly ?name da utilizzare se il nome della tabella o della colonna è una variabile. (Attenzione, non permettete all'utente di manipolare il contenuto di tale variabile):

$table = 'blog.users';
$column = 'name';
$database->query('SELECT * FROM ?name WHERE ?name = ?', $table, $column, $name);
// SELECT * FROM `blog`.`users` WHERE `name` = 'Jim'

Transazioni

Esistono tre metodi per gestire le transazioni:

$database->beginTransaction();

$database->commit();

$database->rollback();

Un modo elegante è offerto dal metodo transaction(). Si passa il callback che viene eseguito nella transazione. Se viene lanciata un'eccezione durante l'esecuzione, la transazione viene abbandonata; se tutto va bene, la transazione viene impegnata.

$id = $database->transaction(function ($database) {
	$database->query('DELETE FROM ...');
	$database->query('INSERT INTO ...');
	// ...
	return $database->getInsertId();
});

Come si può vedere, il metodo transaction() restituisce il valore di ritorno della callback.

Il metodo transaction() può anche essere annidato, il che semplifica l'implementazione di repository indipendenti.

Riflessione

Nette Database offre strumenti per l'introspezione della struttura del database attraverso la classe Nette\Database\Reflection. Questa classe consente di recuperare informazioni su tabelle, colonne, indici e chiavi esterne. È possibile utilizzare la riflessione per generare schemi, creare applicazioni flessibili che lavorano con i database o costruire strumenti generali per i database.

È possibile ottenere un oggetto reflection da un'istanza di connessione al database:

$reflection = $database->getReflection();

Lavorare con le tabelle

Utilizzando la riflessione, è possibile iterare su tutte le tabelle del database:

// Elencare i nomi di tutte le tabelle
foreach ($reflection->tables as $tableName => $table) {
    echo $tableName . "\n";
}

// Controllare se una tabella esiste
if ($reflection->hasTable('users')) {
    echo "The 'users' table exists";
}

// Recuperare una tabella specifica
$table = $reflection->getTable('users');

Informazioni sulla colonna

Per ogni tabella è possibile ottenere informazioni dettagliate sulle sue colonne:

// Iterare su tutte le colonne
foreach ($table->columns as $column) {
    echo "Column: " . $column->name . "\n";
    echo "Type: " . $column->nativeType . "\n";
    echo "Nullable: " . ($column->nullable ? 'Yes': 'No') . "\n";
    echo "Default value: " . ($column->default ?? 'None') . "\n";
    echo "Primary key: " . ($column->primary ? 'Yes': 'No') . "\n";
    echo "Auto-increment: " . ($column->autoIncrement ? 'Yes': 'No') . "\n";
}

// Recuperare una colonna specifica
$idColumn = $table->getColumn('id');

Indici e chiavi primarie

La riflessione fornisce informazioni su indici e chiavi primarie:

$listColumnNames = fn(array $columns) => implode(', ', array_map(fn($col) => $col->name, $columns));

// Elencare tutti gli indici
foreach ($table->indexes as $index) {
    echo "Index: " . ($index->name ?? 'Unnamed') . "\n";
    echo "Columns: " . $listColumnNames($index->columns) . "\n";
    echo "Unique: " . ($index->unique ? 'Yes': 'No') . "\n";
    echo "Primary key: " . ($index->primary ? 'Yes': 'No') . "\n";
}

// Recuperare la chiave primaria
if ($table->primaryKey) {
    echo "Primary key: " . $listColumnNames($table->primaryKey->columns) . "\n";
}

Chiavi straniere

È inoltre possibile ottenere informazioni sulle chiavi esterne:

foreach ($table->foreignKeys as $fk) {
    echo "Foreign key: " . ($fk->name ?? 'Unnamed') . "\n";
    echo "Local columns: " . $listColumnNames($fk->localColumns) . "\n";
    echo "References table: {$fk->foreignTable->name}\n";
    echo "References columns: " . $listColumnNames($fk->foreignColumns) . "\n";
}
versione: 4.0