PHP:: Pdo - Crud Completo: Introdução
PHP:: Pdo - Crud Completo: Introdução
Fonte: https://alexandrebbarbosa.wordpress.com/2016/09/04/php-pdo-crud-completo/
Introdução
Em outros artigos sobre persistência, já abrangi sobre CRUD utilizando inclusive alguns padrões de projetos
mas não eram aplicações completas. Entretanto, imagino que para quem está iniciando a programar em PHP e ainda
não está acostumado a padrões de projetos, poderá estar com inúmeras dúvidas e precisa de algo simples, porém
funcional. Por isso, decidi, criar este artigo que abrange de forma simples o CRUD em PHP fazendo uso do banco de
dados MySQL. CRUD é um acrônimo para as quatro operações do Banco: CREATE (Criar), READ (Ler), Update
(Atualizar) e DELETE (Excluir). Quando você cria uma aplicação que conte com todas estas operações, está
desenvolvendo um CRUD.
Vamos então passa a passo, etapa por etapa entendendo como fazer uma aplicação CRUD (agenda de
contatos) desde a conexão com banco de dados. Queri deixar claro que é importe se acostumar a programar conforme
o paradigma Orientado a Objetos. Você até pode programar conforme o paradigma procedural, mas estará limitado em
alguns aspectos e até irá se deparar com outros problemas que somente terão solução pela Orientação a Objetos. Não
entraremos em detalhes sobre isso aqui. Além disso, é importante que você tenha noções da linguagem SQL a fim de
ter um melhor aproveitamento.
try {
$conexao = new PDO("mysql:host=localhost;dbname=crudsimples;charset=utf8mb4",
"root", "123456", [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
} catch (PDOException $erro) {
echo "Erro na conexão: " . $erro->getMessage();
}
Note como que instanciamos o objeto PDO, sua sintaxe é:
<objeto PDO> = new PDO(String <Nome da fonte de dados>, String <usuário do
banco>, String <senha do banco>);
Note que o objeto que será associado a variável $conexão será do tipo PDO, um objeto que representa a
conexão com o banco e que será o nosso manipulador.
Como parâmetros, o construtor da classe exige:
• uma DSN (Nome da fonte de dados), onde informamos o driver do banco, o nome do host e o nome
do banco de dados e note que são separados por ponte e vírgula. Mas a DSN suporta diferentes
métodos de argumentação. Caso tenha interesse dê uma olhada na documentação
(http://php.net/manual/pt_BR/pdo.construct.php)
• um nome de usuário para conexão com o banco de dados
• a senha para o usuário informado.
Opcionalmente é possível passar um array associativo com opções de conexão, como um terceiro parâmetro,
mas não vou abranger aqui.
Observação: deste momento em diante, não darei tanta ênfase as tags de abertura e fechamento do bloco de
códigos php. Serão mencionadas apenas quando for necessário misturar código php com tags html ou adicionar em
outro local no arquivo.
Perceba que o parâmetro de catch foi informado como um argumento do tipo PDOExecption. Esta classe do
php estende RuntimeException e é específica para representar os erros gerados pela classe PDO. Para termos uma
representação mais específica, a classe PDO nos apresenta um método setAttribute para o objeto PDO, note os blocos
acima reescritos com o método, logo abaixo:
try {
$conexao = new PDO("mysql:host=localhost; dbname=crudsimples", "root", "123456");
$conexao->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $erro) {
echo "Erro na conexão:" . $erro->getMessage();
}
O método setAttribute() nos permite adicionar atributos no objeto de conexão, sua sintaxe:
<valor boleano> = setAttribute(inteiro “Atributo”, misto “Valor”);
Note que o método devolve um valor booleano como resposta e assim você pode descobrir se tudo correu
bem. Ainda, como atributos, há um do tipo inteiro e outro que pode ser de vários tipos. No caso do argumento inteiro,
este método deve receber algum dos atributos válidos para o objeto de conexão. Estes objetos são representados por
constantes, semelhantes a esta que estamos usando aí acima. Se você estiver interessado em saber quais são, veja
a documentação: http://php.net/manual/pt_BR/pdo.setattribute.php. Por hora vamos
utilizar PDO::ATTR_ERRMODE que indica como relatar o erro. Entenda que o segundo argumento deve ser compatível
com o primeiro. No caso de PDO::ATTR_ERRMODE, espera-se receber um dos três modos disponíveis:
PDO::ERRMODE_SILENT: Só defina o código de erro
PDO::ERRMODE_WARNING: gerar um E_WARNING.
PDO::ERRMODE_EXCEPTION: Lance uma exceção.
Atento a isto, descobrimos que a última opção é a melhor por estarmos trabalhando com uma PDOException no
nosso bloco catch. Sendo assim, agora será possível imprimir uma exceção lançada, por chamar o
método getMessage() da PDOException. É exatamente isso que estamos fazendo dentro do bloco catch, como você
pode ter notado.
Pois bem, será que já terminamos a parte de conexão? Talvez, mas poderemos melhorar algo aqui: lembre-se
que estamos no Brasil, onde é comum utilizar acentos e, por isso, a codificação comum é UTF-8. Como é que
informamos isso ao servidor de banco de dados? Note os blocos try e catch reescritos e terminados:
try {
$conexao = new PDO("mysql:host=localhost; dbname=crudsimples", "root",
"123456");
$conexao->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conexao->exec("set names utf8");
} catch (PDOException $erro) {
echo "Erro na conexão:" . $erro->getMessage();
}
O que mudou? Foi adicionado uma linha com o método exec() da classe PDO, que executar uma instrução
SQL e retornar o número de linhas afetadas. Neste caso, não esperamos nenhum retorno, mas queremos executar a
instrução SQL direto no banco. Note que esta não é uma instrução da classe PDO e sim do banco de dados. Caso
queira saber mais a respeito desta instrução no MySQL, consulte http://dev.mysql.com/doc/refman/5.7/en/charset-
connection.html
Muito bem, terminado a segunda etapa de construção da aplicação, a próxima será criar um formulário simples
e inserir dados nele, construindo assim a primeira função do CRUD.
Veja que também utilizamos uma tag input do tipo “hidden” e esta será utilizada para o recurso de Update.
Visto que, já que mencionei o “Update”, vamos já preparar nosso formulário para a função este recurso. Então, dentro
de cada tag input, antes do encerramento “/>”, vamos acrescentar um espaço e adicionar um código php para
preenchimento automático. Este código, naturalmente deverá estar delimitado pelas tags do php “<?php” e “?>”. Então,
leve o cursor até a área informada acima e adicione o código entre as tags do php. Ele deverá ser assim:
<html>
<head>
<meta charset="UTF-8">
<title>Agenda de contatos</title>
</head>
<body>
<form action="?act=save" method="POST" name="form1" >
<h1>Agenda de contatos</h1>
<hr>
<input type="hidden" name="id" <?php
// Preenche o id no campo id com um valor "value"
if (isset($id) && $id != null || $id != "") {
echo "value=\"{$id}\"";
}
?> />
Nome:
<input type="text" name="nome" <?php
// Preenche o nome no campo nome com um valor "value"
if (isset($nome) && $nome != null || $nome != ""){
echo "value=\"{$nome}\"";
}
?> />
E-mail:
<input type="text" name="email" <?php
// Preenche o email no campo email com um valor "value"
if (isset($email) && $email != null || $email != ""){
echo "value=\"{$email}\"";
}
?> />
Celular:
<input type="text" name="celular" <?php
// Preenche o celular no campo celular com um valor "value"
if (isset($celular) && $celular != null || $celular != ""){
echo "value=\"{$celular}\"";
}
?> />
<input type="submit" value="salvar" />
<input type="reset" value="Novo" />
<hr>
</form>
</body>
</html>
Novamente, até aqui se você abrir a página no browser, ainda não vai acontecer porque estas instruções de
decisão apenas estão verificando se uma variável foi definida e se seu valor é diferente de nulo ou diferente de vazio.
Todavia, as variáveis ainda não foram definidas e por isso nada acontece. Mas vamos adicionar estas novas instruções
antes do bloco “try e catch” na primeira parte do nosso código, ou seja, ficará logo depois da primeira tag de abertura
do php “<?php” do início e antes do bloco “try e catch”:
<?php
// Verificar se foi enviando dados via POST
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$id = (isset($_POST["id"]) && $_POST["id"] != null) ? $_POST["id"] : "";
$nome = (isset($_POST["nome"]) && $_POST["nome"] != null) ? $_POST["nome"] : "";
$email = (isset($_POST["email"]) && $_POST["email"] != null) ? $_POST["email"] :
"";
$celular = (isset($_POST["celular"]) && $_POST["celular"] != null) ?
$_POST["celular"] : NULL;
} else if (!isset($id)) {
// Se não se não foi setado nenhum valor para variável $id
$id = (isset($_GET["id"]) && $_GET["id"] != null) ? $_GET["id"] : "";
$nome = NULL;
$email = NULL;
$celular = NULL;
}
Agora, se você executar a página, poderá preencher o formulário e notar uma diferença ao clicar em “Salvar”.
A diferença é quando clicar no botão “Salvar” e fazer o “submit”, pois, os dados permanecerão nos campos como se
nada acontecesse.
Como assim? Esta é a nossa intenção neste momento, porque definimos e preenchemos as variáveis via
POST, aquelas que são verificadas no código dentro das tags input dentro do formulário. Mesmo com refresh da página,
os dados não sumirão, mas continuarão lá. Precisamos agora salvar estes dados no banco de dados e assim alcançar
nosso primeiro objetivo. Esta é a nossa próxima etapa: Create! Você deve ter notado também que estamos trabalhando
com uma variável $id, embora esta variável ainda não receba nenhum valor. Não se preocupe logo voltaremos nossa
atenção a ela.
if ($stmt->execute()) {
if ($stmt->rowCount() > 0) {
echo "Dados cadastrados com sucesso!";
$id = null;
$nome = null;
$email = null;
$celular = null;
} else {
echo "Erro ao tentar efetivar cadastro";
}
} else {
throw new PDOException("Erro: Não foi possível executar a declaração
sql");
}
} catch (PDOException $erro) {
echo "Erro: " . $erro->getMessage();
}
}
Muito bem! se você rodar este código e clicar em salvar, as informações vão ser escritas no banco de dados e
o formulário não retornará com mais nenhum valor.Além disso, uma mensagem “Dados cadastrados com sucesso!”
será apresentada logo acima do formulário:
Agora note na próxima imagem que os dados podem ser recuperados no Banco de dados:
Este método transforma uma declaração SQL na forma de um “objeto declaração” que pode ser
manipulado por alguns métodos específicos. Entre estes métodos, o bindParam() que vincula uma variável a um
espaço demarcado na declaração.
$stmt->bindParam(1, $nome);
$stmt->bindParam(2, $email);
$stmt->bindParam(3, $celular);
O método bindParam() recebe ainda outros argumentos, mas não vamos abrangê-los aqui. Se desejar
descobrir mais sobre isso, não deixe de conferir a documentação do
php: http://php.net/manual/pt_BR/pdostatement.bindparam.php
Qual a diferença entre bindParam e bindValue?
No bindParam() o argumento esperado é uma referência (variável ou constante) e não pode ser um tipo
primitivo como uma string ou número solto, retorno de função/método. Já bindValue() pode receber referências e
valores como argumento.
$stmt->bindParam(':v1', 10); // Inválido
$stmt->bindParam(':v1', getValor()); // Inválido
Diferenças em PDOStatement::execute()
Com bindParam, ao contrário de bindValue, a variável está vinculada com uma referência e só será avaliada
no momento que PDOStatement::execute() é chamado.
Com bindParam:
$sex = 'male';
$s = $dbh->prepare('SELECT name FROM students WHERE sex = :sex');
$s->bindParam(':sex', $sex);
$sex = 'female';
$s->execute(); // Executado quando $sex = 'female'
Com bindValue:
$sex = 'male';
$s = $dbh->prepare('SELECT name FROM students WHERE sex = :sex');
$s->bindValue(':sex', $sex);
$sex = 'female';
$s->execute(); // Executado quando $sex = 'male'
A declaração estando pronta, ou seja, o novo objeto PDO Statement, então poderá ser executada. Neste
momento que chamamos o método execute() do próprio objeto Statement:
if ($stmt->execute()) {
Você até poderia deixar de usar o método bindParam e definir as variáveis dentro de um array passando-os
como argumento diretamente ao método execute(), todavia isto será valido apenas para tipos String. Além disso, você
também poderia colocar a variável diretamente na declaração recebida pelo método prepare(), mas estaria vulnerável
a problemas de segurança que também não vou destacar neste artigo. Em resumo, procure sempre usar
o bindParam().
Ainda, veja que chamamos o método execute() dentro de um bloco “if”, pois ele retornará um valor booleano
true se a instrução for executada com êxito. E por isso, temos um recurso para darmos o início ao tratamento de erros.
Visando refinar esse tratamento de erros, aproveitamos para fazer uso de mais um método da PDO Statement que fica
disponível após o execute, que é o rowCount():
if ($stmt->rowCount() > 0) {
O rowCount() retornará o número de linhas afetadas na tabela por uma última instrução DELETE, INSERT ou
UPDATE executada pelo objeto PDOStatement correspondente. Em nosso caso, até agora foi um INSERT. Já que
estamos inserindo uma linha, esperamos que como resposta haja um número maior que zero “0”. Logo, temos uma
segunda etapa para tratar erros que nos permite fazer mais alguma coisa em caso de êxito ou não. E é isso que
acontece:
Primeiro, imprimimos uma mensagem informando que tudo correu bem:
echo "Dados cadastrados com sucesso!";
Em seguida, limpamos as variáveis para que o formulário não seja preenchido novamente nesta etapa:
$id = null;
$nome = null;
$email = null;
$celular = null;
Concluímos a etapa Create do CRUD. Agora vamos passar para próxima etapa que é o Read.
Se você abrir a página, vai perceber apenas o cabeçalho da tabela, mas nada é impresso abaixo dele e esta é
nossa intenção até este momento.
Mas, agora vamos então gerar uma listagem dos registros abaixo deste cabeçalho e para isso, você deverá
abrir e fechar as tags do php “<?php” e “?>” logo abaixo da tag de fechamento da linha da tabela “</tr>” e, antes da tag
de fechamento da tabela “</table>”. Dentro deste bloco de código php, precisamos adicionar um novo bloco “try e
catch”:
<?php
try {
if ($stmt->execute()) {
while ($rs = $stmt->fetch(PDO::FETCH_OBJ)) {
echo "<tr>";
echo "<td>".$rs->nome."</td><td>".$rs->email."</td><td>".$rs->celular
."</td><td><center><a href=\"\">[Alterar]</a>"
." "
."<a href=\"\">[Excluir]</a></center></td>";
echo "</tr>";
}
} else {
echo "Erro: Não foi possível recuperar os dados do banco de dados";
}
} catch (PDOException $erro) {
echo "Erro: ".$erro->getMessage();
}
?>
Abrindo a página, você perceberá que já é possivel ver um registro salvo anteriormente. Criamos este registro
no teste da quarta etapa.
O que fizemos alí foi indicar na âncora “href” do link os parâmetros “act” e “id” que serão lidos pela mesma
página no refresh. Note também que não indicamos uma página alvo para âncora e sim apenas deixamos o sinal de
interrogação “?”. Se você abrir a página no navegador e levar ponteiro acima do link “Alterar” de cada registro, vai notar
na barra de status inferior do browser que agora um link é informado. Além disso, também já apresenta o id para cada
registro. Já que estamos mexendo alí, podemos também já deixar pronto o outro link para “Delete”. Então, no dentro
da tag “<a>” com rótulo “[Excluir]” logo a frente, preencha ente as duas “aspas escapadas” (\”) a seguinte informação:
Percebeu lá no código que inserimos agora que este bloco “if” também é muito semelhante a aquele anterior
na quinta etapa onde listamos os registros na tabela? Sim, mas com duas diferenças sutis:
Primeira: Utilizamos um filtro na instrução SQL pelo id e novamente fazemos uso do bindParam() para adicioná-
lo ao objeto PDO Statement. Além disso, também informamos agora mais um novo parâmetro no método bindParam(),
que informa qual é o tipo de dado na variável $id. Este parâmetro é indicado pela constante PDO::PARAM_INT.
Segunda: Não utilizamos mais o laço de repetição While! E isso faz sentido aqui, pois se estamos filtrando pela
coluna chave primária, esperamos é claro recuperar apenas um único registro.
Na sequencia, são recuperados os atributos do “result set” para cada variável correspondente, que são lidas
logo em seguida durante a construção do formulário. Assim, os campos são preenchidos com os dados das variáveis,
“alimentado-os com os valores” em “value”. Interessante?
Ok mas, recuperamos os dados no formulário, alteramos e … e agora para salvar os dados atualizados
novamente no banco? Porque se você clicar no botão salvar, o resultado será um novo registro logo abaixo:
Falhamos em nosso projeto? Precisamos adicionar mais um botão para “Update“? Claro que não! Mas fica
evidente que precisamos fazer uma alteração na lógica para atender a nova funcionalidade. Afinal, os nossos objetivos
anteriores estavam sendo alcançados, mas agora estamos em uma nova etapa!
Esta mudança será feita no bloco “if” que criamos na Quarta Etapa: Create. Então, esteja antento as mudanças
seguintes:
Primeiro, olhe atentamente a linha abaixo:
$stmt = $conexao->prepare("INSERT INTO contatos (nome, email, celular) VALUES (?, ?,
?)");
Coloque esta linha dentro do bloco “else” de um novo bloco “if” que criaremos para fazer teste da variável $id,
para checar se a mesma recebeu algum valor. O quê? Sim, desta forma:
if ($id != "") {
} else {
$stmt = $conexao->prepare("INSERT INTO contatos (nome, email, celular) VALUES (?,
?, ?)");
}
Muito bem! então, alí a decisão recai sobre condição em que, se a variável $id estiver vazia, será feito um
Insert, comojá estava sendo feito antes. Mas e se a variável $id possuir um valor diferente de vazio?
Neste caso, faremos um “Update”! Aqui está a grande sacada! Então, o bloco deverá receber uma nova
preparação de declaração para “Update”, fincando portando, assim:
if ($id != "") {
$stmt = $conexao->prepare("UPDATE contatos SET nome=?, email=?, celular=? WHERE id
= ?");
$stmt->bindParam(4, $id);
} else {
$stmt = $conexao->prepare("INSERT INTO contatos (nome, email, celular) VALUES (?,
?, ?)");
}
Notou que, caso seja recebido um valor em $id, estaremos executando um “UPDATE” para aquele valor
armazenado na variável $id?
Além disso, como vamos lidar com um quarto parâmetro a ser inserido no objeto PDO Statement, ele também
já o é informado logo na linha abaixo da instrução contendo o método prepare(), dentro do mesmo do bloco “if”:
$stmt->bindParam(4, $id);
Agora a aplicação está funcionando como esperado! Já é possível criar novos registros e atualizá-los! Faça o
teste!
Você deve se lembrar de que na etapa anterior, preparamos a interface com usuário, deixando basicamente
pronto o Link de exclusão de registro, se lembra? Portanto, falta apenas implementar a funcionalidade para este link!
Para isso, logo abaixo do ultimo bloco “if” e antes da tag de encerramento do primiero bloco php “?>” e que se localiza
antes das tags html, inclua este novo bloco if:
if (isset($_REQUEST["act"]) && $_REQUEST["act"] == "del" && $id != "") {
try {
$stmt = $conexao->prepare("DELETE FROM contatos WHERE id = ?");
$stmt->bindParam(1, $id, PDO::PARAM_INT);
if ($stmt->execute()) {
echo "Registo foi excluído com êxito";
$id = null;
} else {
throw new PDOException("Erro: Não foi possível executar a declaração
sql");
}
} catch (PDOException $erro) {
echo "Erro: ".$erro->getMessage();
}
}
Veja que estes códigos já se tornaram muito familiares, mas com certas semelhanças e sutis diferenças:
Primeira observação: nossa declaração é um Delete e recebe um parâmetro id que será associado ao objeto
PDO Statement utilizando o método bindParam(), novamente com o novo parâmetro PDO::PARAM_INT.
Segunda observação: agora não utilizamos mais um método fetch() para “juntar” alguma coisa, mas apenas o
método execute(). Sim, é muito óbvio o motivo disso: porque não precisamos recuperar nenhum valor mas apenas
executar o método execute() que já o é suficiente para cumprir o objetivo.
Perceba que dentro do bloco “if” do método execute() acima, deixamos uma instrução para imprimir na tela
uma mensagem, informando que o registro foi excluído com êxito. Além disso, após a exclusão, precisamos anular
qualquer valor na variável $id, para que o nosso formulário retorne livre e pronto para receber novos registros. Se você
iniciar a página no browser, verá que o recurso de exclusão agora também funciona perfeitamente!
Segurança
Para reforçar a segurança, especialmente em tratamento de senhas e entrada de usuários, aqui estão algumas
mudanças recomendadas:
Armazenamento de senhas: Use password_hash() para criptografar senhas antes de armazená-las no banco.
Validação de entrada: Utilize filter_var() para sanitizar e validar dados antes de inseri-los no banco.
1. <?php
2. $senha = "minhaSenha123";
3. $senhaSegura = password_hash($senha, PASSWORD_DEFAULT);
4. ?>
5.
1. <?php
2. $email = "[email protected]";
3. if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
4. echo "E-mail inválido!";
5. }
6. ?>
7.
Conclusão
Pronto! Agora temos um CRUD completo, contendo as quatro operações do banco de dados em
funcionamento: Create, Read, Update e Delete! Acredito que por seguir este passo a passo a risca, lendo e relendo se
for o caso, você estará apto a criar aplicações com CRUD. Poderá criar até outras aplicações mais complexas do que
esta deste artigo!
Dica importante: Nunca
recomendo que você seja forte
seguidor do uso das teclas
CTRL+C e CTRL+V quando
está aprendendo, mas o ideal é
ir digitando caractere a
caractere! Todavia, se preferir
fazê-lo a fim de conferir a
funcionalidade do código, a
decisão está em suas mãos!
have a fun!
<?php
/**
* Projeto de aplicação CRUD utilizando PDO - Agenda de Contatos
*
* Alexandre Bezerra Barbosa
*/
if ($stmt->execute()) {
if ($stmt->rowCount() > 0) {
echo "Dados cadastrados com sucesso!";
$id = null;
$nome = null;
$email = null;
$celular = null;
} else {
echo "Erro ao tentar efetivar cadastro";
}
} else {
throw new PDOException("Erro: Não foi possível executar a declaração
sql");
}
} catch (PDOException $erro) {
echo "Erro: ".$erro->getMessage();
}
}