PHP MySQL e MariaDB
Até aqui, a lojinha da Ana guardou tudo num SQLite: um arquivo loja.db ao lado dos programas. Para um site pequeno, isso basta. Mas a maioria das hospedagens e dos sistemas PHP do mundo (WordPress, Moodle, lojas virtuais) usa um servidor de banco de dados, quase sempre o MySQL ou o seu "irmão" MariaDB. A boa notícia: com o PDO, o seu código PHP quase não muda. Muda a linha da conexão e alguns detalhes do SQL. Esta aula mostra exatamente quais.
- A diferença entre um banco "arquivo" (SQLite) e um banco "servidor" (MySQL, MariaDB).
- O que é o MariaDB e por que ele aparece no lugar do MySQL em muitos Linux.
- Como instalar o servidor em cada sistema e criar um banco e um usuário para a loja.
- Como montar o DSN
mysql:host=…;dbname=…;charset=utf8mb4e conectar com o PDO. - As diferenças de SQL entre SQLite e MySQL que mais dão problema (como o
AUTO_INCREMENT). - O que é o
mysqlie por que este guia usa o PDO. - O que significam as mensagens
SQLSTATE[HY000] [2002] …ecould not find driver.
O editor do guia roda o PHP de verdade, mas dentro do seu navegador: ele até tem o driver do MySQL, mas não tem acesso à rede, então não consegue falar com um servidor MySQL. Por isso, as conexões com MySQL desta página aparecem em sessões de terminal da máquina da Ana (com saídas reais), e as missões treinam o que roda em qualquer lugar: montar o DSN e adaptar SQL do MySQL para o SQLite. A máquina da Ana não tem um servidor MySQL instalado; foi de propósito, para você ver as mensagens de erro reais de "servidor não encontrado", que são as que todo mundo encontra no primeiro dia.
Antes desta aula, você precisa conhecer o PDO, o CRUD e os prepared statements. Os programas foram executados com o PHP 8.5 na máquina fictícia da Ana, na pasta ~/loja.
A ideia: o caderno e a secretaria
Pense em duas formas de guardar as fichas dos alunos de uma escola:
- Um caderno na mesa da professora (o SQLite). Quem quer uma ficha abre o caderno. É simples, não precisa de ninguém para cuidar, mas só uma pessoa escreve de cada vez, e o caderno está naquela mesa.
- A secretaria da escola (o MySQL). As fichas ficam num arquivo trancado, e existe um funcionário no balcão. Você não mexe nas fichas: vai ao balcão, diz quem é (usuário e senha) e faz o pedido. O funcionário atende muitas pessoas ao mesmo tempo, controla quem pode ver o quê e pode ficar em outro prédio.
Esse "funcionário" é um programa servidor que fica rodando o tempo todo (no Linux, o processo se chama mariadbd ou mysqld) e atende pedidos pela rede, normalmente na porta 3306. O seu PHP é o cliente: ele se conecta, se identifica e envia SQL.
| SQLite | MySQL / MariaDB | |
|---|---|---|
| Onde os dados ficam | Num arquivo (loja.db) | Com um programa servidor, que guarda em arquivos próprios |
| Precisa instalar e ligar algo? | Não: vem dentro do PHP (pdo_sqlite) | Sim: o servidor, e ele precisa estar rodando |
| Usuário e senha | Não tem (vale a permissão do arquivo) | Tem: cada programa entra com um usuário |
| Vários acessos ao mesmo tempo | Muitos leem; um escreve por vez | Muitos leem e escrevem ao mesmo tempo |
| Pode ficar em outra máquina? | Não (o arquivo precisa estar acessível) | Sim, pela rede |
| Bom para | Aprender, testes, sites pequenos, programas locais | Sites com muitos visitantes, hospedagens, sistemas grandes |
MySQL ou MariaDB?
O MySQL nasceu em 1995 e virou o banco mais usado com PHP. Em 2009, quando ele passou a pertencer à Oracle, um dos criadores (Michael "Monty" Widenius) fez uma cópia livre chamada MariaDB (MySQL e MariaDB têm os nomes das filhas dele, My e Maria). Hoje:
- O Debian e várias outras distribuições trazem o MariaDB como padrão. O comando
mysqlcontinua existindo nelas, apontando para o MariaDB. - Para tudo o que este guia ensina (criar tabelas,
SELECT,INSERT, conectar pelo PHP), os dois são iguais. O PHP usa o mesmo driver para os dois:pdo_mysql, com o prefixomysql:. - As diferenças aparecem em recursos avançados e em versões novas. Se a sua hospedagem diz "MySQL 8" ou "MariaDB 11", o código desta aula funciona nos dois.
No resto da página, "MySQL" vale para os dois.
1. Instalar o servidor e a extensão do PHP
São duas peças diferentes, e esquecer uma delas é o erro mais comum:
- O servidor (MariaDB ou MySQL): o "funcionário da secretaria".
- A extensão
pdo_mysqldo PHP: o "idioma" que o PHP usa para falar com ele. Sem ela, o PHP nem tenta conectar (você vai ver a mensagemcould not find drivermais adiante).
A tabela abaixo mostra os comandos de cada sistema. Ela é ilustrativa: os nomes dos pacotes mudam de versão para versão, então confira a documentação do seu sistema. A instalação do PHP em si está em Instalar no Linux e Instalar no Windows e macOS.
| Sistema | Servidor | Extensão do PHP |
|---|---|---|
| Debian, Ubuntu, Mint | sudo apt install mariadb-server | sudo apt install php-mysql (traz pdo_mysql e mysqli) |
| Fedora | sudo dnf install mariadb-server e depois sudo systemctl enable --now mariadb | sudo dnf install php-mysqlnd |
| Arch Linux | sudo pacman -S mariadb, depois sudo mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql e sudo systemctl enable --now mariadb | Já vem no PHP; tire o ; da linha extension=pdo_mysql no php.ini |
| Windows | Pacotes prontos como XAMPP ou Laragon (trazem PHP + MariaDB), ou o instalador oficial do MySQL | Já vem; tire o ; de extension=pdo_mysql no php.ini |
| macOS (Homebrew) | brew install mariadb (ou mysql) e brew services start mariadb | O PHP do Homebrew já traz pdo_mysql |
| Qualquer um, com Docker | docker run -d --name banco -e MARIADB_ROOT_PASSWORD=troque -p 3306:3306 mariadb:11 | Depende de onde o PHP está instalado |
No Debian e no Ubuntu, o servidor já começa a rodar depois da instalação. Vale ainda rodar sudo mariadb-secure-installation, um assistente que remove usuários de teste e o banco test que vêm de fábrica.
Conferindo se o PHP tem o driver
Antes de qualquer coisa, pergunte ao PHP se a extensão está lá. O php -m lista os módulos (extensões) e o grep -i mysql filtra só as linhas com "mysql":
$ php -m | grep -i mysql
mysqlnd « a biblioteca que fala o "idioma" do MySQL
pdo_mysql « o driver do PDO: é este que você precisa
$ php -r 'print_r(PDO::getAvailableDrivers());'
Array
(
[0] => mysql
[1] => pgsql
[2] => sqlite
)
PDO::getAvailableDrivers() devolve a lista de bancos que este PHP sabe usar. Na máquina da Ana são três: mysql (MySQL e MariaDB), pgsql (PostgreSQL) e sqlite. Se mysql não aparecer, instale a extensão (tabela acima) antes de continuar.
Experimente: rode e veja quais drivers o PHP do editor do guia tem. Ele tem o mysql, mas roda dentro do navegador, sem acesso à rede: a tentativa de conexão termina com SQLSTATE[HY000] [2002] Host is unreachable ("máquina inalcançável"). É por isso que as missões desta aula usam SQLite.
<?php
print_r(PDO::getAvailableDrivers());
try {
$pdo = new PDO("mysql:host=localhost;dbname=loja;charset=utf8mb4", "ana", "troque-esta-senha");
echo "Conectado!\n";
} catch (PDOException $e) {
echo "Falhou: ", $e->getMessage(), "\n";
}
2. Criar o banco e o usuário da loja
Um servidor MySQL guarda vários bancos (um para a loja, outro para o blog…), cada um com suas tabelas. E ele tem usuários próprios, que não são os usuários do Linux. A regra de ouro: cada sistema tem o seu usuário, com acesso só ao seu banco. Nunca ponha o usuário root do MySQL no código PHP.
Você entra no servidor como administrador com o cliente de linha de comando (no Debian, sudo mariadb; em outros sistemas, mysql -u root -p) e digita:
CREATE DATABASE loja CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'ana'@'localhost' IDENTIFIED BY 'troque-esta-senha'; GRANT ALL PRIVILEGES ON loja.* TO 'ana'@'localhost';
| Linha | O que faz |
|---|---|
CREATE DATABASE loja | Cria um banco vazio chamado loja (no SQLite, isso era criar o arquivo loja.db). |
CHARACTER SET utf8mb4 | O texto será guardado em UTF-8 completo: acentos, ç, emoji. Veja o aviso logo abaixo. |
COLLATE utf8mb4_unicode_ci | A regra de comparar e ordenar texto. O ci (case insensitive) faz 'ana' e 'Ana' serem iguais numa busca. |
CREATE USER 'ana'@'localhost' | Cria a usuária ana, que só pode entrar a partir da própria máquina (localhost). |
IDENTIFIED BY '…' | A senha dela. Use uma senha forte de verdade. |
GRANT ALL PRIVILEGES ON loja.* | Dá à ana todos os poderes sobre todas as tabelas (.*) do banco loja, e só dele. |
utf8mb4, nunca utf8
No MySQL, o nome utf8 é uma armadilha histórica: ele guarda só caracteres de até 3 bytes, então emoji (e alguns ideogramas) não cabem. O UTF-8 completo se chama utf8mb4 ("até 4 bytes"). Use utf8mb4 no banco, nas tabelas e na conexão do PHP.
A tabela produtos, versão MySQL
A mesma tabela que você criou no SQLite em CRUD com PDO fica assim no MySQL:
CREATE TABLE produtos (
id INT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
preco DECIMAL(10,2) NOT NULL,
estoque INT NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- INT AUTO_INCREMENT PRIMARY KEY
- Número inteiro que o servidor preenche sozinho (1, 2, 3…). No SQLite, o equivalente é
INTEGER PRIMARY KEY. - VARCHAR(100)
- Texto de até 100 caracteres. O MySQL quer saber o tamanho máximo; o SQLite usa só
TEXT. - DECIMAL(10,2)
- Número exato com 10 dígitos, 2 deles depois da vírgula: o tipo certo para dinheiro (nada de
0.1 + 0.2dando 0.30000000000000004). - ENGINE=InnoDB
- O "motor" que guarda a tabela. O InnoDB é o padrão e suporta transações.
- DEFAULT CHARSET=utf8mb4
- A tabela guarda texto em UTF-8 completo.
3. O DSN do MySQL
Lembra que o PDO recebe um DSN (Data Source Name, "nome da fonte de dados"), um texto que diz qual banco usar e onde ele está? No SQLite, era só sqlite: e o caminho do arquivo. No MySQL, o DSN tem mais peças, separadas por ;:
mysql:host=localhost;port=3306;dbname=loja;charset=utf8mb4
- mysql:
- O driver: "fale com um MySQL (ou MariaDB)". Precisa da extensão
pdo_mysql. - host=localhost
- Onde o servidor está.
localhost= esta mesma máquina. Pode ser um IP (127.0.0.1) ou um nome (db.minhahospedagem.com.br). - port=3306
- A porta. Opcional: se você não escrever, vale 3306, a padrão.
- dbname=loja
- Qual banco, dos vários do servidor.
- charset=utf8mb4
- Em que codificação o texto vai e volta. Sem isso, acentos podem virar
é.
Repare no que não vai no DSN: o usuário e a senha. Eles são o segundo e o terceiro argumento do new PDO.
O DSN é lido ao pé da letra. mysql:host=localhost; dbname=loja (com espaço depois do ;) ou host = localhost podem fazer o PDO procurar um banco chamado " loja". Escreva tudo grudado.
Missão: monte o DSN numa variável $dsn usando as variáveis $host, $porta e $banco (sem digitar localhost, 3306 nem loja de novo) e mostre-o. A saída deve ser exatamente:
mysql:host=localhost;port=3306;dbname=loja;charset=utf8mb4
<?php $host = "localhost"; $porta = 3306; $banco = "loja"; // monte o $dsn aqui echo $dsn, "\n";
<?php $host = "localhost"; $porta = 3306; $banco = "loja"; $dsn = "mysql:host=$host;port=$porta;dbname=$banco;charset=utf8mb4"; echo $dsn, "\n";
4. Conectar com o PDO
Com o servidor rodando, o banco e o usuário criados, a conexão é a mesma de Conectar com PDO, só que com o DSN novo e com usuário e senha:
<?php
$dsn = "mysql:host=localhost;port=3306;dbname=loja;charset=utf8mb4";
$usuario = "ana";
$senha = "troque-esta-senha";
try {
$pdo = new PDO($dsn, $usuario, $senha, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
echo "Conectado ao MySQL!\n";
} catch (PDOException $e) {
error_log("Falha no banco: " . $e->getMessage());
echo "Desculpe, a loja está fora do ar. Tente mais tarde.\n";
exit(1);
}
| Trecho | O que faz |
|---|---|
new PDO($dsn, $usuario, $senha, [...]) | Tenta conectar. Os dois argumentos do meio são o usuário e a senha criados no passo 2; o último é a lista de opções. |
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | Erros de SQL viram exceções. Já é o padrão desde o PHP 8.0; escrever deixa a intenção clara. |
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC | Cada linha volta como array associativo ($linha["nome"]), sem precisar repetir isso em todo fetch. |
catch (PDOException $e) | Se a conexão falhar, o PDO lança uma PDOException (veja Exceções). |
error_log(...) | Guarda a mensagem técnica no log (registro de erros), para você ler depois. No terminal, ela aparece na saída de erros. |
echo "Desculpe..."; exit(1); | Mostra ao visitante uma mensagem amigável e encerra com código 1 ("deu errado"). |
Na máquina da Ana não há servidor MySQL. Veja o que acontece:
$ php conexao.php Falha no banco: SQLSTATE[HY000] [2002] No such file or directory « veio do error_log Desculpe, a loja está fora do ar. Tente mais tarde. $ echo $? 1 $ php conexao.php 2>/dev/null « escondendo a saída de erros Desculpe, a loja está fora do ar. Tente mais tarde.
Na primeira execução aparecem as duas linhas, porque no terminal a saída de erros (onde o error_log escreve) e a saída normal vão para a mesma tela. Na segunda, o 2>/dev/null joga fora a saída de erros, e sobra só o que um visitante do site veria: a mensagem amigável. Num site de verdade, o error_log vai para o arquivo de log do servidor web, e o visitante nunca vê detalhes técnicos.
E se eu não tratar o erro?
Sem o try/catch, a exceção não é capturada e o PHP para com um Fatal error:
<?php
$pdo = new PDO("mysql:host=localhost;dbname=loja;charset=utf8mb4", "ana", "segredo");
echo "Conectou!\n";
$ php conectar.php
Fatal error: Uncaught PDOException: SQLSTATE[HY000] [2002] No such file or directory in /home/ana/loja/conectar.php:2
Stack trace:
#0 /home/ana/loja/conectar.php(2): PDO->__construct('mysql:host=loca...', 'ana', Object(SensitiveParameterValue))
#1 {main}
thrown in /home/ana/loja/conectar.php on line 2
Leia a linha #0 do stack trace (o "rastro" das chamadas): o PHP mostra os argumentos do new PDO, mas a senha aparece como Object(SensitiveParameterValue) ("valor sensível"), e não como 'segredo'. Desde o PHP 8.2+, parâmetros marcados como sensíveis são escondidos assim. Mesmo assim, o caminho dos arquivos, o nome do usuário e o começo do DSN aparecem. Por isso: trate o erro de conexão e, no servidor de produção, deixe display_errors desligado (veja Tipos de Erro).
5. As mensagens de erro de conexão
A mensagem SQLSTATE[HY000] [2002] … tem três partes:
- SQLSTATE[HY000]
- Um código padrão de erro de SQL.
HY000é o "erro geral": quer dizer só "veja o resto". - [2002]
- O código do próprio MySQL.
2002= "não consegui chegar até o servidor". - No such file or directory
- O motivo, explicado pelo sistema.
O motivo muda conforme o host. Este programa tenta três endereços e mostra a mensagem de cada um:
<?php
$enderecos = [
"mysql:host=localhost;dbname=loja;charset=utf8mb4",
"mysql:host=127.0.0.1;port=3306;dbname=loja;charset=utf8mb4",
"mysql:host=banco.exemplo.invalid;dbname=loja;charset=utf8mb4",
];
foreach ($enderecos as $dsn) {
echo $dsn, "\n";
try {
new PDO($dsn, "ana", "troque-esta-senha");
} catch (PDOException $e) {
echo " → ", $e->getMessage(), "\n";
}
}
$ php tres-servidores.php mysql:host=localhost;dbname=loja;charset=utf8mb4 → SQLSTATE[HY000] [2002] No such file or directory mysql:host=127.0.0.1;port=3306;dbname=loja;charset=utf8mb4 → SQLSTATE[HY000] [2002] Connection refused mysql:host=banco.exemplo.invalid;dbname=loja;charset=utf8mb4 → SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for banco.exemplo.invalid failed: Name does not resolve
Três mensagens diferentes para o mesmo problema ("não achei o servidor"). Cada uma aponta uma causa:
| Mensagem | O que aconteceu | O que conferir |
|---|---|---|
[2002] No such file or directory | Com host=localhost, o PDO não usa a rede: procura um socket, um arquivo especial (como /run/mysqld/mysqld.sock) que o servidor cria quando está ligado. O arquivo não existe. | O servidor está rodando? (systemctl status mariadb). O PHP procura o socket no lugar certo? (opção unix_socket= no DSN) |
[2002] Connection refused | Com um IP (127.0.0.1), o PDO usa a rede, chegou à máquina, mas ninguém atende na porta 3306. | O servidor está ligado? Está na porta que você escreveu? Aceita conexões pela rede? |
[2002] php_network_getaddresses: getaddrinfo … failed | O nome do servidor não foi encontrado (não existe, ou foi digitado errado). | A grafia do host. O texto depois de failed: vem do sistema e pode variar. |
could not find driver | O PHP não tem o driver pedido no DSN. | A extensão pdo_mysql (veja o cenário abaixo). |
Quando o servidor existe e atende, os erros passam a ser sobre quem você é e o que pediu. Estes não puderam ser reproduzidos na máquina da Ana (ela não tem servidor), mas você vai reconhecê-los pelo código:
| Código | Significado | Causa comum |
|---|---|---|
[1045] Access denied | Acesso negado | Usuário ou senha errados, ou o usuário foi criado para outro host ('ana'@'localhost' não entra de outra máquina). |
[1049] Unknown database | Banco desconhecido | O dbname está errado ou o CREATE DATABASE não foi feito. |
[1044] Access denied … to database | Sem permissão naquele banco | Faltou o GRANT para o usuário. |
localhost × 127.0.0.1
Para você, os dois são "esta máquina". Para o PDO com MySQL, são caminhos diferentes: localhost usa o socket (um arquivo), 127.0.0.1 usa a rede (porta 3306). Se um não funciona, testar o outro ajuda a descobrir o problema. Isso também muda o usuário: para o MySQL, 'ana'@'localhost' e 'ana'@'127.0.0.1' podem ser contas diferentes.
6. SQLite × MySQL: o que muda no SQL
Com o PDO, o PHP é o mesmo: prepare, execute, fetch, lastInsertId, rowCount, transações. O que muda é o SQL que você escreve, porque cada banco tem o seu "sotaque".
| Assunto | SQLite | MySQL / MariaDB |
|---|---|---|
| Conectar | new PDO("sqlite:loja.db") | new PDO("mysql:host=…;dbname=…;charset=utf8mb4", $usuario, $senha) |
| Chave que se numera sozinha | id INTEGER PRIMARY KEY (ou … AUTOINCREMENT) | id INT AUTO_INCREMENT PRIMARY KEY |
| Texto | TEXT | VARCHAR(100) (com tamanho máximo) ou TEXT |
| Dinheiro | REAL (ou centavos em INTEGER) | DECIMAL(10,2) |
| Tipos das colunas | Flexíveis: aceita texto numa coluna REAL (a não ser em tabelas STRICT) | Rígidos: no modo estrito (o padrão), recusa o valor errado |
| Data e hora de agora | datetime('now') | NOW() |
| Listar as tabelas | SELECT name FROM sqlite_master WHERE type = 'table' | SHOW TABLES |
| Detalhes da tabela | ENGINE e CHARSET não existem | ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
| Igual em ambos | SELECT … FROM … WHERE … ORDER BY … LIMIT, INSERT, UPDATE, DELETE, COUNT, SUM, JOIN | |
A armadilha: SQL do MySQL dentro do SQLite
Muito tutorial na internet traz o CREATE TABLE do MySQL. O que acontece se você colar isso num SQLite? Com o final ENGINE=InnoDB, o SQLite reclama na hora:
<?php
$pdo = new PDO("sqlite::memory:");
$pdo->exec("CREATE TABLE produtos (
id INT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$ php engine.php
Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 1 near "=": syntax error in /home/ana/loja/engine.php:3
Stack trace:
#0 /home/ana/loja/engine.php(3): PDO->exec('CREATE TABLE pr...')
#1 {main}
thrown in /home/ana/loja/engine.php on line 3
near "=": syntax error = "erro de sintaxe perto do =": o SQLite não conhece ENGINE=…. Até aí, fácil de ver. O perigo é o próximo caso: sem o ENGINE, o SQLite aceita o resto sem reclamar…
<?php
$pdo = new PDO("sqlite::memory:");
$pdo->exec("CREATE TABLE produtos (
id INT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
preco DECIMAL(10,2) NOT NULL
)");
$pdo->exec("INSERT INTO produtos (nome, preco) VALUES ('Caderno', 15.90)");
$pdo->exec("INSERT INTO produtos (nome, preco) VALUES ('Caneta', 2.50)");
foreach ($pdo->query("SELECT id, nome FROM produtos") as $linha) {
echo "id: ", var_export($linha["id"], true), " | nome: ", $linha["nome"], "\n";
}
$ php mysql-no-sqlite.php id: NULL | nome: Caderno « o id não foi numerado! id: NULL | nome: Caneta
Nenhum erro, e os produtos ficaram sem id. O motivo: o SQLite leu INT AUTO_INCREMENT como um nome de tipo qualquer (ele aceita quase tudo como tipo) e não entendeu AUTO_INCREMENT como "numere sozinho". No SQLite, só a forma exata INTEGER PRIMARY KEY (com INTEGER escrito por extenso) cria a numeração automática. Um INT PRIMARY KEY é só uma coluna comum, que aceita NULL.
Os tipos flexíveis do SQLite
Outra diferença que surpreende quem vem do MySQL:
<?php
$pdo = new PDO("sqlite::memory:");
$pdo->exec("CREATE TABLE produtos (nome TEXT, preco REAL)");
$pdo->exec("INSERT INTO produtos VALUES ('Caderno', 'quinze reais')");
$linha = $pdo->query("SELECT preco, typeof(preco) AS tipo FROM produtos")->fetch(PDO::FETCH_ASSOC);
print_r($linha);
$ php tipos.php
Array
(
[preco] => quinze reais « texto guardado numa coluna de números
[tipo] => text
)
O SQLite guardou o texto quinze reais na coluna preco sem reclamar (a função typeof do SQLite confirma: text). Um MySQL em modo estrito recusaria esse INSERT. Moral: valide os dados no PHP antes de gravar (veja Validar Dados), qualquer que seja o banco. No SQLite, você pode ainda criar a tabela com a palavra STRICT no fim (CREATE TABLE … (…) STRICT), e aí ele passa a recusar, com a mensagem cannot store TEXT value in REAL column produtos.preco.
7. Trocar de banco mudando só a conexão
A grande vantagem do PDO: se o SQL da aplicação é o "comum" (o que é igual nos dois), só a criação do PDO muda. Um jeito organizado é guardar os dados da conexão num arquivo de configuração e montar o DSN a partir dele:
<?php
// Em casa: SQLite. Na hospedagem: troque por MySQL.
return [
"driver" => "mysql",
"host" => "localhost",
"banco" => "loja",
"usuario" => "ana",
"senha" => "troque-esta-senha",
];
O resto do sistema faz $config = require "config.php"; (veja include e require) e nunca precisa saber qual banco está do outro lado. Duas regras de segurança para esse arquivo: ele não vai para o Git (a senha ficaria pública) e não fica numa pasta acessível pelo navegador.
Missão: a função montarDsn só sabe montar DSN de MySQL. Faça com que, quando $c["driver"] for "sqlite", ela devolva "sqlite:" seguido de $c["arquivo"]. A saída deve ser:
sqlite:loja.db mysql:host=localhost;dbname=loja;charset=utf8mb4 Conectado com o driver sqlite
<?php
function montarDsn(array $c): string
{
// se o driver for "sqlite", devolva outro DSN aqui
return "mysql:host={$c['host']};dbname={$c['banco']};charset=utf8mb4";
}
echo montarDsn(["driver" => "sqlite", "arquivo" => "loja.db"]), "\n";
echo montarDsn(["driver" => "mysql", "host" => "localhost", "banco" => "loja"]), "\n";
$pdo = new PDO(montarDsn(["driver" => "sqlite", "arquivo" => ":memory:"]));
echo "Conectado com o driver ", $pdo->getAttribute(PDO::ATTR_DRIVER_NAME), "\n";
<?php
function montarDsn(array $c): string
{
if ($c["driver"] === "sqlite") {
return "sqlite:" . $c["arquivo"];
}
return "mysql:host={$c['host']};dbname={$c['banco']};charset=utf8mb4";
}
echo montarDsn(["driver" => "sqlite", "arquivo" => "loja.db"]), "\n";
echo montarDsn(["driver" => "mysql", "host" => "localhost", "banco" => "loja"]), "\n";
$pdo = new PDO(montarDsn(["driver" => "sqlite", "arquivo" => ":memory:"]));
echo "Conectado com o driver ", $pdo->getAttribute(PDO::ATTR_DRIVER_NAME), "\n";
$pdo->getAttribute(PDO::ATTR_DRIVER_NAME) pergunta ao objeto PDO com qual driver ele está conectado. Útil quando um trecho de SQL precisa ser diferente para cada banco.
8. mysqli × PDO
Você vai encontrar em tutoriais e em sistemas antigos uma segunda forma de falar com MySQL: a extensão mysqli (MySQL improved, "MySQL melhorado"). Ela é boa e segura quando usada com prepared statements, mas só fala com MySQL/MariaDB. Este é o mesmo SELECT nas duas, só para você reconhecer (exemplo para ler):
<?php
$db = new mysqli("localhost", "ana", "troque-esta-senha", "loja");
$db->set_charset("utf8mb4");
$stmt = $db->prepare("SELECT nome, preco FROM produtos WHERE preco < ?");
$stmt->bind_param("d", $limite); // "d" = double (número com casas decimais)
$limite = 20;
$stmt->execute();
foreach ($stmt->get_result() as $linha) {
echo $linha["nome"], "\n";
}
<?php
$pdo = new PDO("mysql:host=localhost;dbname=loja;charset=utf8mb4", "ana", "troque-esta-senha");
$stmt = $pdo->prepare("SELECT nome, preco FROM produtos WHERE preco < ?");
$stmt->execute([20]);
foreach ($stmt as $linha) {
echo $linha["nome"], "\n";
}
mysqli | PDO | |
|---|---|---|
| Bancos | Só MySQL e MariaDB | Vários: SQLite, MySQL, PostgreSQL, SQL Server… |
| Estilos | Objetos (new mysqli) e funções (mysqli_connect) | Só objetos |
| Marcadores no SQL | Só ?, com bind_param("sdi", …) (uma letra de tipo por valor) | ? e :nome; os valores vão direto no execute([...]) |
| Erros | Exceções por padrão desde o PHP 8.1 | Exceções por padrão desde o PHP 8.0 |
| Recursos só do MySQL | Tem alguns a mais (consultas múltiplas, assíncronas) | Fica no que é comum a todos |
| Neste guia | Só para reconhecer | É o que usamos |
Qual usar? Para aprender e para sistemas novos, PDO: uma forma só de trabalhar, para qualquer banco, e você treina tudo com SQLite antes de ter um servidor. Use mysqli se for manter um sistema que já o usa. Se encontrar funções mysql_query, mysql_connect (sem o "i"), é código muito antigo: essa extensão foi removida no PHP 7.0 e não existe mais.
Quando dá errado
SQLSTATE[HY000] [2002] No such file or directory
$ php conexao.php Falha no banco: SQLSTATE[HY000] [2002] No such file or directory Desculpe, a loja está fora do ar. Tente mais tarde.
O que significa: com host=localhost, o PHP tentou falar com o servidor pelo socket (um arquivo especial) e o arquivo não existe. Quase sempre: o servidor não está rodando ou nem foi instalado.
Como consertar: confira com systemctl status mariadb (ou mysql) e ligue com sudo systemctl start mariadb. Se o servidor está rodando e o erro continua, o socket está em outro lugar: tente host=127.0.0.1 ou informe o caminho com unix_socket=/caminho/do/mysqld.sock no DSN.
SQLSTATE[HY000] [2002] Connection refused
$ php tres-servidores.php mysql:host=localhost;dbname=loja;charset=utf8mb4 → SQLSTATE[HY000] [2002] No such file or directory mysql:host=127.0.0.1;port=3306;dbname=loja;charset=utf8mb4 → SQLSTATE[HY000] [2002] Connection refused mysql:host=banco.exemplo.invalid;dbname=loja;charset=utf8mb4 → SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for banco.exemplo.invalid failed: Name does not resolve
O que significa: "conexão recusada". A máquina existe, mas nada atende na porta pedida. O servidor está desligado, está em outra porta, ou está configurado para não aceitar conexões pela rede.
Como consertar: ligue o servidor; confira a porta (port= no DSN, padrão 3306). Se o banco está em outra máquina, o servidor precisa aceitar conexões externas (no MariaDB, a opção bind-address) e o usuário precisa ter sido criado para aquele host.
could not find driver
A máquina da Ana tem o pdo_mysql, então, para mostrar a mensagem, pedimos um driver que ela não tem (sqlsrv, do SQL Server). Com o MySQL sem extensão, a mensagem é a mesma:
$ php -r 'new PDO("sqlsrv:Server=localhost;Database=loja");'
Fatal error: Uncaught PDOException: could not find driver in Command line code:1
Stack trace:
#0 Command line code(1): PDO->__construct('sqlsrv:Server=l...')
#1 {main}
thrown in Command line code on line 1
O que significa: "não encontrei o driver". O PHP nem tentou conectar: não sabe falar o "idioma" pedido no começo do DSN.
Como consertar: confira o prefixo do DSN (é mysql:, não mariadb: nem mysqli:). Rode php -m | grep -i pdo. Se pdo_mysql não aparecer, instale (sudo apt install php-mysql no Debian/Ubuntu) ou ative extension=pdo_mysql no php.ini, e reinicie o servidor web. Atenção: o PHP do terminal e o do servidor web podem usar php.ini diferentes.
Class "mysqli" not found
$ php -r 'new mysqli("localhost", "ana", "segredo", "loja");'
Fatal error: Uncaught Error: Class "mysqli" not found in Command line code:1
Stack trace:
#0 {main}
thrown in Command line code on line 1
O que significa: a extensão mysqli não está instalada neste PHP. É o que acontece na máquina da Ana, que tem só o PDO. Em código antigo, a mesma situação aparece como Call to undefined function mysqli_connect().
Como consertar: se o código é seu, prefira reescrever com PDO. Se precisa rodar um sistema que usa mysqli, instale a extensão (no Debian/Ubuntu ela vem no mesmo pacote php-mysql).
NULL (SQL do MySQL no SQLite)
$ php mysql-no-sqlite.php id: NULL | nome: Caderno id: NULL | nome: Caneta
O que significa: a tabela foi criada com id INT AUTO_INCREMENT PRIMARY KEY, que no SQLite não numera nada. Sem erro nenhum, todos os produtos ficam sem id, e um UPDATE … WHERE id = 1 não encontra ninguém.
Como consertar: no SQLite, escreva id INTEGER PRIMARY KEY (ou INTEGER PRIMARY KEY AUTOINCREMENT). Se precisa usar os dois bancos, mantenha um arquivo .sql para cada um.
Missão: este CREATE TABLE foi copiado de um tutorial de MySQL, e os produtos estão saindo com id: NULL. Adapte a tabela para o SQLite (só o CREATE TABLE) até a saída ficar:
id: 1 | nome: Caderno id: 2 | nome: Caneta
<?php
$pdo = new PDO("sqlite::memory:");
$pdo->exec("CREATE TABLE produtos (
id INT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
preco DECIMAL(10,2) NOT NULL
)");
$pdo->exec("INSERT INTO produtos (nome, preco) VALUES ('Caderno', 15.90)");
$pdo->exec("INSERT INTO produtos (nome, preco) VALUES ('Caneta', 2.50)");
foreach ($pdo->query("SELECT id, nome FROM produtos") as $linha) {
echo "id: ", var_export($linha["id"], true), " | nome: ", $linha["nome"], "\n";
}
<?php
$pdo = new PDO("sqlite::memory:");
$pdo->exec("CREATE TABLE produtos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL,
preco REAL NOT NULL
)");
$pdo->exec("INSERT INTO produtos (nome, preco) VALUES ('Caderno', 15.90)");
$pdo->exec("INSERT INTO produtos (nome, preco) VALUES ('Caneta', 2.50)");
foreach ($pdo->query("SELECT id, nome FROM produtos") as $linha) {
echo "id: ", var_export($linha["id"], true), " | nome: ", $linha["nome"], "\n";
}
Referência
| O quê | Como |
|---|---|
| DSN do MySQL/MariaDB | mysql:host=localhost;port=3306;dbname=loja;charset=utf8mb4 |
| DSN com socket | mysql:unix_socket=/run/mysqld/mysqld.sock;dbname=loja;charset=utf8mb4 |
| Conectar | new PDO($dsn, $usuario, $senha, $opcoes) |
| Opções recomendadas | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC |
| Drivers deste PHP | PDO::getAvailableDrivers() ou php -m | grep -i pdo |
| Driver da conexão atual | $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) |
| Criar banco | CREATE DATABASE loja CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; |
| Criar usuário | CREATE USER 'ana'@'localhost' IDENTIFIED BY 'senha'; |
| Dar acesso a um banco | GRANT ALL PRIVILEGES ON loja.* TO 'ana'@'localhost'; |
| Auto-incremento | MySQL: INT AUTO_INCREMENT PRIMARY KEY · SQLite: INTEGER PRIMARY KEY |
| Erro "servidor inalcançável" | SQLSTATE[HY000] [2002] … |
| Erro "usuário/senha" | [1045] Access denied |
| Erro "banco não existe" | [1049] Unknown database |
| Extensão faltando | could not find driver (PDO) · Class "mysqli" not found |
Exercícios
1. Completar o DSN
Complete o DSN para conectar ao banco blog, no servidor localhost, com UTF-8 completo:
2. O tipo do dinheiro
No MySQL, complete o tipo da coluna preco para guardar valores exatos até 99999999,99:
3. A chave do SQLite
Complete a coluna id para que o SQLite a numere sozinho:
4. O programa mostra SQLSTATE[HY000] [2002] Connection refused. O que é mais provável?
5. Por que usar utf8mb4 em vez de utf8 no MySQL?
6. A mensagem é could not find driver. Qual o primeiro passo?
7. Qual destas afirmações sobre mysqli e PDO está correta?
Resumo
- MySQL e MariaDB são servidores: um programa que fica rodando, com usuários e senhas, e atende muitos acessos ao mesmo tempo. O SQLite é um arquivo.
- Você precisa de duas peças: o servidor e a extensão
pdo_mysqldo PHP. Confira comphp -m. - Crie um banco e um usuário só para o sistema; nunca use o
rootdo MySQL no código. - DSN:
mysql:host=localhost;dbname=loja;charset=utf8mb4; usuário e senha vão separados nonew PDO. - Use
utf8mb4no banco, nas tabelas e na conexão. [2002]= não chegou ao servidor (No such file or directory: socket; Connection refused: porta; getaddrinfo: nome).could not find driver= falta a extensão.- Trate o erro de conexão com
try/catch: log para você, mensagem amigável para o visitante. - O SQL muda nos detalhes:
AUTO_INCREMENT×INTEGER PRIMARY KEY,VARCHAR/DECIMAL×TEXT/REAL, tipos rígidos × flexíveis. - Prefira PDO a
mysqli: uma forma de trabalhar para qualquer banco.
Com isso, você termina o Nível 2. Para ver tudo junto num sistema de verdade, faça o projeto Tarefas com SQLite; no Nível 3, você vai organizar o código da conexão em classes.