Núcleo do banco de dados

Nette Database Core é a camada de abstração do banco de dados e fornece a funcionalidade do núcleo.

Instalação

Baixe e instale o pacote usando o Composer:

composer require nette/database

Conexão e configuração

Para conectar-se ao banco de dados, basta criar uma instância da classe Nette\Database\Connection:

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

O parâmetro $dsn (nome da fonte de dados) é o mesmo que o utilizado pela DOP, por exemplo host=127.0.0.1;dbname=test. Em caso de falha, ele lança Nette\Database\ConnectionException.

Entretanto, uma maneira mais sofisticada oferece configuração de aplicação. Acrescentaremos uma seção database e ela cria os objetos necessários e um painel de banco de dados na barra Tracy.

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

O objeto de conexão que recebemos como um serviço de um container DI, por exemplo:

class Model
{
	// pass Nette\Database\Explorer para trabalhar com a camada Database Explorer\Database
	public function __construct(
		private Nette\Database\Connection $database,
	) {
	}
}

Para mais informações, consulte a configuração do banco de dados.

Consultas

Para consultar o banco de dados utilize o método query() que retorna o ResultSet.

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

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

echo $result->getRowCount(); // devolve o número de filas se for conhecido

Sobre o ResultSet é possível iterar apenas uma vez, se precisarmos iterar várias vezes, é necessário converter o resultado para a matriz através do método fetchAll().

Você pode facilmente adicionar parâmetros à consulta, anote o ponto de interrogação:

$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 is array

AVISO, nunca concatenar cordas para evitar vulnerabilidade de injeção SQL!

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

Em caso de falha query() lança ou Nette\Database\DriverException ou um de seus descendentes:

Além de query(), existem outros métodos úteis:

// retorna a matriz associativa id => nome
$pairs = $database->fetchPairs('SELECT id, name FROM users');

// devolve todas as filas como matriz
$rows = $database->fetchAll('SELECT * FROM users');

// retorna em fila única
$row = $database->fetch('SELECT * FROM users WHERE id = ?', $id);

// retornar campo único
$name = $database->fetchField('SELECT name FROM users WHERE id = ?', $id);

Em caso de falha, todos esses métodos jogam Nette\Database\DriverException.

Inserir, atualizar e excluir

O parâmetro que inserimos na consulta SQL também pode ser o array (nesse caso é possível pular a declaração wildcard ?), which may be useful for the INSERT:

$database->query('INSERT INTO users ?', [ // aqui pode ser omitido o ponto de interrogação
	'name' => $name,
	'year' => $year,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978)

$id = $database->getInsertId(); // devolve o auto-incremento da linha inserida

$id = $database->getInsertId($seqüência); // ou valor da seqüência

Inserção múltipla:

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

Também podemos passar arquivos, objetos DateTime ou enumerações:

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

Atualização de linhas:

$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(); // devolve o número de filas afetadas

Para a atualização, podemos utilizar os operadores += e -=:

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

Eliminação:

$result = $database->query('DELETE FROM users WHERE id = ?', $id);
echo $result->getRowCount(); // returns the number of affected rows

Consultas avançadas

Inserir ou atualizar, se já existir:

$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

Observe que o Nette Database reconhece o contexto SQL no qual o parâmetro da matriz é inserido e constrói o código SQL de acordo. Assim, a partir do primeiro array ele gera (id, name, year) VALUES (123, 'Jim', 1978), enquanto o segundo converte para name = 'Jim', year = 1978.

Também podemos descrever a ordenação usando matriz, em chaves estão os nomes das colunas e os valores são booleanos que determinam se a ordenação deve ser feita em ordem ascendente:

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

Se a detecção não funcionou, você pode especificar a forma da montagem com um curinga ? seguido de uma dica. Estas dicas são suportadas:

?valores (chave1, chave2, …) VALORES (valor1, valor2, …)
?set chave1 = valor1, chave2 = valor2, …
?e chave1 = valor1 E chave2 = valor2 …
?ou chave1 = valor1 OU chave2 = valor2 …
?ordem chave1 ASC, chave2 DESC

A cláusula WHERE utiliza o operador ?and, portanto as condições estão vinculadas por AND:

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

O que pode ser facilmente alterado para OR, utilizando o wildcard ?or:

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

Podemos utilizar os operadores em condições:

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

E também enumerações:

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

Também podemos incluir uma peça de código SQL personalizada usando o chamado SQL literal:

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

Alternativamente:

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

SQL literal também pode ter seus parâmetros:

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

Graças a isso, podemos criar combinações interessantes:

$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 da variável

Há um wildcard ?name que você utiliza se o nome da tabela ou nome da coluna for uma variável. (Cuidado, não permita que o usuário manipule o conteúdo de tal variável):

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

Transações

Há três métodos para lidar com as transações:

$database->beginTransaction();

$database->commit();

$database->rollback();

Uma maneira elegante é oferecida pelo método transaction(). Você passa o callback que é executado na transação. Se for lançada uma exceção durante a execução, a transação é descartada, se tudo correr bem, a transação é comprometida.

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

Como você pode ver, o método transaction() retorna o valor de retorno da ligação de retorno.

A transação() também pode ser aninhada, o que simplifica a implementação de repositórios independentes.

Reflexão

O Nette Database fornece ferramentas para introspecção da estrutura do banco de dados por meio da classe Nette\Database\Reflection. Essa classe permite que você recupere informações sobre tabelas, colunas, índices e chaves estrangeiras. Você pode usar a reflexão para gerar esquemas, criar aplicativos flexíveis que funcionem com bancos de dados ou criar ferramentas gerais de banco de dados.

É possível obter um objeto de reflexão de uma instância de conexão de banco de dados:

$reflection = $database->getReflection();

Trabalhando com tabelas

Usando a reflexão, você pode iterar sobre todas as tabelas do banco de dados:

// Listar os nomes de todas as tabelas
foreach ($reflection->tables as $tableName => $table) {
    echo $tableName . "\n";
}

// Verificar se uma tabela existe
if ($reflection->hasTable('users')) {
    echo "The 'users' table exists";
}

// Recuperar uma tabela específica
$table = $reflection->getTable('users');

Informações sobre a coluna

Para cada tabela, você pode obter informações detalhadas sobre suas colunas:

// Iterar por todas as colunas
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";
}

// Recuperar uma coluna específica
$idColumn = $table->getColumn('id');

Índices e chaves primárias

A reflexão fornece informações sobre índices e chaves primárias:

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

// Listar todos os índices
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";
}

// Recuperar a chave primária
if ($table->primaryKey) {
    echo "Primary key: " . $listColumnNames($table->primaryKey->columns) . "\n";
}

Chaves estrangeiras

Você também pode obter informações sobre chaves estrangeiras:

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";
}
versão: 4.0