В этой статье я расскажу про систему диалогов на php, которую я создал на одном из сервисов компании, где я работаю.

Изначально, хотелось получить систему, которая бы повторяла функционал диалоговой системы контакта. Требования были следующие: скрипт не должен требовать установки на сервер дополнительных средств (поддержка long-pool запросов, phpdemon, поддержки websocket и т.п), позволять создавать диалог неограниченного числа пользователей и работать на обычном ajax-post сообщении с сайтом.

Пощупайте Demo

Приступим

Для начала создадим структуру БД

Система диалогов на php как в контакте

Вот дамп

Когда один пользователь хочет написать сообщение другому, скрипт ищет подходящий диалог. Диалог, является подходящим, если в нем участвую те же лица. Т.е. если мы раньше писали этому человеку, и никого больше не подключали к этому диалогу, то он подходит. Если же подходящего диалога нет, то создаем новый. Диалог и пользователь связаны таблицей user_to_dialog. Когда пользователь посылает сообщение, оно записывается в табличку message. А информация о том, кому оно предназначено лежит в табличке message_to_user. По сути, эта таблица избыточна, так как у нас уже есть информация кому показывать сообщение исходя из данных user_to_dialog. Но мне было так удобно. Можете это изменить в своем форке.

Перейдем к коду.

Структура класса dialog


<?php
class dialog{
	public $utime = 0; // время по часовому поясу пользователя в UNIXTIME
	public $userid = 0;// id пользователя
	public $err = '';
	public $hash = ''; // hash диалога
	public $id = '';
	private $user_id_field = 'IDClient'; // название поля с id пользователя, необходимо для подключения 
	//к Вашему скрипту с уже созданной структурой пользователей
	private function create(){} // создание нового диалога
	public function find_suit_dialog($userlist = array()){} // поиск подходящего диалога
	function get_new_messages_cnt(){} // количество новых сообщений для пользователя
	function get_users_from_dialog(){} // список пользователей принадлежащих диалогу
	function get_user_dialogs( $start=0,$cnt = 10 ){} // вывод диалогов пользователя
	function get_messages_from_dialog($new=false,$reset_status = true){} // вывод сообщений из диалога
	function remove_users_from_dialog( $userlist = array() ){}
	function add_users_to_dialog( $userlist = array() ){} // добавить пользователя в диалог
	function send($msg,$intro = false){} // посылка сообщения в диалог
	function send_many_users( $msg,$userlist,$intro = false ){} // посылка сообщения нескольким пользователям
	public function delete_message( $messageid ){} // удаление сообщения
}

поле user_id_field нужно для того, чтобы пристроить скрипт к Вашей бд, в которой возможно уже есть система пользователей.

Полная реализация всех методов 

Как использовать

Создаем экземпляр класса dialog


<?php
$dialog = new dialog($db,time(),$userid,isset($_REQUEST['hash'])?$_REQUEST['hash']:'');

где $db инициализированный и подключенный экземпляр класса db, а $userid это id текущего пользователя.

Получить все диалоги пользователя


$dialogs = $xddialog->get_user_dialogs();
$out = '';
foreach($dialogs as $dg)
	$out.='
	<div class="dialog '.(!$dg['msg_status']?'newmsg':'').'">
		<div class="float_left">
			<span class="nikname"><a href="#" id="user_'.$dg['senderid'].'">'.$dg['sender_name'].'</a></span>
			<span class="message_time">'.date('H:i:s d/m/Y',$dg['public']).'</span>
			<div>'.$dg['message'].'</div>
		</div>
		<div class="float_right">
			<input  class="btn gotodialog" id="dialog_'.$dg['hash'].'" value="ПЕРЕЙТИ К ДИАЛОГУ"/>
		</div>
		<div class="clearex"></div>
	</div>';

получить все сообщения из текущего дилога


$cnt = $xddialog->get_new_messages_cnt();
$messages = (!$cnt)?array():$xddialog->get_messages_from_dialog();
$out = '';
foreach($messages as $msg)
	$out.='
	<div>
		<div class="float_left">
			<span class="nikname"><a href="#" id="user_'.$msg['senderid'].'">'.$msg['sender_name'].'</a></span>
			<span class="message_time">'.date('H:i:s d/m/Y',$msg['public']).'</span>
			<div>'.$msg['message'].'</div>
		</div>
		<div class="clearex"></div>
	</div>';

получить только новые сообщения 


$cnt = $xddialog->get_new_messages_cnt();
$messages = (!$cnt)?array():$xddialog->get_messages_from_dialog(true);

Отправка сообщения в диалог


$xddialog->send($_POST['message']);

Поиск подходящего диалога и добавление туда всех пользователей


$xddialog->find_suit_dialog(array($userid1,$userid2,$userid3,));
$xddialog->add_users_to_dialog(array($userid,$userid1,$userid2,$userid3,));

где $userid это id текущего пользователя, а $userid,$userid1,$userid2,$userid3, id пользователей с которыми будет вестись диалог

Протестировать систему можно скачав пример с гитхаба, либо на моем сайте в demo.

Разумеется, это мой очередной велосипед. 

Оставлять комментарии могут только зарегистрированные пользователи

Комментарии  

Александр13
# Александр13 03.06.2013 20:54
Как прикрутить данную систему к codeigniter?
Cadenzar
# Cadenzar 17.07.2013 22:24
У вас в файле /examples/js/dialog.js идет при загрузке вызов функции listenMe, которая находится в этом же файле и она отправляет Post-запрос к index.php к функции get_new_messages .... Ни в этом файле ни в одном другом этой функции нет оО , может вы забыли выложить какой то файл, который надо подключить дополнительно ? или xddialog.php не полный ?
Leroy
# Leroy 18.07.2013 00:44
да это просто ошибка примера. action=get_new_messages просто нужно чтобы попасть в блок кода
if(!isset($_GET['action'])){
...
}else{
//сюда
echo json_encode(get_messages(true));


чтобы получить все новые сообщения
televnoy
# televnoy 08.09.2013 04:34
Можете подсказать как в этом примере изменить, что бы ник не давался новый к новому ip, а писался один и тот же только впереди него просто новая цифра добавлялась, и не важно с какого компьютера ты сидишь, с каждым новым сообщением с любого компьютера, был новый ник с новой цифрой!

Например:

a1: bla bla

a2: bla bla

a3: bla bla

И так далее!
Leroy
# Leroy 08.09.2013 04:43
Как-то так, в index.php начиная с 7 строки


$db->insert('user',array('name'=>substr(md5(time()),0,8))); //устанавливаем случайное имя
$id = $db->insertid();
$db->update('user',array('name'=>'user'.$id,'id='.$id); // заменяем имя на user{номер}
setcookie('userid',$id,time()+3600*24*365,'/');
$add = true;
$_COOKIE['userid'] = $id;


А вообще это же всего лишь пример)
televnoy
# televnoy 08.09.2013 04:59
Ругается на 13 строчку почему-то!
televnoy
# televnoy 08.09.2013 05:01
Ну если полностью заменить на предыдущее то что там находилось, то это 9-ая строчка!

$db->update('user',array('name'=>'user'.$id­,'id ='.$id); // заменяем имя на ­user{номер}
televnoy
# televnoy 08.09.2013 05:10
Ай, Ай, Ай, ты забыл закрыть вторую скобку =)
televnoy
# televnoy 08.09.2013 05:17
И да, не помогло, ник у меня все еще один и тот же! Запускаю на Денвере, тестирую!
televnoy
# televnoy 08.09.2013 05:23
Вот такой ef2b022b! И ничего! Он постоянно такой, при обновлении страницы, при новом сообщении!
Leroy
# Leroy 08.09.2013 17:47
ну он сменится только при новом юзере
Leroy
# Leroy 08.09.2013 17:48
Должно быть так

$db->update('user',array('name'=>'user'.$id),'id='.$id); // заменяем имя на user{номер}
Евгений
# Евгений 12.09.2013 00:30
А как сделать счетчик всех оставленных комментариев ?
Женя
# Женя 11.09.2014 01:05
Спасибо за прекрасный скрипт уже реализовал у себя на проекте очень круто !
Джей
# Джей 26.02.2014 02:47
Как допилить скрипт чтоб реально можно было сделать Чат в виде диалога на сайте?
Джей
# Джей 26.02.2014 02:48
Как допилить Ваш скрипт чтоб организовать на сайте диалоги?

Спасибо заранее!
Игорь
# Игорь 18.04.2014 16:55
Это ведь легко сделать при помощи всего лишь двух таблиц, ну вы замудрили систему с диалогами))
Leroy
# Leroy 19.04.2014 01:32
это что за таблички такие?) юсеры, комменты? а как вы реализуете чтобы одно сообщение можно было нескольким пользователям отсылать? а как человека к разговору подключить?
andrey
# andrey 20.04.2014 02:42
это можно было сделать одной таблицей в БД, ваша схема полный бред!
Leroy
# Leroy 25.04.2014 14:06
Аргументируйте
Михееще
# Михееще 25.07.2014 03:49
Да, тоже было бы интересно послушать, как это сделать одной таблицей или пустослов?
Андрей
# Андрей 21.07.2014 06:32
Здраствуйте,можете скинуть исходники на мыло ,а то не вдуплю как скачать?
Leroy
# Leroy 21.07.2014 11:36
Вот ссылка https://github.com/xdan/xddialogs/archive/master.zip
Андрей
# Андрей 21.07.2014 17:54
Спасибо
Алексей1989
# Алексей1989 30.08.2014 00:44
Наверное классные чат, но не могу врубиться, как его запустить (( Где вводить свои данные подключения к БД?
Александр14
# Александр14 22.11.2014 16:33
Система диалогов не логичная - полный бред! Некоторые функции, вообще мало понятны. Пробую реализовывать данный пример на своём сайте.

В итоге при отправке сообщений одному и тому же пользователю в БД каждый раз создаётся новый диалог?!

А всё из-за логики, а вернее всякое отсутствие логики.

Вот, например, запрос на поиск подходящего диалога (функция public function find_suit_dialog($userlist = array()) ...) выглядит следующим образом:



select count(utd.userid) as cnt,utd.dialogid,dg.hash from #_user_to_dialog as utd left join #_dialog as dg on dg.id=utd.dialogid

where utd.dialogid in (select dialogid from #_user_to_dialog where userid='.intval($userlist[0]).' and dialogid in(select dialogid from #_user_to_dialog where userid='.$this->userid.')) group by utd.dialogid;



Что переводится дословно по-русски: выбрать значения из таблицы то кому адресовано сообщение ГДЕ идентификатор диалога равен выборке идентификатора диалога из этой же таблицы. Это что за идиотизм? Выбрать самого себя что-ли. Или выбрать той записи, которой не существует?!
Leroy
# Leroy 27.11.2014 11:16
$userlist[0] и $this->userid это разные пользователи. данный запрос дергает все диалоги в которых эти два юзера пересекаются.

find_suit_dialog - метод который ищет подходящий диалог для юзера, если вы раньше писали чуваку, то берется он, но если в вашем диалоге учавствовал третий юзер, то этот диалог не подходит и создается новый.

говоря по русски: берем количество юзеров в диалоге, номер диалога и его хеш для каждого диалога где был задействован сам юзер и при этом был задействован его собеседник.

Все вроде верно, предложите свое написание подобного запроса, я внесу правки в исходники. Проект таки опенсорс и работает уже в куче проектов. Разумеется доработанный, когда писал эту статью он был сырой.
Александр14
# Александр14 22.11.2014 16:43
к тому же, как я понял, и как есть, в таблицу user_to_dialog добавляется информация о том, кому адресовано сообщение. К чему тогда в этой же функции вложенный запрос типа select dialogid from #_user_to_dialog where userid='.$this->userid.')) group by utd.dialogid; , если $this->userid это тот, кто отправляет сообщение. Вообще полный бред! Как тогда в одной и той же таблице которая предназначена для того, чтобы хранить информацию, о том, кому отправлено сообщение и в каком диалоге, может содержаться информация о том кто ещё и отправил сообщение???
Leroy
# Leroy 27.11.2014 11:19
в user_to_dialog содержится запись о том кто создал диалог. вся информация о том кому предназначено сообщение формируется из двух таблиц: сообщение ту диалог, юсер ту диалог. Берме диалог, дергаем все сообщения этого диалога, показываем все сообщения этого диалога конкретному юзеру который к этому диалогу относится. логика такая же как в ВК, вы бы разобрались сперва, прежде чем писать
Анатолий
# Анатолий 01.04.2015 19:29
Добрый вечер. помогите плз реализовать индивидуальную переписку пользователей. мучаюсь капэц как долго
Leroy
# Leroy 02.04.2015 08:10
Все исходники даны же. что-то не понятно?
Дмитрий5
# Дмитрий5 10.05.2015 09:12
Объясните подробнее как внедрить на сайт с уже имеющимися пользователями. Есть база и таблица с пользователями
scorpion
# scorpion 25.05.2015 15:09
ну вконтакте же не так реализовано, там не посылаются постоянно запросы, для проверки были ли новые сообщения или нет
Leroy
# Leroy 26.05.2015 08:28
ну как же не посылаются. longpull просто используется. Я не стал делать его на сервере.
Александр15
# Александр15 27.06.2015 10:09
Цитирую scorpion:
ну в контакте же не так реализовано, там не посылаются постоянно запросы, для проверки были ли новые сообщения или нет

Там логика такая же, но просто используется не Ajax для послания запросов к серверу, а используется серверный JS, например, Node.js. Если посылать так запросы каждую секунду серверу на то, нет ли там чего-нибудь новенького, то сервер упадёт при большом количестве одновременно подключенных пользователей.
Антонио
# Антонио 29.05.2015 14:23
Чего то не работает, не хочет подключаться к базе, или как правильно указать подключение к базе в class.db.php? Можете это как то пояснить в тексте?
Антонио
# Антонио 29.05.2015 14:24
Цитирую Антонио:
Чего то не работает, не хочет подключаться к базе, или как правильно указать подключение к базе в class.db.php? Можете это как то пояснить в тексте?

Это я про ваш пример на гитхабе!
Александр28
# Александр28 14.07.2015 13:00
Много ошибок и недоработок в системе диалогов!
Например, первое: строка 154 у Вас
$this->db->getRows('select userid from #_user_to_dialog where dialogid='.$this->id); // под вопросом, нужно ли добавлять отправителю сообщение
ответ: нет, не добавлять! Зачем добавлять информацию для отправителя? Чтобы при подсчёте новых сообщений для него же самого его же сообщение отображалось и считалось, как новое!

2 пример: строка 67 у Вас
if( !$this->id and (!$this->hash or !($this->id = $this->db->exists('dialog',$this->db->escape($this ->hash),'hash','','id'))) )

Для чего результат работы функции сохранять в атрибут класса?

3 пример: самое главное!!!
при поиске диалога 69 строка
$dlgs = $this->db->getRows('select count(utd.userid) as cnt,utd.dialogid,dg.hash from #_user_to_dialog as utd left join #_dialog as dg on dg.id=utd.dialogid where utd.dialogid in (select dialogid from #_user_to_dialog where userid='.intval($userlist[0]).' and dialogid in(select dialogid from #_user_to_dialog where userid='.$this->userid.')) group by utd.dialogid;');

диалог найден если id первого пользователя из массива userlist существует в таблице и при этом в этой же таблице существует id пользователя который отправляет сообщение.
Но!!! Что если, пользователь с id 1 первый раз отправил сообщение пользователям с id 2,3,4, создался диалог с id, скажем 8.В следующий раз тот же пользователь с id номер 1 отправил новое сообщение, только уже пользователям с id 2,5,7,10. Должен создаться новый диалог, так как списки совершенно разные! Но не тут то было!!! Найдя в первой ячейке userlist id пользователя под номером, 2 скрипт посчитает, что диалог есть, и пользователей 2,5,7,10 добавит в диалог 8.
Это неправильно, неккоректно и нарушает конфиденциальность переписки. Посути пользователи с id 5,7,10 смогут читать переписку пользователей с id 2,3,4 в диалоге с id 8.
Кому нужна помощь обращайтесь: http://vk.com/id60635284
Сергей12314123
# Сергей12314123 29.02.2016 14:13
Мое мнение, очень и очень фиговая система, как для "боевого" чата. Сообщения приходят с задержкой 1-2s, неуважительно, как для пользователя, может нарушить диалог. Да и вообще, делать чат на ajax.. вы не переживаете за нагрузку на сервер, или что его просто "заDDOSят", да и расширять такое ajax приложение это "яма", как для программиста, так и для сервера, который будет не просыхать от регулярных множественных соединений, их обработку и выдачу ответа.
Leroy
# Leroy 29.02.2016 15:38
задача номер один - чтобы этот скрипт завелся на хостинге за 100 рублей в месяц. Такой хостинг у тех кто не подвергается ддосу и у кого людей в чате единицы. Те кто может себе позволить лонгпулы и вебсокеты и выделенные сервера с демонами, такие статьи не читают и такой скрипт ясное дело использовать не будут.
Можете сходу назвать технологию которая бы позволила завести этот чат на подавляющем количестве серверов?
А про код, так он писался сугубо в академических целях и применялся всего один раз на практике в развивающемся проекте, в котором был с успехом заменен когда пришел народ и соответственно деньги.
Я его не дорабатываю. Все лежит на гитхабе, почему бы вам не прислать мне пару пул реквестов к скрипту и сделать его лучше. А не голословно кричать что скрипт плох
Денис Катриченко
# Денис Катриченко 26.05.2016 01:50
Структура не очень удачная. Больше интересует как выловить момент отправки сообщения на другой стороне, не отправляя при этом ежесекундные аякс запросы, на сервер со стороны отправителя.
Leroy
# Leroy 26.05.2016 07:31
Возможно. Проект на гитхабе, присылайте свою. А ответов может быть много: websocets, longpool
Самый дешевый, и реально работающий во всех браузерах это конечно longpool
Максимка
# Максимка 06.07.2016 21:07
Просто великолепная штука!!!!
Только вот никак не могу его приделать к блогу на wordpress. Может кто подскажет что и где надо переписать, что бы был возможен обмен сообщениями между зарегистрированными пользователями? Только за ранее предупреждаю в php я ни черта не понимаю так что если возможно, то конкретней)
Alex777
# Alex777 02.01.2018 16:10
Спасибо за хороший, продуманный механизм - сэкономил кучу времени =)
С наступившим!
Test6556
# Test6556 14.11.2019 19:08
https://github.com/xdan/class.db.php/blob/master/class.db.php

строка 31 private $_logid = 0; // если указать 1 то будет лошировать всеоперации