Como Fazer um Chat de Bate-Papo com PHP


Nesse projeto iremos focar na parte funcional do sistema, então não me venham dizer que o layout ou design do Chat está feio. Como esse é um artigo sobre PHP, vou dar prioridade para o ensino da parte programável do sistema, quanto ao layout e visual, após aprender a fazer o sistema funcionar, você poderá editá-lo a seu gosto ou à gosto de um possível cliente.

Definindo o funcionamento do Chat

Antes de tudo, é necessário definir como o nosso sistema irá funcionar, sem isso, é praticamente impossível desenvolver qualquer coisa que seja. Então vamos lá:

1- O Sistema terá uma página inicial onde o visitante escolherá um nome e selecionará a sala que deseja entrar.
2- Após clicar no botão entrar, o usuário será redirecionado para a página de conversas. Nessa página serão listados todos os usuários que estão online.
3- Ainda na página de conversas haverá um campo para o usuário digitar o texto juntamente com um botão para envio do texto para a sala. Também será possível selecionar um usuário para que a mensagem seja direcionada para ele, porém a mensagem será pública e todos os outros usuários poderão ver ela.
4- As conversas deverão ser atualizadas a cada 6 segundos.
5- Deverá ter um link para o usuário poder sair da sala.
6- Quando o usuário sair da sala, uma mensagem deverá ser enviada para a respectiva sala avisando que o usuário saiu da sala. Esse seria o funcionamento básico do chat, porém há outros detalhes a serem levados em consideração. Vejamos:

1- Não poderão conter dois usuários com o mesmo nome na sala. Isso iria gerar confusão.
2- O sistema deve enviar uma mensagem dizendo que determinado usuário saiu da sala, mesmo quando ele não clicar no botão sair e fechar o navegador ou a página do
chat.
3- Embora as conversas devam ser atualizadas a cada 6 segundos, isso não pode ocorrer na página inteira. Isso iria irritar o usuário e tornar a conversa praticamente impossível, visto que ele teria seu texto interrompido a cada 6 segundos.
4- O usuário só poderá visualizar as conversas que foram enviadas depois que ele entrou na sala.
5- As interações mais antigas que 10 horas deverão ser excluídas para poupar espaço em banco de dados

Estruturando o Banco de Dados

O primeiro passo é criar um banco de dados com algum nome sugestivo. Sugiro nomeá-lo como chat, nada mais sugestivo, não é mesmo? O segundo passo é criar as tabelas que irão armazenar os dados. Vejam as tabelas que criei, com explanações:

1- salas – a tabela salas conterá duas colunas:
a) id_sala (int) – identificador da sala
b) nm_sala (varchar 20) – nome da sala
A função da tabela salas é armazenar o identificador e o nome das salas. Nessa tabela você pode inserir quantas salas desejar.

2- usuarios – a tabela usuários conterá 4 colunas:
a) id_usuario (int) – identificador do usuario
b) nm_usuario (varchar 20) – nome do usuario
c) id_sala (int) – chave estrangeira provinda da coluna id_sala da tabela salas.
d) dt_refresh (datetime) – guarda a última data e hora que houve interação entre o chat e o respectivo usuário. A função da tabela usuários é servir como controle para sabermos quais usuários estão online, em quais salas eles entraram, quais são os nomes desses usuários e qual foi a última data e hora que determinado usuário interagiu com o sistema. Note que, nesse caso, interagir não significa que o usuário enviou mensagens no chat, mas simplesmente que a página está aberta e se atualizando (conectando ao servidor web e buscando as ultimas conversas).

3- interacoes – a tabela interações conterá 5 colunas:
a) nm_usuario (varchar 20) – nome do usuáro que originou a interação. Note que interação, nesse caso, significa alguma ação em relação ao chat. Por exemplo: entrou na sala, saiu da sala, fala com alguém. Talvez você esteja se perguntando qual a razão de ter esse nome de usuário visto que a tabela usuários já possui
esse dado. Bem, não podemos esquecer que a tabela usuário é uma tabela dinâmica, ou seja, os usuários de lá serão excluídos quando saírem da sala.
b) id_sala (int) – chave estrangeira provinda da coluna id_sala da tabela salas. Servirá para sabermos em qual sala, dada interação ocorreu.
c) dt_interacao (datetime) – data e hora que a interação ocorreu.
d) ds_interacao (varchar 500) – campo que irá armazenar o descritivo da interação, ou seja, a mensagem que os usuários enviaram e suas
interações com o sistema.
e) nm_destinatario (varchar 200) – caso uma mensagem tenha sido enviada para um usuário específico, o nome desse usuário será armazenado nesse campo. È isso senhores. Por incrível que pareça, nosso banco de dados está pronto. Agora só precisamos fazer a parte mais legal do sistema, codificar ele.

Vejam o código do banco:

--
-- Estrutura da tabela `interacoes`
--

CREATE TABLE IF NOT EXISTS `interacoes` (
  `nm_usuario` varchar(20) NOT NULL,
  `id_sala` int(11) NOT NULL,
  `dt_interacao` datetime NOT NULL,
  `ds_interacao` varchar(500) NOT NULL,
  `nm_destinatario` varchar(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

--
-- Extraindo dados da tabela `interacoes`
--


-- --------------------------------------------------------

--
-- Estrutura da tabela `salas`
--

CREATE TABLE IF NOT EXISTS `salas` (
  `id_sala` int(11) NOT NULL AUTO_INCREMENT,
  `nm_sala` varchar(20) NOT NULL,
  PRIMARY KEY (`id_sala`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

--
-- Extraindo dados da tabela `salas`
--

INSERT INTO `salas` (`id_sala`, `nm_sala`) VALUES
(1, 'Crianças'),
(2, 'Adolescentes'),
(3, 'Homens e Mulheres');

-- --------------------------------------------------------

--
-- Estrutura da tabela `usuarios`
--

CREATE TABLE IF NOT EXISTS `usuarios` (
  `id_usuario` int(11) NOT NULL AUTO_INCREMENT,
  `nm_usuario` varchar(20) NOT NULL,
  `id_sala` int(11) NOT NULL,
  `dt_refresh` datetime NOT NULL,
  PRIMARY KEY (`id_usuario`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=26 ;

--
-- Extraindo dados da tabela `usuarios`
--

INSERT INTO `usuarios` (`id_usuario`, `nm_usuario`, `id_sala`, `dt_refresh`) VALUES
(22, 'Edir', 3, '2010-12-11 19:13:48');

Na próxima etapa iremos crias as páginas PHP bem como a codificação para o chat, de fato, funcionar. Fique antenado aqui no blog Profissionais Web para não perder as novidades.

Fazer Chat no PHP – Etapa 2

O nosso chat PHP será composto por 5 arquivos, ou 5 páginas php, como queira. Vou te mostrar cada uma delas, os códigos e também os comentários explanatórios. Vamos lá:

config.php do Chat

O arquivo config.php, como o nome sugere, conterá as configurações para conexão com o banco de dados, onde:

$servidor: variável que conterá o servidor do teu banco de dados, no meu caso é localhost
$tipo_servidor: conterá o tipo do teu servidor, no meu caso é o Mysql.
$nome_do_banco: nome do teu banco de dados, aqui eu criei um banco chamado chat
$usuario: o usuario do banco de dados
$senha: a senha do banco de dados

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//Informações para conexão com o banco de dados
$servidor = "localhost";
$tipo_servidor = "mysql";
$nome_do_banco = "chat";
$usuario = "root";
$senha = "";
 
//instancia um objeto da classe PDO chamado $conn
$conn = new PDO("$tipo_servidor:host=$servidor;dbname=$nome_do_banco",$usuario,$senha);
 
?>
<?php
//Informações para conexão com o banco de dados
$servidor = "localhost";
$tipo_servidor = "mysql";
$nome_do_banco = "chat";
$usuario = "root";
$senha = "";

//instancia um objeto da classe PDO chamado $conn
$conn = new PDO("$tipo_servidor:host=$servidor;dbname=$nome_do_banco",$usuario,$senha);

?>

Por fim, instancio um objeto com a conexão com o banco de dados. A partir de agora, poderei interagir com o banco de dados facilmente através da variável $conn. Note que estou usando PDO, caso você não sabia o que é PDO, leia este artigo aqui: O que é PDO

functions.php do Chat

Este arquivo PHP conterá as funções que serão utilizadas no chat. Veja o código abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
<?php
//--------------------------------------------------------------------Funções específicas do Chat:
 
//Cria as interações na tabela interações.
function interagir($from, $to, $sala, $chat){
    global $conn;
    
    $now = date("Y-m-d H:i:s");
    
    $tbInt = $conn->prepare("insert into interacoes values(:nome, :sala, :data, :chat, :to)");
    $tbInt->bindParam(":nome", $from, PDO::PARAM_STR);
    $tbInt->bindParam(":sala", $sala, PDO::PARAM_INT);
    $tbInt->bindParam(":data", $now, PDO::PARAM_STR);
    $tbInt->bindParam(":chat", $chat, PDO::PARAM_STR);
    $tbInt->bindParam(":to", $to, PDO::PARAM_STR);
    $tbInt->execute();
}
 
//Insere o usuário na tabela ususarios, configura as sessões, cria a interação de entrada na sala e redireciona o usuário para a página principal do chat.
function start_chat(){
    global $conn, $nome, $sala;
    
    $unique_name = get_unique_name($nome,$nome);
    $now = date("Y-m-d H:i:s");
    
    //Insere o usuario no banco
    $insert = $conn->prepare("insert into usuarios(nm_usuario, id_sala, dt_refresh) values(:nm,:sala, :now)");
    $insert->bindParam(":nm", $unique_name, PDO::PARAM_STR);
    $insert->bindParam(":sala", $sala, PDO::PARAM_INT); 
    $insert->bindParam(":now", $now, PDO::PARAM_STR);       
    $insert->execute();
    $_SESSION["user"] = $conn->lastInsertId();
    $_SESSION["user_name"] = $unique_name;
    $_SESSION["sala"] = $sala;
    $_SESSION["data_logon"] = $now;
    interagir($_SESSION["user_name"], "", $_SESSION["sala"], "Entrou na sala.");
    header("Location: chat.php");
    
}
 
 
//Função que garante um nome única na respectiva sala. Caso o nome já existe, essa função insere um underline no final do nome e vai incrementando valores até que o nome gerado não tenha sido utiliado por outro usuário ativo na sala.
function get_unique_name($nome_original, $nome_alterado, $repetido=1){
    global $conn, $sala;
    
    $tbUsers = $conn->prepare("select count(*) as total from usuarios where id_sala =:sala and nm_usuario =:nm");
    $tbUsers->bindParam(":sala",$sala, PDO::PARAM_INT);
    $tbUsers->bindParam(":nm",$nome_alterado, PDO::PARAM_STR);  
    $tbUsers->execute();
    $linha = $tbUsers->fetch(PDO::FETCH_ASSOC);
    
    if($linha["total"]>0){
        echo $nome_alterado;
        echo "<br>";
        return get_unique_name($nome_original, $nome_original . "_" . $repetido, ($repetido+1));
    }else{
        return $nome_alterado;  
    }
}
 
 
// Exclui os usuários onde não houve refresh na página há mais de 16 segundos.
function delete_offline_users(){
    global $conn;
    
    $now = date("Y/m/d H:i:s");
    $past16s = makeDataTime($now, 0,0,0,0,0,-16);
    
    //Seleciona usuarios ativos
    $ativos = $conn->prepare("select id_usuario from usuarios where dt_refresh > :dt");
    $ativos->bindParam(":dt", $past16s, PDO::PARAM_STR);
    $ativos->execute();
    $ativos = $ativos->fetchAll(PDO::FETCH_NUM|PDO::FETCH_COLUMN);
    $ativos_ = "";
    
    if(count($ativos)<=0) return;
    
    $ativos = implode(",", $ativos);
 
    //Pega dados dos usuarios e cria interacao de saída
    $tbUser = $conn->prepare("select nm_usuario, id_sala from usuarios where id_usuario not in($ativos)");
    $tbUser->execute();
    while($l = $tbUser->fetch(PDO::FETCH_ASSOC)){
        interagir($l["nm_usuario"], "", $l["id_sala"], "Saiu da sala.");
    }
    
    //Exclui usuarios inativos
    $del = $conn->prepare("delete from usuarios where id_usuario not in($ativos)");
    $del->execute();    
}
 
//Exclui interações antigas, criadas há 10 horas atrás ou mais.
function delete_old_entries(){
    global $conn;
    
    $now = date("Y/m/d H:i:s");
    $past10h = makeDataTime($now, 0,0,0,-10,0,0);
    
    //Excluir Interações antigas (10 horas)
    $del = $conn->prepare("delete from interacoes where dt_interacao < :dt");
    $del->bindParam(":dt", $past10h, PDO::PARAM_STR);
    $del->execute();
}
 
//Como o nome sugere, retorna o nome de uma dada sala. Deve-se passar o id da sala que desejas o nome, como parâmetro.
function pega_nome_sala($id_sala){
    global $conn;
    
    $tbSala = $conn->prepare("select nm_sala from salas where id_sala=:id");
    $tbSala->bindParam(":id", $id_sala, PDO::PARAM_INT);
    $tbSala->execute();
    $l = $tbSala->fetch(PDO::FETCH_ASSOC);
    return $l["nm_sala"];
}
 
//--------------------------------------------------------------------Funções gerais:
function makeData($data, $anoConta,$mesConta,$diaConta){
   $ano = substr($data,0,4);
   $mes = substr($data,5,2);
   $dia = substr($data,8,2);
   return date('Y-m-d',mktime (0, 0, 0, $mes+($mesConta), $dia+($diaConta), $ano+($anoConta))); 
}
 
function makeDataTime($data, $anoConta,$mesConta,$diaConta, $horaConta, $minutoConta, $segundoConta){
   $ano = substr($data,0,4);
   $mes = substr($data,5,2);
   $dia = substr($data,8,2);
   $hora = substr($data,11,2);
   $minuto = substr($data,14,2);
   $segundo = substr($data,17,2);
   
   return date('Y-m-d H:i:s',mktime ($hora+($horaConta), $minuto+($minutoConta), $segundo+($segundoConta), $mes+($mesConta), $dia+($diaConta), $ano+($anoConta)));  
}
 
function isSelected($campo, $varCampo){
    if($campo==$varCampo) return " selected=selected ";
    return "";
}
 
function isEmpty($campo,$name){
    global $msg;
    if(str_replace(" ","",$campo)==""){
       $msg = "O campo " . $name . " não foi preenchido corretamente! A ação foi cancelada!";
       return true; 
    }
    return false;
}
 
 
function isValidEmail($value){
    $pattern = "/^([a-zA-Z0-9])+([\.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-]+)+/";
    return preg_match($pattern, $value);
}
 
 
function isDate($date){
    $char = strpos($date, "/")!==false?"/":"-";
    $date_array = explode($char,$date);
    if(count($date_array)!=3) return false;
    return checkdate($date_array[1],$date_array[0],$date_array[2])?($date_array[2] . "-" . $date_array[1] . "-" . $date_array[0]):false;
}
 
?>
<?php
//--------------------------------------------------------------------Funções específicas do Chat:

//Cria as interações na tabela interações.
function interagir($from, $to, $sala, $chat){
	global $conn;
	
	$now = date("Y-m-d H:i:s");
	
	$tbInt = $conn->prepare("insert into interacoes values(:nome, :sala, :data, :chat, :to)");
	$tbInt->bindParam(":nome", $from, PDO::PARAM_STR);
	$tbInt->bindParam(":sala", $sala, PDO::PARAM_INT);
	$tbInt->bindParam(":data", $now, PDO::PARAM_STR);
	$tbInt->bindParam(":chat", $chat, PDO::PARAM_STR);
	$tbInt->bindParam(":to", $to, PDO::PARAM_STR);
	$tbInt->execute();
}

//Insere o usuário na tabela ususarios, configura as sessões, cria a interação de entrada na sala e redireciona o usuário para a página principal do chat.
function start_chat(){
	global $conn, $nome, $sala;
	
	$unique_name = get_unique_name($nome,$nome);
	$now = date("Y-m-d H:i:s");
	
	//Insere o usuario no banco
	$insert = $conn->prepare("insert into usuarios(nm_usuario, id_sala, dt_refresh) values(:nm,:sala, :now)");
	$insert->bindParam(":nm", $unique_name, PDO::PARAM_STR);
	$insert->bindParam(":sala", $sala, PDO::PARAM_INT);	
	$insert->bindParam(":now", $now, PDO::PARAM_STR);		
	$insert->execute();
	$_SESSION["user"] = $conn->lastInsertId();
	$_SESSION["user_name"] = $unique_name;
	$_SESSION["sala"] = $sala;
	$_SESSION["data_logon"] = $now;
	interagir($_SESSION["user_name"], "", $_SESSION["sala"], "Entrou na sala.");
	header("Location: chat.php");
	
}


//Função que garante um nome única na respectiva sala. Caso o nome já existe, essa função insere um underline no final do nome e vai incrementando valores até que o nome gerado não tenha sido utiliado por outro usuário ativo na sala.
function get_unique_name($nome_original, $nome_alterado, $repetido=1){
	global $conn, $sala;
	
	$tbUsers = $conn->prepare("select count(*) as total from usuarios where id_sala =:sala and nm_usuario =:nm");
	$tbUsers->bindParam(":sala",$sala, PDO::PARAM_INT);
	$tbUsers->bindParam(":nm",$nome_alterado, PDO::PARAM_STR);	
	$tbUsers->execute();
	$linha = $tbUsers->fetch(PDO::FETCH_ASSOC);
	
	if($linha["total"]>0){
		echo $nome_alterado;
		echo "<br>";
		return get_unique_name($nome_original, $nome_original . "_" . $repetido, ($repetido+1));
	}else{
		return $nome_alterado;	
	}
}


// Exclui os usuários onde não houve refresh na página há mais de 16 segundos.
function delete_offline_users(){
	global $conn;
	
	$now = date("Y/m/d H:i:s");
	$past16s = makeDataTime($now, 0,0,0,0,0,-16);
	
    //Seleciona usuarios ativos
	$ativos = $conn->prepare("select id_usuario from usuarios where dt_refresh > :dt");
	$ativos->bindParam(":dt", $past16s, PDO::PARAM_STR);
	$ativos->execute();
	$ativos = $ativos->fetchAll(PDO::FETCH_NUM|PDO::FETCH_COLUMN);
	$ativos_ = "";
	
	if(count($ativos)<=0) return;
	
	$ativos = implode(",", $ativos);

	//Pega dados dos usuarios e cria interacao de saída
	$tbUser = $conn->prepare("select nm_usuario, id_sala from usuarios where id_usuario not in($ativos)");
	$tbUser->execute();
	while($l = $tbUser->fetch(PDO::FETCH_ASSOC)){
		interagir($l["nm_usuario"], "", $l["id_sala"], "Saiu da sala.");
	}
	
	//Exclui usuarios inativos
	$del = $conn->prepare("delete from usuarios where id_usuario not in($ativos)");
	$del->execute();	
}

//Exclui interações antigas, criadas há 10 horas atrás ou mais.
function delete_old_entries(){
	global $conn;
	
	$now = date("Y/m/d H:i:s");
	$past10h = makeDataTime($now, 0,0,0,-10,0,0);
	
    //Excluir Interações antigas (10 horas)
	$del = $conn->prepare("delete from interacoes where dt_interacao < :dt");
	$del->bindParam(":dt", $past10h, PDO::PARAM_STR);
	$del->execute();
}

//Como o nome sugere, retorna o nome de uma dada sala. Deve-se passar o id da sala que desejas o nome, como parâmetro.
function pega_nome_sala($id_sala){
	global $conn;
	
	$tbSala = $conn->prepare("select nm_sala from salas where id_sala=:id");
	$tbSala->bindParam(":id", $id_sala, PDO::PARAM_INT);
	$tbSala->execute();
	$l = $tbSala->fetch(PDO::FETCH_ASSOC);
	return $l["nm_sala"];
}

//--------------------------------------------------------------------Funções gerais:
function makeData($data, $anoConta,$mesConta,$diaConta){
   $ano = substr($data,0,4);
   $mes = substr($data,5,2);
   $dia = substr($data,8,2);
   return date('Y-m-d',mktime (0, 0, 0, $mes+($mesConta), $dia+($diaConta), $ano+($anoConta)));	
}

function makeDataTime($data, $anoConta,$mesConta,$diaConta, $horaConta, $minutoConta, $segundoConta){
   $ano = substr($data,0,4);
   $mes = substr($data,5,2);
   $dia = substr($data,8,2);
   $hora = substr($data,11,2);
   $minuto = substr($data,14,2);
   $segundo = substr($data,17,2);
   
   return date('Y-m-d H:i:s',mktime ($hora+($horaConta), $minuto+($minutoConta), $segundo+($segundoConta), $mes+($mesConta), $dia+($diaConta), $ano+($anoConta)));	
}

function isSelected($campo, $varCampo){
	if($campo==$varCampo) return " selected=selected ";
	return "";
}

function isEmpty($campo,$name){
	global $msg;
	if(str_replace(" ","",$campo)==""){
	   $msg = "O campo " . $name . " não foi preenchido corretamente! A ação foi cancelada!";
	   return true;	
	}
	return false;
}


function isValidEmail($value){
	$pattern = "/^([a-zA-Z0-9])+([\.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-]+)+/";
	return preg_match($pattern, $value);
}


function isDate($date){
	$char = strpos($date, "/")!==false?"/":"-";
	$date_array = explode($char,$date);
	if(count($date_array)!=3) return false;
	return checkdate($date_array[1],$date_array[0],$date_array[2])?($date_array[2] . "-" . $date_array[1] . "-" . $date_array[0]):false;
}

?>

Em cada função há um comentário explicando qual sua finalidade, mas se você ficou com alguma dúvida, pergunte nos comentários.

index.php do Chat

A index.php será a página de entrada, ou seja, onde o usuário irá digitar o nome dele e selecionar a sala que quer entrar. Veja o código abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php
session_start();
require_once("config.php");
require_once("functions.php");
 
//Pega o nome e a sala que o usuário soliciou entrar
$nome = isset($_POST["txtNome"])?strip_tags($_POST["txtNome"]):"";
$sala = isset($_POST["slSala"])?(int)$_POST["slSala"]:1;
 
//Se o nome não estiver em branco, executa uma rotina de limpeza delete_olde_entries() e inicia o chat.
if(!empty($nome)){
    delete_old_entries();
    start_chat();
}
 
 
?>
<html>
<head>
<title>Chat</title>
<style>
.tab{
    background-color:#000;
    color:#FFF;
    font-size:12px;
    font-weight:bold;
    padding:4px;
}
</style>
</head>
<body>
<div style="text-align:center">
 
<h1>Chat Online</h1>
 
<hr />
 
Escolha um Nickname e a Sala
 
<form action="index.php" method="post">
<table width="200" border="0" cellpadding="0" cellspacing="0" align="center">
  <tr>
    <td width="70" class="tab">Nome</td>
    <td width="130">
        <input type="text" name="txtNome" id="txtNome" />
    </td>
   </tr>
  <tr>
    <td class="tab">Sala</td>
    <td>
    <select name="slSala">
    <?php
    //Lista todas as salas cadastradas no banco de dados
    $tbSala = $conn->prepare("select * from salas");
    $tbSala->execute();
    while($linha=$tbSala->fetch(PDO::FETCH_ASSOC)){
        echo "<option value='$linha[id_sala]'>$linha[nm_sala]</option>";
    }
    ?>
    </select>
    </td>
    </tr>
  <tr>
    <td> </td>
    <td><label>
      <input type="submit" name="btnEntrar" id="btnEntrar" value="Entrar" />
    </label></td>
    </tr>
</table>
</form>
 
 
</div>
</body>
</html>
<?php
session_start();
require_once("config.php");
require_once("functions.php");

//Pega o nome e a sala que o usuário soliciou entrar
$nome = isset($_POST["txtNome"])?strip_tags($_POST["txtNome"]):"";
$sala = isset($_POST["slSala"])?(int)$_POST["slSala"]:1;

//Se o nome não estiver em branco, executa uma rotina de limpeza delete_olde_entries() e inicia o chat.
if(!empty($nome)){
	delete_old_entries();
	start_chat();
}


?>
<html>
<head>
<title>Chat</title>
<style>
.tab{
	background-color:#000;
	color:#FFF;
	font-size:12px;
	font-weight:bold;
	padding:4px;
}
</style>
</head>
<body>
<div style="text-align:center">

<h1>Chat Online</h1>

<hr />

Escolha um Nickname e a Sala

<form action="index.php" method="post">
<table width="200" border="0" cellpadding="0" cellspacing="0" align="center">
  <tr>
    <td width="70" class="tab">Nome</td>
    <td width="130">
        <input type="text" name="txtNome" id="txtNome" />
    </td>
   </tr>
  <tr>
    <td class="tab">Sala</td>
    <td>
    <select name="slSala">
    <?php
	//Lista todas as salas cadastradas no banco de dados
    $tbSala = $conn->prepare("select * from salas");
	$tbSala->execute();
	while($linha=$tbSala->fetch(PDO::FETCH_ASSOC)){
		echo "<option value='$linha[id_sala]'>$linha[nm_sala]</option>";
	}
	?>
    </select>
    </td>
    </tr>
  <tr>
    <td> </td>
    <td><label>
      <input type="submit" name="btnEntrar" id="btnEntrar" value="Entrar" />
    </label></td>
    </tr>
</table>
</form>


</div>
</body>
</html>

Note que as parter importantes do código estão comentadas no próprio código. Em suma, após o usuário clicar no botão Entrar, o código executará duas funções principais:

delete_old_entries() – esta função fará excluir interações (conversas) antigas, de 10 horas atrás ou mais. O objetivo desta função é poupar espaço em banco de dados, afinal, para que ter conversas tão antigas armazenadas em teu banco de dados?

start_chat() – esta função vai inserir o novo usuario na tabela usuarios, criar a interação “Fulano Entrou na Sala” e na sequência irá redirecionar o usuário para a página chat.php

chat.php do Chat

A página chat.php é a página principal, que permitirá aos usuários presentes na mesma sala a comunicação. Veja o código abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
session_start();
require_once("config.php");
require_once("functions.php");
 
//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
    header("Location: index.php");
    exit();
}
 
//Verifica se o usuário já foi excluído do banco
$tbUser = $conn->prepare("select count(*) as total from usuarios where id_usuario=:id");
$tbUser->bindParam(":id",$_SESSION["user"], PDO::PARAM_INT);
$tbUser->execute();
$linha = $tbUser->fetch(PDO::FETCH_ASSOC);
if($linha["total"] < 1){
    session_destroy();
    header("Location: index.php");
    exit(); 
}
 
//Pega o nome do destinatário da mensagem
$to = isset($_POST["slUsers"])?$_POST["slUsers"]:"";
 
//Verifica se o usuário enviou alguma mensagem, caso positivo, ele chama a função interagir passando os dados do respectivo usuário como parâmetro.
 
if(isset($_POST["btnEnviar"]) && isset($_POST["txtMensagem"])){
    interagir($_SESSION["user_name"], $to, $_SESSION["sala"], strip_tags($_POST["txtMensagem"]) );
}
 
?>
<html>
<head>
<title>Chat</title>
<style>
.tab{
    background-color:#000;
    color:#FFF;
    font-size:12px;
    font-weight:bold;
    padding:4px;
}
</style>
</head>
<body>
<div style="text-align:center">
 
<h1>Chat Online</h1>
 
<h2 style="color:#0C3">Você está na Sala <?php echo pega_nome_sala($_SESSION["sala"]);?> <a href="sair.php">Sair da Sala</a></h2>
 
<hr />
<form action="chat.php" method="post">
<table width="709" border="1" align="center" cellpadding="0" cellspacing="0">
  <tr>
    <td width="516"><iframe src="interacao.php" width="500px" height="500px" frameborder="0" scrolling="yes"></iframe> </td>
    <td width="4"> </td>
    <td width="189"><?php require_once("users-online.php");?> </td>
  </tr>
  <tr>
    <td colspan="3"><?php require_once("writing.php");?> </td>
    </tr>
</table>
</form>
</div>
</body>
</html>
<?php
session_start();
require_once("config.php");
require_once("functions.php");

//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
	header("Location: index.php");
	exit();
}

//Verifica se o usuário já foi excluído do banco
$tbUser = $conn->prepare("select count(*) as total from usuarios where id_usuario=:id");
$tbUser->bindParam(":id",$_SESSION["user"], PDO::PARAM_INT);
$tbUser->execute();
$linha = $tbUser->fetch(PDO::FETCH_ASSOC);
if($linha["total"] < 1){
	session_destroy();
	header("Location: index.php");
	exit();	
}

//Pega o nome do destinatário da mensagem
$to = isset($_POST["slUsers"])?$_POST["slUsers"]:"";

//Verifica se o usuário enviou alguma mensagem, caso positivo, ele chama a função interagir passando os dados do respectivo usuário como parâmetro.

if(isset($_POST["btnEnviar"]) && isset($_POST["txtMensagem"])){
	interagir($_SESSION["user_name"], $to, $_SESSION["sala"], strip_tags($_POST["txtMensagem"]) );
}

?>
<html>
<head>
<title>Chat</title>
<style>
.tab{
	background-color:#000;
	color:#FFF;
	font-size:12px;
	font-weight:bold;
	padding:4px;
}
</style>
</head>
<body>
<div style="text-align:center">

<h1>Chat Online</h1>

<h2 style="color:#0C3">Você está na Sala <?php echo pega_nome_sala($_SESSION["sala"]);?> <a href="sair.php">Sair da Sala</a></h2>

<hr />
<form action="chat.php" method="post">
<table width="709" border="1" align="center" cellpadding="0" cellspacing="0">
  <tr>
    <td width="516"><iframe src="interacao.php" width="500px" height="500px" frameborder="0" scrolling="yes"></iframe> </td>
    <td width="4"> </td>
    <td width="189"><?php require_once("users-online.php");?> </td>
  </tr>
  <tr>
    <td colspan="3"><?php require_once("writing.php");?> </td>
    </tr>
</table>
</form>
</div>
</body>
</html>

O código PHP do topo da página está comentado, então basta ler para entender, agora, no final temos um formulário, aí temos três detalhes:

a) Um iframe que abre o arquivo interacao.php. O objetivo de utilizar este iframe é fazer com que somente as interações sejam atualizadas de 6 em 6 segundos. Note que se todo o resto da página se auto atualizasse a cada 6 segundos, o usuário poderia ter o texto cortado enquanto digitava alguma mensagem, o que seria muito chato. Além disso, buscar os usuários ativos no banco de dados a cada 6 segundos consumiria muitos processamentes desnecessário.

b) Inclusão do arquivo users-online.php. Este arquivo apenas seleciona todos os usuarios que estão no banco de dados

c) Inclusão do arquivo writing.php. Inclui o campo de inserção da mensagem e também o botão de envio da mesma. Este código poderia ter sido posto junto com o arquivo chat.php, mas para deixar o código mais “Limpo”, preferí coloca-lo num arquivo separado.

interacao.php do Chat

A página interacao.php é atualizada de 6 em 6 segundos para manter a conversa sempre atualizada. Além disso, atualiza a data do update e monta todas as interações do usuário desde que entrou na sala. Veja o script abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
session_start();
require_once("config.php");
require_once("functions.php");
 
//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
    header("Location: index.php");
    exit();
}
 
?>
<html>
<head>
<META HTTP-EQUIV="Refresh" CONTENT="6;URL=interacao.php">
</head>
<body onLoad="pageScroll()">
<script>
function pageScroll() {
                window.scrollBy(0,1000000); // horizontal and vertical scroll increments
                //scrolldelay = setTimeout('pageScroll()',100); // scrolls every 100 milliseconds
}
 
</script>
<?php
 
//Atualiza data do refresh
$now = date("Y-m-d H:i:s");
$tbUp = $conn->prepare("update usuarios set dt_refresh =:refresh where id_usuario=:id");
$tbUp->bindParam(":refresh", $now, PDO::PARAM_STR);
$tbUp->bindParam(":id", $_SESSION["user"], PDO::PARAM_INT);
$tbUp->execute();
 
//Exclui os usuários inativos
delete_offline_users();
 
//Lista todas as entradas desde que o usuario entrou.
$tbChat = $conn->prepare("select nm_usuario, ds_interacao, nm_destinatario, DATE_FORMAT(dt_interacao, '%H:%i:%s') as dt_interacao from interacoes where dt_interacao >= :data and id_sala=:sala order by dt_interacao ASC");
$tbChat->bindParam(":data", $_SESSION["data_logon"], PDO::PARAM_STR);
$tbChat->bindParam(":sala", $_SESSION["sala"], PDO::PARAM_INT);
$tbChat->execute();
 
 
while($lChat = $tbChat->fetch(PDO::FETCH_ASSOC)){
    $chat = $lChat["nm_destinatario"] == ""?strip_tags($lChat["ds_interacao"]):"fala com <strong>$lChat[nm_destinatario]:</strong><span style='color:#006699'> " . strip_tags($lChat["ds_interacao"]) . "</span>";
    echo "<div style='font-family:tahoma;font-size:12px;padding:4px'>";
    echo "<strong>$lChat[nm_usuario]</strong> $chat <span style='color:#cccccc'>$lChat[dt_interacao]</span>";
    echo "</div>";
}
 
?>
</body>
</html>
<?php
session_start();
require_once("config.php");
require_once("functions.php");

//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
	header("Location: index.php");
	exit();
}

?>
<html>
<head>
<META HTTP-EQUIV="Refresh" CONTENT="6;URL=interacao.php">
</head>
<body onLoad="pageScroll()">
<script>
function pageScroll() {
                window.scrollBy(0,1000000); // horizontal and vertical scroll increments
                //scrolldelay = setTimeout('pageScroll()',100); // scrolls every 100 milliseconds
}

</script>
<?php

//Atualiza data do refresh
$now = date("Y-m-d H:i:s");
$tbUp = $conn->prepare("update usuarios set dt_refresh =:refresh where id_usuario=:id");
$tbUp->bindParam(":refresh", $now, PDO::PARAM_STR);
$tbUp->bindParam(":id", $_SESSION["user"], PDO::PARAM_INT);
$tbUp->execute();

//Exclui os usuários inativos
delete_offline_users();

//Lista todas as entradas desde que o usuario entrou.
$tbChat = $conn->prepare("select nm_usuario, ds_interacao, nm_destinatario, DATE_FORMAT(dt_interacao, '%H:%i:%s') as dt_interacao from interacoes where dt_interacao >= :data and id_sala=:sala order by dt_interacao ASC");
$tbChat->bindParam(":data", $_SESSION["data_logon"], PDO::PARAM_STR);
$tbChat->bindParam(":sala", $_SESSION["sala"], PDO::PARAM_INT);
$tbChat->execute();


while($lChat = $tbChat->fetch(PDO::FETCH_ASSOC)){
	$chat = $lChat["nm_destinatario"] == ""?strip_tags($lChat["ds_interacao"]):"fala com <strong>$lChat[nm_destinatario]:</strong><span style='color:#006699'> " . strip_tags($lChat["ds_interacao"]) . "</span>";
	echo "<div style='font-family:tahoma;font-size:12px;padding:4px'>";
	echo "<strong>$lChat[nm_usuario]</strong> $chat <span style='color:#cccccc'>$lChat[dt_interacao]</span>";
	echo "</div>";
}

?>
</body>
</html>

users-online.php do Chat

Pega todos os usuários que estão no banco de dados, ou seja, que estão online:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
    header("Location: index.php");
    exit();
}
 
//Cria a lista com todos os usuários online na respectiva sala.
 
$tbUsers = $conn->prepare("select * from usuarios where id_sala=:sala");
$tbUsers->bindParam(":sala", $_SESSION["sala"], PDO::PARAM_STR);
$tbUsers->execute();
 
echo "<select name='slUsers' id='slUsers' size='30' style='width:180px' >";
while($l_users = $tbUsers->fetch(PDO::FETCH_ASSOC)){
    echo "<option value='$l_users[nm_usuario]' " . isSelected($to, $l_users["nm_usuario"]) . ">$l_users[nm_usuario]</option>";
}
echo "</select>";
?>
<?php
//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
	header("Location: index.php");
	exit();
}

//Cria a lista com todos os usuários online na respectiva sala.

$tbUsers = $conn->prepare("select * from usuarios where id_sala=:sala");
$tbUsers->bindParam(":sala", $_SESSION["sala"], PDO::PARAM_STR);
$tbUsers->execute();

echo "<select name='slUsers' id='slUsers' size='30' style='width:180px' >";
while($l_users = $tbUsers->fetch(PDO::FETCH_ASSOC)){
	echo "<option value='$l_users[nm_usuario]' " . isSelected($to, $l_users["nm_usuario"]) . ">$l_users[nm_usuario]</option>";
}
echo "</select>";
?>

writing.php do Chat

Adicione o campo texto e o botão de envio da mensagem:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
    header("Location: index.php");
    exit();
}
 
?>
 
<table width="558" border="0" cellpadding="0" cellspacing="0">
  <tr>
    <td width="377"><label>
      <textarea name="txtMensagem" cols="60" rows="3" id="txtMensagem"></textarea>
    </label></td>
    <td width="27"> </td>
    <td width="154"><input type="submit" name="btnEnviar" id="btnEnviar" value="Enviar Mensagem" style="padding-left:4px" /></td>
  </tr>
</table>
<?php
//Verifica se a sessão existe
if(!isset($_SESSION["user"])){
	header("Location: index.php");
	exit();
}

?>

<table width="558" border="0" cellpadding="0" cellspacing="0">
  <tr>
    <td width="377"><label>
      <textarea name="txtMensagem" cols="60" rows="3" id="txtMensagem"></textarea>
    </label></td>
    <td width="27"> </td>
    <td width="154"><input type="submit" name="btnEnviar" id="btnEnviar" value="Enviar Mensagem" style="padding-left:4px" /></td>
  </tr>
</table>

A página users-online.php e writing.php, juntamente com chat.php só são atualizadas quando o usuário envia uma nova mensagem.

Sair.php do Chat

Last but not least, temos o arquivo sair.php. Sua função é encerrar a sessão e redirecionar o usuário para a página de login. Veja o código abaixo:

1
2
3
4
5
6
<?php
//Destoi a sessão e redireciona o usuário para a página de início do chat.
session_start();
session_destroy();
header("Location: index.php");
?>
<?php
//Destoi a sessão e redireciona o usuário para a página de início do chat.
session_start();
session_destroy();
header("Location: index.php");
?>

Simulação de uma conversa

Veja no vídeo abaixo um chat onde João bate papo com Pedro na sala adolescentes:

Conclusão

Este script é apenas uma ideia, a partir dele você poderia criar sistemas de chat mais elaborados, com mais funções e mais bonitos, visualmente falando. Caso você tenha ficado com alguma dúvida sobre este script de chat em PHP, deixe aqui nos comentários que irei tentar te esclarecer. Acredito que os grandes sistemas de Chat, como por exemplo o chat do terra, tenha uma base muito parecida com este script.

Até o próximo artigo aqui no blog

One Response

  1. pedogo maio 18, 2019

Leave a Reply