SQL direto
Você pode trabalhar com o Nette Database de duas maneiras: escrevendo consultas SQL diretamente (Direct Access) ou permitindo que o SQL seja gerado automaticamente(Explorer Access). O Direct Access permite criar consultas com segurança e manter controle total sobre sua estrutura.
Para obter informações sobre como criar uma conexão e configurá-la, consulte a página separada.
Consulta básica
O método query()
executa consultas ao banco de dados e retorna um objeto ResultSet que representa o resultado. Se a
consulta falhar, o método lançará uma exceção. Você pode
percorrer o resultado da consulta usando um loop foreach
ou usar uma das funções
auxiliares.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
}
Para inserir valores com segurança em consultas SQL, use consultas parametrizadas. O Nette Database torna isso muito simples: basta acrescentar uma vírgula e o valor à consulta SQL.
$database->query('SELECT * FROM users WHERE name = ?', $name);
Para vários parâmetros, você pode intercalar a consulta SQL com os parâmetros:
$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);
Ou escrever a consulta SQL inteira primeiro e depois anexar todos os parâmetros:
$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);
Proteção contra injeção de SQL
Por que é importante usar consultas parametrizadas? Porque elas o protegem contra ataques de injeção de SQL, em que os invasores podem injetar comandos SQL mal-intencionados para manipular ou acessar dados do banco de dados.
**Nunca insira variáveis diretamente em uma consulta SQL! Sempre use consultas parametrizadas para se proteger contra injeção de SQL.
// CÓDIGO INSEGURO - vulnerável à injeção de SQL
$database->query("SELECT * FROM users WHERE name = '$name'");
// Consulta parametrizada segura
$database->query('SELECT * FROM users WHERE name = ?', $name);
Não deixe de se familiarizar com os possíveis riscos de segurança.
Técnicas de consulta
Condições WHERE
Você pode escrever as condições WHERE
como uma matriz associativa, em que as chaves são os nomes das colunas e
os valores são os dados a serem comparados. O Nette Database seleciona automaticamente o operador SQL mais adequado com base no
tipo de valor.
$database->query('SELECT * FROM users WHERE', [
'name' => 'John',
'active' => true,
]);
// WHERE `name` = 'John' AND `active` = 1
Você também pode especificar explicitamente o operador na chave:
$database->query('SELECT * FROM users WHERE', [
'age >' => 25, // usa o operador >
'name LIKE' => '%John%', // usa o operador LIKE
'email NOT LIKE' => '%example.com%', // usa o operador NOT LIKE
]);
// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%'
Casos especiais como null
valores ou matrizes são tratados automaticamente:
$database->query('SELECT * FROM products WHERE', [
'name' => 'Laptop', // usa o operador =
'category_id' => [1, 2, 3], // usa IN
'description' => null, // uses IS NULL
]);
// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL
Para condições negativas, use o operador NOT
:
$database->query('SELECT * FROM products WHERE', [
'name NOT' => 'Laptop', // usa o operador <>
'category_id NOT' => [1, 2, 3], // usa NOT IN
'description NOT' => null, // usa IS NOT NULL
'id' => [], // ignorado
]);
// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL
Por padrão, as condições são combinadas usando o operador AND
. Você pode alterar esse comportamento usando
o espaço reservado ?or.
Regras ORDER BY
A cláusula ORDER BY
pode ser definida como uma matriz, em que as chaves representam colunas e os valores são
booleanos que indicam a ordem crescente:
$database->query('SELECT id FROM author ORDER BY', [
'id' => true, // ascendente
'name' => false, // descendente
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC
Inserção de dados (INSERT)
Para inserir registros, use a instrução SQL INSERT
.
$values = [
'name' => 'John Doe',
'email' => 'john@example.com',
];
$database->query('INSERT INTO users ?', $values);
$userId = $database->getInsertId();
O método getInsertId()
retorna o ID da última linha inserida. Para determinados bancos de dados (por exemplo,
PostgreSQL), você deve especificar o nome da sequência usando $database->getInsertId($sequenceId)
.
Você também pode passar valores especiais, como arquivos, objetos DateTime ou tipos de enum, como parâmetros.
Inserção de vários registros de uma só vez:
$database->query('INSERT INTO users ?', [
['name' => 'User 1', 'email' => 'user1@mail.com'],
['name' => 'User 2', 'email' => 'user2@mail.com'],
]);
A execução de um INSERT em lote é muito mais rápida porque apenas uma única consulta ao banco de dados é executada em vez de várias consultas individuais.
Nota de segurança: Nunca use dados não validados como $values
. Familiarize-se com os possíveis riscos.
Atualização de dados (UPDATE)
Para atualizar registros, use a instrução SQL UPDATE
.
// Atualizar um único registro
$values = [
'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);
Você pode verificar o número de linhas afetadas usando $result->getRowCount()
.
Você pode usar os operadores +=
e -=
em UPDATE
:
$database->query('UPDATE users SET ? WHERE id = ?', [
'login_count+=' => 1, // incrementar login_count
], 1);
Para inserir ou atualizar um registro se ele já existir, use a técnica 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)
// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978
Observe que o Nette Database reconhece o contexto do comando SQL no qual um parâmetro com uma matriz é usado e gera
o código SQL de acordo. Por exemplo, ele construiu (id, name, year) VALUES (123, 'Jim', 1978)
a partir da primeira
matriz, enquanto converteu a segunda em name = 'Jim', year = 1978
. Isso é abordado em mais detalhes na seção Dicas para a construção de SQL.
Exclusão de dados (DELETE)
Para excluir registros, use a instrução SQL DELETE
. Exemplo com o número de linhas excluídas:
$count = $database->query('DELETE FROM users WHERE id = ?', 1)
->getRowCount();
Dicas de construção de SQL
Os placeholders de SQL permitem que você controle como os valores de parâmetros são incorporados às expressões SQL:
Dica | Descrição | Usado automaticamente para |
---|---|---|
?name |
Usado para nomes de tabelas ou colunas | – |
?values |
Gera (key, ...) VALUES (value, ...) |
INSERT ... ? , REPLACE ... ? |
?set |
Gera atribuições key = value, ... |
SET ? , KEY UPDATE ? |
?and |
Une condições em uma matriz com AND |
WHERE ? , HAVING ? |
?or |
Une condições em um array com OR |
– |
?order |
Gera a cláusula ORDER BY |
ORDER BY ? , GROUP BY ? |
Para inserir dinamicamente nomes de tabelas ou colunas, use o espaço reservado ?name
. O Nette Database garante
o escape adequado de acordo com as convenções do banco de dados (por exemplo, entre aspas para MySQL).
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
// SELECT `name` FROM `users` WHERE id = 1 (no MySQL)
Aviso: Use apenas o espaço reservado ?name
para nomes validados de tabelas e colunas. Caso contrário,
você corre o risco de sofrer vulnerabilidades de
segurança.
Normalmente, não é necessário especificar outras dicas, pois a Nette usa a detecção automática inteligente ao construir
consultas SQL (consulte a terceira coluna da tabela). No entanto, você pode usá-las em situações em que deseja combinar
condições usando OR
em vez de AND
:
$database->query('SELECT * FROM users WHERE ?or', [
'name' => 'John',
'email' => 'john@example.com',
]);
// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com'
Valores especiais
Além dos tipos escalares padrão (por exemplo, string
, int
, bool
), você também pode
passar valores especiais como parâmetros:
- Files (Arquivos): Use
fopen('file.png', 'r')
para inserir o conteúdo binário de um arquivo. - Data e hora: os objetos
DateTime
são convertidos automaticamente para o formato de data do banco de dados. - Valores de enum: As instâncias de
enum
são convertidas em seus valores correspondentes. - Literais SQL: Criados usando
Connection::literal('NOW()')
, são inseridos diretamente na consulta.
$database->query('INSERT INTO articles ?', [
'title' => 'My Article',
'published_at' => new DateTime,
'content' => fopen('image.png', 'r'),
'state' => Status::Draft,
]);
Para bancos de dados que não têm suporte nativo para o tipo datetime
(por exemplo, SQLite e Oracle), os valores
DateTime
são convertidos de acordo com a opção de configuração formatDateTime
(padrão:
U
para carimbo de data/hora Unix).
Literais SQL
Em alguns casos, talvez seja necessário inserir o código SQL bruto como um valor sem tratá-lo como uma cadeia de caracteres
ou escapar dele. Para isso, use objetos da classe Nette\Database\SqlLiteral
, que podem ser criados usando o método
Connection::literal()
.
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())
Como alternativa:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())
Os literais SQL também podem conter 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)
Isso permite combinações flexíveis:
$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')
Obtenção de dados
Atalhos para consultas SELECT
Para simplificar a recuperação de dados, a classe Connection
oferece vários atalhos que combinam uma chamada
query()
com uma chamada fetch*()
subsequente. Esses métodos aceitam os mesmos parâmetros que
query()
, ou seja, uma consulta SQL e parâmetros opcionais. Uma descrição detalhada dos métodos
fetch*()
pode ser encontrada abaixo.
fetch($sql, ...$params): ?Row |
Executa a consulta e obtém a primeira linha como um objeto Row . |
fetchAll($sql, ...$params): array |
Executa a consulta e obtém todas as linhas como uma matriz de objetos Row . |
fetchPairs($sql, ...$params): array |
Executa a consulta e obtém uma matriz associativa em que a primeira coluna é a chave e a segunda é o valor. |
fetchField($sql, ...$params): mixed |
Executa a consulta e obtém o valor da primeira célula na primeira linha. |
fetchList($sql, ...$params): ?array |
Executa a consulta e obtém a primeira linha como uma matriz indexada. |
Exemplo:
// fetchField() - retorna o valor da primeira célula
$count = $database->query('SELECT COUNT(*) FROM articles')
->fetchField();
foreach
– Iteração sobre linhas
Após a execução de uma consulta, um objeto ResultSet é retornado, o que permite iterar sobre
os resultados de várias maneiras. A maneira mais simples e mais eficiente em termos de memória para buscar linhas é a
iteração em um loop foreach
. Esse método processa as linhas uma de cada vez e evita o armazenamento de todos os
dados na memória de uma só vez.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
//...
}
O ResultSet
só pode ser iterado uma vez. Se precisar iterar várias vezes, você deve primeiro
carregar os dados em uma matriz, por exemplo, usando o método fetchAll()
.
fetch(): ?Row
Executa a consulta e obtém uma única linha como um objeto Row
. Se não houver mais linhas disponíveis, ele
retorna null
. Esse método avança o ponteiro interno para a próxima linha.
$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // busca a primeira linha
if ($row) {
echo $row->name;
}
fetchAll(): array
Obtém todas as linhas restantes do site ResultSet
como uma matriz de objetos Row
.
$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // obtém todas as linhas
foreach ($rows as $row) {
echo $row->name;
}
fetchPairs (string|int|null $key = null, string|int|null $value = null): array
Obtém os resultados como uma matriz associativa. O primeiro argumento especifica a coluna a ser usada como chave e o segundo especifica a coluna a ser usada como valor:
$result = $database->query('SELECT id, name FROM users');
$names = $result->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]
Se apenas o primeiro parâmetro for fornecido, o valor será a linha inteira (como um objeto Row
):
$rows = $result->fetchPairs('id');
// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...]
Se null
for passado como a chave, a matriz será indexada numericamente a partir de zero:
$names = $result->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]
fetchPairs (Closure $callback): array
Como alternativa, você pode fornecer um retorno de chamada que determine os pares de valores-chave ou valores para cada linha.
$result = $database->query('SELECT * FROM users');
$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
// ['1 - John', '2 - Jane', ...]
// A chamada de retorno também pode retornar uma matriz com um par de chave e valor:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]
fetchField(): mixed
Obtém o valor da primeira célula na linha atual. Se não houver mais linhas disponíveis, ele retornará null
.
Esse método avança o ponteiro interno para a próxima linha.
$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // obtém o nome da primeira linha
fetchList(): ?array
Obtém a linha como uma matriz indexada. Se não houver mais linhas disponíveis, ele retornará null
. Esse
método avança o ponteiro interno para a próxima linha.
$result = $database->query('SELECT name, email FROM users');
$row = $result->fetchList(); // ['John', 'john@example.com']
getRowCount(): ?int
Retorna o número de linhas afetadas pela última consulta UPDATE
ou DELETE
. Para as consultas
SELECT
, ele retorna o número de linhas obtidas, mas isso nem sempre é conhecido – nesses casos, ele retorna
null
.
getColumnCount(): ?int
Retorna o número de colunas no site ResultSet
.
Informações da consulta
Para recuperar detalhes sobre a consulta executada mais recentemente, use:
echo $database->getLastQueryString(); // gera a consulta SQL
$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString(); // gera a consulta SQL
echo $result->getTime(); // gera o tempo de execução em segundos
Para exibir o resultado como uma tabela HTML, use:
$result = $database->query('SELECT * FROM articles');
$result->dump();
Você também pode obter informações sobre os tipos de coluna no site ResultSet
:
$result = $database->query('SELECT * FROM articles');
$types = $result->getColumnTypes();
foreach ($types as $column => $type) {
echo "$column is of type $type->type"; // Por exemplo, 'id é do tipo int'
}
Registro de consultas
Você pode implementar o registro de consultas personalizado. O evento onQuery
é uma matriz de retornos de
chamada que são invocados após cada execução de consulta:
$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');
}
};