Acentuação em PHP com MySQL

Por André Ataíde em 03/11/2019.

Ao iniciar sua jornada na linguagem PHP e estabelecer conexões com os bancos de dados MySQL ou MariaDB, é comum deparar-se com desafios relacionados à exibição adequada de acentuação no navegador. Nesse cenário, é possível observar substituições de acentos e do caractere "ç" por símbolos estranhos ou mesmo interrogações dentro de losangos. Essa questão também pode surgir ao salvar informações no banco de dados. Essa problemática é frequentemente originada da discrepância entre as diversas tabelas de caracteres utilizadas pelos diferentes sistemas em comunicação.

Com a chegada e popularização do HTML5, isso fica mais evidente, já que, por padrão, páginas HTML5 usam o conjunto de caracteres UTF-8.

<!DOCTYPE html>

<html lang="pt-br">

<head>

    <meta charset="UTF-8">

    <title>Minha página</title>

</head>

<body>

    ...    

</body>

</html>

Assim, é importante verificar se todos os sistemas também usam UTF-8 que é uma charset universal que pode representar e converter caracteres de praticamente qualquer sistema, porque usa como base o padrão Unicode.

Criando sites em UTF-8

Se você usa PHP, a primeira linha a ser interpretada pelo seu script deve ser:

<?php header("Content-type: text/html; charset=utf-8"); ?>

No HTML5, como já vimos, é importante que a linha abaixo esteja dentro da tag <header>...</header>:    

<meta charset="UTF-8">

Outra coisa importante é setar o UTF-8 como conjunto de caracteres padrão do SGBD, quando este se relacionar com seus scripts PHP. Para isso, costumo criar um arquivo de configuração inicial das páginas, que é "incluído" em todos os scripts do site, antes de qualquer outro código PHP ou de "frontend". Observe que isso já faz a conexão com o banco de dados e seta as queries para UTF-8: 

<?php

# Informa qual o conjunto de caracteres será usado

header('Content-Type: text/html; charset=utf-8');


# Conecta ao banco de dados

$conn = new mysqli('servidor', 'usuario', 'senha', 'banco');


# Trap de erros de conexão

if ($conn->connect_error) die("Erro no servidor: " . $conn->connect_error); 


# Aqui está o segredo da conexão em UTF-8

$conn->query("SET NAMES 'utf8'");

$conn->query('SET character_set_connection=utf8');

$conn->query('SET character_set_client=utf8');

$conn->query('SET character_set_results=utf8');


# Meses e dias da semana em pt/BR

$conn->query('SET lc_time_names = "pt_BR"');


# Outras configurações iniciais

•••


O código acima faz a conexão com o MySQL usando a biblioteca "MySQLi" e seta todas as trocas de dados entre banco de dados e o PHP para UTF-8. Ainda aproveitei para setar o MySQL para traduzir os nomes dos meses e dias da semana para português/Brasil no trexo:

$conn->query('SET lc_time_names = "pt_BR"');

Bases de dados em UTF-8

No MySQL ou MariaDB, sempre crie bancos de dados com o conjunto de caracteres UTF-8, como no exemplo abaixo: 

CREATE DATABASE bancoteste CHARACTER SET utf8 COLLATE utf8_general_ci;

No exemplo, definimos o conjunto de caracteres a ser usando com o banco de dados como UTF-8 e também o agrupamento usado nas consultas. No caso do agrupamento, existem várias alternativas onde, algumas são:

utf8_general_ci

Consultas em qualquer idioma devem usar o conjunto UTF-8 e são "case-insensitive", ou seja, ignoram a diferença entre maiúsculas e minúsculas. Assim, a query:

SELECT * FROM usuario WHERE nome = 'Joca Silva';

e a query: 

SELECT * FROM usuario WHERE nome = 'joca silva';

tem o mesmo resultado.

utf8_swedish_ci 

A "consulta sueca" também deve usar o conjunto UTF-8, são "case-insensitive", ou seja, ignoram a diferença entre maiúsculas e minúsculas. A diferença para utf8_general_ci é que esta ignora os acentos. Assim, todas as query abaixo retornam o mesmo resultado: 

SELECT * FROM cadastro WHERE cidade = "São Paulo";

SELECT * FROM cadastro WHERE cidade = "Sao Paulo";

SELECT * FROM cadastro WHERE cidade = "são paulo";

SELECT * FROM cadastro WHERE cidade = "Sao Paulo";

...

Essa é a teoria (que está na documentação do MySQL) porque na prática, talvez por questões relacionadas ao sistema operacional e ao modo de compilação do SGBD, as consultas a tabelas com collate utf8_swedish_ci se comportam de forma diferente.

utf8_bin

A "consulta binária" é bem mais rígida já que, em vez de usar a tabela de caracteres, faz uma consulta binária. Isso significa que maiúsculas são diferentes de minúsculas. As queries abaixo retornam resultados diferentes: 

SELECT * FROM sistema WHERE empresa = "Microsoft";

SELECT * FROM sistema WHERE empresa = "microsoft";

SELECT * FROM sistema WHERE empresa = "MICROSOFT";

Você pode usar collates e conjuntos de caracteres diferentes para tabelas diferentes de um banco de dados. Basta criar a tabela conforme o exemplo: 

CREATE TABLE documentos (

    id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,

    data TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    titulo VARCHAR(200) NOT NULL,

    corpo LONGTEXT,

    status ENUM('ativo', 'inativo', 'revisando', 'apagado') DEFAULT 'ativo'

) CHARACTER SET utf8 COLLATE utf8_bin;

Assim, mesmo estando em um banco de dados, por exemplo, utf8_general_ci, as consultas nesta tabela serão "case-sensitive", ou seja, utf8_bin.

Conclusão

Vimos então que, para resolver problemas de acentuação entre o PHP e o MySQL, bastam algumas linhas de código adicionais e um cuidado especial ao criar bancos de dados e tabelas. 

Se gostou do conteúdo, tem alguma dica, sugestão de melhoria ou achou algum "bug", não deixe de me contactar.

E, até a próxima!