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:
- ConstraintViolationException – violação de qualquer restrição
- ForeignKeyConstraintViolationException – chave estrangeira inválida
- NotNullConstraintViolationException – violação da condição NOT NULL
- UniqueConstraintViolationException – conflito de índice único
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";
}