Fonte de dados: SQL


1Apresentação

Se você configurou corretamente os parâmetros de conexão com o banco de dados, o Temma cria automaticamente um objeto do tipo \Temma\Datasources\Sql. Por convenção, vamos supor que você nomeou essa conexão db no arquivo etc/temma.php (veja a documentação de configuração).

A conexão fica então disponível no controlador da seguinte forma:

$db = $this->db;

Nos demais objetos gerenciados pelo componente de injeção de dependências, a conexão com o banco de dados é acessível da seguinte forma:

$db = $loader->dataSources->db;
$db = $loader->dataSources['db'];

2Configuração

No arquivo etc/temma.php (veja a documentação de configuração), você declara o DSN (Data Source Name) usado para se conectar ao banco de dados.
A forma como o DSN é escrito depende do tipo de banco de dados envolvido. Para servidores de banco de dados clássicos, ele é escrito: TYPE://LOGIN:PASSWORD@SERVER[:PORT]/BASE

Isso se aplica aos seguintes tipos de banco de dados: mysql, mysqli, pgsql, cubrid, sybase, mssql, dblib, firebird, ibm, informix, sqlsrv, oci, odbc, 4D.

Para bancos de dados sqlite e sqlite2, o DSN é: sqlite:/chemin/vers/fichier.sq3


3Chamadas unificadas

Bancos de dados SQL podem ser usados da mesma forma que outras fontes de dados, para armazenar pares chave-valor, tanto de forma bruta (armazenamento de string) quanto serializando dados complexos.

Para isso, a conexão com o banco de dados deve permitir acesso a uma tabela chamada TemmaData, definida da seguinte forma:

CREATE TABLE TemmaData (
    key    CHAR(255) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
    data   LONGTEXT,
    PRIMARY KEY (key)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

3.1Acesso tipo array

// verificação da existência do dado
if (isset($db['key1']))
    doSomething();

// leitura do dado (desserializado)
$data = $db['key1'];

// escrita do dado (serializado)
$db['key1'] = $value;

// exclusão do dado
unset($db['key1']);

// contagem de elementos
$cnt = count($db);

// contagem de elementos correspondentes a um prefixo
$cnt = $db->count('user:');

3.2Métodos gerais

// verificação da existência do dado
if ($db->isSet('user:1'))
    doSomething();

// exclusão do dado
$db->remove('user:1');

// exclusão de múltiplos dados
$db->mRemove(['user:1', 'user:2', 'user:3']);

// exclusão de dados por prefixo
$db->clear('user:');

// exclusão de todos os dados
$db->flush();

3.3Gerenciamento de dados serializados complexos

// busca de chaves correspondentes a um prefixo
$users = $db->search('user:');

// busca de chaves por prefixo, com recuperação dos dados (desserializados)
$users = $db->search('user:', true);

// busca paginada (10 resultados a partir do 20º)
$users = $db->search('user:', true, 20, 10);

// leitura do dado (desserializado)
$user = $db->get('user:1');
// leitura do dado com valor padrão
$color = $db->get('color', 'blue');
// leitura do dado com criação do dado se necessário
$user = $db->get("user:$userId", function() use ($userId) {
    return $this->dao->get($userId);
});

// leitura de múltiplos dados (desserializados)
$users = $db->mGet(['user:1', 'user:2', 'user:3']);

// escrita do dado (serializado)
$db->set('user:1', $userData);

// escrita de múltiplos dados (serializados)
$db->mSet([
    'user:1' => $user1data,
    'user:2' => $user2data,
    'user:3' => $user3data,
]);

3.4Gerenciamento de dados brutos

// busca de chaves correspondentes a um prefixo
$colors = $db->find('color:');

// busca de chaves por prefixo, com recuperação dos dados (brutos)
$colors = $db->find('color:', true);

// busca paginada (10 resultados a partir do 20º)
$colors = $db->find('color:', true, 20, 10);

// leitura do dado (bruto)
$html = $db->read('page:home');
// leitura do dado com valor padrão
$html = $db->read('page:home',
                        '<html><body><h1>Homepage</h1><body><html>');
// leitura do dado com criação do dado se necessário
$html = $db->read('page:home', function() {
    return file_get_contents('/path/to/homepage.html');
});

// leitura de múltiplos dados (brutos)
$pages = $db->mRead(['page:home', 'page:admin', 'page:products']);

// cópia do dado para um arquivo local
$db->copyFrom('page:home', '/path/to/newpage.html');
// cópia do dado para um arquivo local, com valor padrão
$db->copyFrom('page:home', '/path/to/newpage.html', $defaultHtml);
// cópia do dado para um arquivo local, com criação do dado se necessário
$db->copyFrom('page:home', '/path/to/newpage.html', function() {
    return file_get_contents('/path/to/oldpage.html');
});

// escrita do dado (bruto)
$db->write('color:blue', '#0000ff');

// escrita de múltiplos dados (brutos)
$db->mWrite([
    'color:blue'  => '#0000ff',
    'color:red'   => '#ff0000',
    'color:green' => '#00ff00',
]);

// escrita do dado (bruto) a partir de um arquivo local
$db->copyTo('page:home', '/path/to/homepage.html');

// escrita de múltiplos dados (brutos) a partir de arquivos locais
$db->mCopyTo([
    'page:home'     => '/path/to/homepage.html',
    'page:admin'    => '/path/to/admin.html',
    'page:products' => '/path/to/products.html',
]);

4Chamadas específicas

4.1exec()

exec(string $sql, [bool $buffered=false], [?array $parameters=null]) : void

O método exec() é usado para executar consultas para as quais nenhum dado de resultado é esperado.

Parâmetros:

  1. A consulta SQL a ser executada.
  2. (opcional) true para colocar a consulta em buffer. Uma consulta em buffer fica em espera até que uma consulta não bufferizada seja executada; nesse momento, todas as consultas em buffer pendentes são executadas primeiro.
  3. (opcional) Um array contendo os parâmetros da consulta.

Se parâmetros forem fornecidos, a consulta não é colocada em buffer.

Exemplos:

// exclui um artigo pelo seu identificador
$db->exec("DELETE FROM article WHERE id = '12'");

// atualiza a data do último login
// de todos os usuários para os quais essa data não está definida
$sql = "UPDATE user
        SET lastLogin = NOW()
        WHERE lastLogin IS NULL";
$db->exec($sql);

Exemplo usando parâmetros:

// exclui um artigo pelo seu identificador
$db->exec(
    "DELETE FROM article WHERE id = :id",
    parameters: ['id' => 12],
);

// insere um novo usuário
$db->exec(
    "INSERT INTO user
     SET email = :email,
         name = :name,
         admin = :isAdmin",
    parameters: [
        'email' => 'vador@darkside.com',
        'name'  => 'Darth Vader',
        'admin' => true,
    ]
);

4.2queryOne()

queryOne(string $sql, [?string $valueField=null], [?array $parameters=null]) : array

O método queryOne() executa uma consulta e retorna uma única linha de dados. O resultado é retornado como um array associativo cujas chaves correspondem aos nomes das colunas.

Parâmetros:

  1. A consulta SQL a ser executada.
  2. (opcional) O nome do campo a ser retornado. Por padrão, o array associativo completo é retornado.
  3. (opcional) Um array contendo os parâmetros da consulta.

Se houver consultas em buffer pendentes, elas são executadas primeiro.

Exemplo de uso básico:

$sql = "SELECT * FROM article WHERE id = '11'";
$data = $db->queryOne($sql);
print_r($data);

O que produz um resultado semelhante a:

Array
(
    [id] => 11
    [status] => valid
    [title] => Título do artigo
    [creationDate] => 2021-04-04 23:17:39
    [content] => Texto do artigo...
    [authorId] => 2
)

O segundo parâmetro pode conter um nome de campo. Nesse caso, o método retorna diretamente o valor desse campo.

Exemplo:

$sql = "SELECT COUNT(*) AS cnt FROM article";
$cnt = $db->queryOne($sql, 'cnt');
print($cnt); // exibe, por exemplo, 1234

O terceiro parâmetro permite passar parâmetros que serão escapados com segurança.

Exemplo:

$sql = "SELECT * FROM article
        WHERE date_creation > :dateFrom
          AND status = :status
        ORDER BY id DESC
        LIMIT 1";
$article = $db->queryOne(
    $sql,
    parameters: [
        'dateFrom' => '1990-01-01',
        'status'   => 'valid',
    ]
);

Outro exemplo usando os três parâmetros:

$sql = "SELECT title FROM article WHERE id = :id";
$title = $db->queryOne(
    $sql,
    'title',
    ['id' => 123]
);

4.3queryAll()

queryAll(string $sql, [string $key], [string $valueField], [?array $parameters=null]) : array

O método queryAll() executa consultas que retornam múltiplas linhas de dados. Os resultados são retornados como uma lista, onde cada elemento é um array associativo representando uma linha, com chaves correspondentes aos nomes das colunas.

Parâmetros:

  1. A consulta SQL a ser executada.
  2. (opcional) O nome do campo usado para indexar as linhas.
  3. (opcional) O nome do campo a ser retornado como valor de cada linha.
  4. (opcional) Um array contendo os parâmetros da consulta.

Se houver consultas em buffer pendentes, elas são executadas primeiro.

O primeiro parâmetro contém a consulta SQL a ser executada.

Por exemplo, o código a seguir:

$sql = "SELECT * FROM article WHERE authorId = '2'";
$data = $db->queryAll($sql);
print_r($data);

Produzirá um resultado semelhante a:

Array
(
    [0] => Array
        (
            [id] => 11
            [status] => valid
            [title] => Título do artigo
            [creationDate] => 2011-04-04 23:17:39
            [content] => Texto do artigo ...
            [authorId] => 2
        )

    [1] => Array
        (
            [id] => 12
            [status] => valid
            [title] => Outro título
            [creationDate] => 2011-04-05 20:34:20
            [content] => Texto diferente ...
            [authorId] => 2
        )

    [2] => Array
        (
            [id] => 16
            [status] => waiting
            [title] => Terceiro artigo
            [creationDate] => 2011-04-10 11:03:44
            [content] => Outro texto ...
            [authorId] => 2
        )
)

O segundo parâmetro ($key) contém o nome do campo usado para indexar a lista de resultados.

Por exemplo, o código a seguir:

$sql = "SELECT * FROM article WHERE authorId = '2'";
$data = $db->queryAll($sql, 'id');
print_r($data);

Produzirá um resultado semelhante a:

Array
(
    [11] => Array
        (
            [id] => 11
            [status] => valid
            [title] => Título do artigo
            [creationDate] => 2011-04-04 23:17:39
            [content] => Texto do artigo ...
            [authorId] => 2
        )

    [12] => Array
        (
            [id] => 12
            [status] => valid
            [title] => Outro título
            [creationDate] => 2011-04-05 20:34:20
            [content] => Texto diferente ...
            [authorId] => 2
        )

    [16] => Array
        (
            [id] => 16
            [status] => waiting
            [title] => Terceiro artigo
            [creationDate] => 2011-04-10 11:03:44
            [content] => Outro texto ...
            [authorId] => 2
        )
)

O terceiro parâmetro ($valueField) pode conter o nome de um campo que será usado para retornar um único valor por linha.

Por exemplo, o código a seguir:

$sql = "SELECT * FROM article WHERE authorId = '2'";

$data = $db->queryAll($sql, null, 'title');
print_r($data);

$data = $db->queryAll($sql, 'id', 'title');
print_r($data);

Produzirá um resultado semelhante a:

Array
(
    [0] => Título do artigo
    [1] => Outro título
    [2] => Terceiro artigo
)
Array
(
    [11] => Título do artigo
    [12] => Outro título
    [16] => Terceiro artigo
)

O quarto parâmetro (parameters) contém uma lista de parâmetros que serão fornecidos à consulta e escapados com segurança:

$sql = "SELECT * FROM article WHERE status = :status";
$articles = $db->queryAll(
    $sql,
    parameters: ['status' => 'valid']
);

Exemplo usando os quatro parâmetros:

$titles = $db->queryAll(
    "SELECT * FROM article WHERE status = :status",
    'id',
    'title',
    ['status' => 'valid']
);

Isso pode produzir um resultado como:

Array
(
    [11] => Título do artigo
    [12] => Outro título
    [16] => Terceiro artigo
)

4.4lastInsertId()

lastInsertId(void) : int|string

O método lastInsertId() retorna a chave primária do último registro criado na conexão atual com o banco de dados.

Exemplo de uso:

// adiciona um artigo ao banco de dados
$sql = "INSERT INTO article
        SET title = 'Título', content = 'Texto', creationDate = NOW()";
$db->exec($sql);

// obtém o identificador do novo artigo
$id = $db->lastInsertId();

4.5Escape de caracteres especiais

Ao escrever consultas SQL, é preciso ter um cuidado especial para evitar vulnerabilidades de injeção de SQL. Para evitar isso, os caracteres especiais devem ser escapados, seja usando o argumento parameters (nos métodos exec(), queryOne() e queryAll()), seja realizando o escape explicitamente.

Para isso, o objeto \Temma\Datasources\Sql fornece os métodos quote() e quoteNull().

Ambos os métodos recebem uma string como parâmetro e a retornam após escapar todos os caracteres especiais. Eles também adicionam aspas simples no início e no final da string escapada.

O método quoteNull() retorna a string NULL (sem aspas) se receber uma string vazia, um valor igual a zero, ou um array vazio.

Exemplos:

$str = "Título do artigo";
$sql = "INSERT INTO article SET text = " . $db->quote($str);
// INSERT INTO article SET text = 'Título do artigo'

$str = '';
$sql = "INSERT INTO article SET text = " . $db->quote($str);
// INSERT INTO article SET text = ''

$str = '';
$sql = "INSERT INTO article SET text = " . $db->quoteNull($str);
// INSERT INTO article SET text = NULL

4.6Requisições em buffer

Como explicado acima, o método exec() permite colocar consultas em buffer, para que elas fiquem em espera até que uma consulta não bufferizada seja executada (e, nesse caso, todas as consultas em espera são executadas primeiro).

É possível forçar a execução de todas as consultas em espera chamando o método execBufferedRequests().

Exemplo:

// execução de uma consulta em buffer
$db->exec("DELETE FROM article");

// ...

// execução das consultas em espera
$db->execBufferedRequests();

5Requisições preparadas

O objeto \Temma\Datasources\Sql permite executar consultas preparadas com antecedência, modificando apenas os parâmetros usados em cada execução.

Os parâmetros podem ser fornecidos na forma de uma lista, cujos elementos substituem os pontos de interrogação colocados na consulta:

// prepara a consulta
$sql = "DELETE FROM article WHERE status = ? AND title != ?";
$statement = $db->prepare($sql);
// primeira execução
$statement->exec(['invalid', 'Este é bom']);
// segunda execução
$statement->exec(['waiting', 'Este também']);

Os parâmetros podem ser nomeados na consulta, e fornecidos como um array associativo:

// prepara a consulta
$sql = "DELETE FROM article WHERE status = :status AND title != :title";
$statement = $db->prepare($sql);
// primeira execução
$statement->exec([
    ':status' => 'invalid',
    ':title' => 'Este é bom',
]);
// segunda execução
$statement->exec([
    ':status' => 'waiting',
    ':title' => 'Este também',
]);

Em ambos os casos, os parâmetros são escapados automaticamente (veja os métodos quote() e quoteNull()).

Também é possível usar consultas preparadas para recuperar dados. Os métodos queryOne() e queryAll() são usados para recuperar uma linha de dados e todas as linhas de dados, respectivamente. O primeiro retorna um array associativo cujas chaves são os nomes das colunas; o segundo retorna uma lista de arrays associativos.
Assim como com o método exec(), esses métodos podem receber uma lista de parâmetros, ou um array associativo de parâmetros.

// prepara a consulta
$sql = "SELECT * FROM article WHERE id = ?";
$statement = $db->prepare($sql);
// primeira leitura
$article1 = $statement->queryOne([12]);
// segunda leitura
$article2 = $statement->queryOne([23]);

// prepara a consulta
$sql = "SELECT * FROM article WHERE status = :status";
$statement = $db->prepare($sql);
// leitura
$articles = $statement->queryAll([':status' => 'invalid']);

6Gerenciamento de transações

O objeto \Temma\Datasources\Sql oferece duas formas diferentes de gerenciar transações.

Por meio de uma função anônima, passada como parâmetro para o método transaction(). Se uma exceção for lançada dentro da função anônima, um rollback automático é realizado; caso contrário, um commit é realizado automaticamente ao final da execução da função anônima.

// criamos a transação
// a função anônima recebe a conexão
// com o banco de dados como parâmetro
$db->transaction(function($db) use ($userId) {
    // executamos várias consultas
    $db->exec(...);
    $db->exec(...);
    $db->exec(...);
});

Ou de forma explícita, usando os métodos startTransaction(), commit() e rollback():

// abrimos uma transação
$db->startTransaction();

// executamos várias consultas
$db->exec(...);
$db->exec(...);
$db->exec(...);

// processamento condicional
if ($something) {
    // a transação é aceita
    $db->commit();
} else {
    // a transação é cancelada
    $db->rollback();
}