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.