<?php namespace ISMS\ChatBundle\Entity; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table */ class Chat { /** * @ORM\Id * @ORM\Column(type="bigint") * @ORM\GeneratedValue(strategy="AUTO") * * @var int */ private $id; /** * @var bool * * @ORM\Column(type="boolean") */ protected $isCompleted = false; /** * @ORM\OneToMany(targetEntity="ChatUser", mappedBy="Chat") * @var ArrayCollection */ private $users; /** * Constructor */ public function __construct() { $this->users = new ArrayCollection(); } /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Add users * * @param ChatUser $user * @return Chat */ public function addUser(ChatUser $user) { $this->users[] = $user; return $this; } /** * Remove users * * @param ChatUser $user */ public function removeUser(ChatUser $user) { $this->users->removeElement($user); } /** * Get users * * @return ArrayCollection|ChatUser[] */ public function getUsers() { return $this->users; } /** * @param boolean $isCompleted */ public function setIsCompleted($isCompleted) { $this->isCompleted = $isCompleted; } /** * @return boolean */ public function getIsCompleted() { return $this->isCompleted; } }
<?php namespace ISMS\ChatBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table */ class ChatUser { /** * @ORM\Id * @ORM\Column(type="bigint") * @ORM\GeneratedValue(strategy="AUTO") * * @var int */ private $id; /** * @ORM\Column(type="integer", unique=true) * * @var int */ private $rid; /** * @ORM\ManyToOne(targetEntity="Chat", inversedBy="users") * @ORM\JoinColumn(name="chat_id", referencedColumnName="id") * @var Chat */ private $Chat; /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set rid * * @param integer $rid * @return ChatUser */ public function setRid($rid) { $this->rid = $rid; return $this; } /** * Get rid * * @return string */ public function getRid() { return $this->rid; } /** * Set Chat * * @param Chat $chat * @return ChatUser */ public function setChat(Chat $chat = null) { $this->Chat = $chat; $chat->addUser($this); return $this; } /** * Get Chat * * @return Chat */ public function getChat() { return $this->Chat; } }
parameters: isms_chat.manager.class: ISMS\ChatBundle\Manager\ChatManager services: isms_chat.manager: class: %isms_chat.manager.class% arguments: [ @doctrine.orm.entity_manager ]
<?php namespace ISMS\ChatBundle\Manager; use Doctrine\Common\Persistence\ObjectManager; use ISMS\ChatBundle\Entity\Chat; use ISMS\ChatBundle\Entity\ChatUser; class ChatManager { /** @var ObjectManager */ private $em; public function __construct(ObjectManager $em) { $this->em = $em; } public function removeUserFromChat(ChatUser $user, Chat $chat) { if ($chat->getIsCompleted()) { $chat->removeUser($user); $chat->setIsCompleted(false); } else { $this->em->remove($chat); } $this->em->remove($user); $this->em->flush(); } public function findOrCreateChatForUser($rid) { $chat_user = new ChatUser(); $chat_user->setRid($rid); $chat = $this->getUncompletedChat(); if ($chat) { $chat->setIsCompleted(true); } else { $chat = new Chat(); } $chat_user->setChat($chat); $this->em->persist($chat); $this->em->persist($chat_user); $this->em->flush(); return $chat; } public function getChatByUser($rid) { $chat_user = $this->getUserByRid($rid); return $chat_user ? $chat_user->getChat() : null; } public function getUserByRid($rid) { return $this->em->getRepository('ISMSChatBundle:ChatUser')->findOneBy(['rid' => $rid]); } public function getUncompletedChat() { return $this->em->getRepository('ISMSChatBundle:Chat')->findOneBy(['isCompleted' => false]); } public function truncateChats() { /** @var \Doctrine\DBAL\Connection $conn */ $conn = $this->em->getConnection(); $platform = $conn->getDatabasePlatform(); $conn->query('SET FOREIGN_KEY_CHECKS=0'); $conn->executeUpdate($platform->getTruncateTableSQL('chat_user')); $conn->executeUpdate($platform->getTruncateTableSQL('chat')); $conn->query('SET FOREIGN_KEY_CHECKS=1'); } }
<?php namespace ISMS\ChatBundle\Chat; use ISMS\ChatBundle\Manager\ChatManager; use Ratchet\ConnectionInterface; use Ratchet\MessageComponentInterface; use Ratchet\WebSocket\Version\RFC6455\Connection; class Chat implements MessageComponentInterface { /** @var ConnectionInterface[] */ protected $clients = []; /** @var ChatManager */ protected $chm; public function __construct(ChatManager $chm) { $this->chm = $chm; $this->chm->truncateChats(); } /** * @param ConnectionInterface|Connection $conn * @return string */ private function getRid(ConnectionInterface $conn) { return $conn->resourceId; } /** * @param ConnectionInterface|Connection $conn */ function onOpen(ConnectionInterface $conn) { $this->clients[$this->getRid($conn)] = $conn; } function onClose(ConnectionInterface $conn) { $rid = array_search($conn, $this->clients); if ($user = $this->chm->getUserByRid($rid)) { $chat = $user->getChat(); $this->chm->removeUserFromChat($user, $chat); foreach ($chat->getUsers() as $user) { $this->clients[$user->getRid()]->close(); } } unset($this->clients[$rid]); } function onError(ConnectionInterface $conn, \Exception $e) { $conn->close(); } function onMessage(ConnectionInterface $from, $msg) { $msg = json_decode($msg, true); $rid = array_search($from, $this->clients); switch ($msg['type']) { case 'request': $chat = $this->chm->findOrCreateChatForUser($rid); if ($chat->getIsCompleted()) { $msg = json_encode(['type' => 'response']); foreach ($chat->getUsers() as $user) { $conn = $this->clients[$user->getRid()]; $conn->send($msg); } } break; case 'message': if ($chat = $this->chm->getChatByUser($rid)) { foreach ($chat->getUsers() as $user) { $conn = $this->clients[$user->getRid()]; $msg['from'] = $conn === $from ? 'me' : 'guest'; $conn->send(json_encode($msg)); } } break; } } }
<?php namespace ISMS\ChatBundle\Command; use ISMS\ChatBundle\Chat\Chat; use Ratchet\Http\HttpServer; use Ratchet\Server\IoServer; use Ratchet\WebSocket\WsServer; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Wrep\Daemonizable\Command\EndlessCommand; class DaemonCommand extends EndlessCommand implements ContainerAwareInterface { /** @var ContainerInterface */ private $container; public function setContainer(ContainerInterface $container = null) { $this->container = $container; } protected function configure() { $this->setName('isms:chat:daemon'); } protected function execute(InputInterface $input, OutputInterface $output) { $chm = $this->container->get('isms_chat.manager'); $server = IoServer::factory( new HttpServer( new WsServer( new Chat($chm) ) ), 8080 ); $server->run(); } }
<div id="chat_wrapper"> <div id="template_idle" class="template"> <div class="row text-center"> <div> <h3> !</h3> <p> , " " , </p> <p> , " " " ".</p> <p> . !</p> </div> <a class="btn btn-large btn-primary begin-chat"> </a> </div> </div> <div id="template_wait" class="template"> <div class="row text-center"> <h3><i class="fa fa-spin fa-refresh"></i> </h3> <span class="state"></span> </div> </div> <div id="template_chat" class="template"> <div class="row"> <div class="message_box" id="message_box"></div> </div> <div class="row well"> <form id="send-msg-form"> <div class="input-append"> <textarea id="message" rows="2" placeholder=" ( Ctrl + Enter)" required="required" class="span6"></textarea> <button id="send-btn" type="submit" class="btn btn-primary btn-large has-spinner"><span class="spinner"><i class="fa fa-spin fa-refresh"></i></span></button> </div> <div class="text-center"> <div class="show-chat"><a href="#" class="btn btn-danger close-chat"> </a></div> <div class="show-closed"> . <a href="#" class="btn btn-primary begin-chat"> </a></div> </div> </form> </div> </div> </div> <script type="text/javascript" src="{{ asset('bundles/ismschat/js/chat-widget.js') }}"></script> <script type="text/javascript"> $(document).ready(function(){ $('#chat_wrapper').chatWidget(); }); </script>
(function($) { $.fn.extend({chatWidget: function(options){ var o = jQuery.extend({ wsUri: 'ws://'+location.host+':8080', tmplClass: '.template', tmplIdle: '#template_idle', tmplWait: '#template_wait', tmplChat: '#template_chat', btnBeginChat: '.begin-chat', labelWaitState: '.state', messageBox: '#message_box', formSend: '#send-msg-form', textMessage: '#message', btnCloseChat: '.close-chat' },options); var websocket, fsm; var windowNotifier = function(){ var window_active = true, new_message = false; $(window).blur(function(){ window_active = false; }); $(window).focus(function(){ window_active = true; new_message = false; }); var original = document.title; window.setInterval(function() { if (new_message && window_active == false) { document.title = '******'; setTimeout(function(){ document.title = original; }, 750); } }, 1500); return { setNewMessage: function() { new_message = true; } }; } (); var initSocket = function() { websocket = new WebSocket(o.wsUri); websocket.onopen = function(e) { fsm.request(); }; websocket.onclose = function(e){ fsm.close(); }; websocket.onerror = function(e){ console.log(e); if (websocket.readyState == 1) { websocket.close(); } }; websocket.onmessage = function(e) { var msg = JSON.parse(e.data); switch (msg.type) { case 'response': fsm.response(); windowNotifier.setNewMessage(); break; case 'message': chatController.addMessage(msg); if (msg.from == 'me') { chatController.unspinChat(); } else { windowNotifier.setNewMessage(); } $(o.textMessage).focus(); break; } } }; var setView = function(tmpl) { $(o.tmplClass).removeClass('active'); $(tmpl).addClass('active'); }; var idleController = function() { $(o.btnBeginChat).click(function() { fsm.open(); }); return { show: function() { setView(o.tmplIdle); } }; } (); var waitController = function() { return { show: function(label) { $(o.labelWaitState).text(label); setView(o.tmplWait); } }; } (); var chatController = function() { $(o.textMessage).keydown(function (e) { if (e.ctrlKey && e.keyCode == 13) { $(o.formSend).trigger('submit'); } }); $(document).on('submit', o.formSend, function(e) { e.preventDefault(); var text = $(o.textMessage).val(); text = $.trim(text); if (!text) { return; } var msg = { type: 'message', message: text }; websocket.send(JSON.stringify(msg)); $(o.textMessage).val(''); chatController.spinChat(); }); $(o.btnCloseChat).click(function(e) { websocket.close(); }); var htmlForTextWithEmbeddedNewlines = function(text) { var htmls = []; var lines = text.split(/\n/); var tmpDiv = jQuery(document.createElement('div')); for (var i = 0 ; i < lines.length ; i++) { htmls.push(tmpDiv.text(lines[i]).html()); } return htmls.join("<br>"); }; return { clear: function() { $(o.messageBox).empty(); }, lockChat: function() { $(o.formSend).find(':input').attr('disabled', 'disabled'); }, unlockChat: function() { $(o.formSend).find(':input').removeAttr('disabled'); }, spinChat: function() { chatController.lockChat(); $(o.formSend).find('.btn').addClass('active'); }, unspinChat: function() { $(o.formSend).find('.btn').removeClass('active'); chatController.unlockChat(); }, showChat: function() { chatController.unlockChat(); $('.show-closed').hide(); $('.show-chat').show(); setView(o.tmplChat); }, showClosed: function() { chatController.lockChat(); $('.show-chat').hide(); $('.show-closed').show(); setView(o.tmplChat); }, addMessage: function(msg) { var d = new Date(); var text = htmlForTextWithEmbeddedNewlines(msg.message); $(o.messageBox).append( '<div>' + '<span class="user_name">'+msg.from+'</span> : <span class="user_message">'+text + '</span>' + '<span class="pull-right">'+d.toLocaleTimeString()+'</span>' + '</div>' ); $(o.messageBox).scrollTop($(o.messageBox)[0].scrollHeight); }, addSystemMessage: function(msg) { $(o.messageBox).append('<div class="system_msg">'+msg+'</div>'); } }; } (); fsm = StateMachine.create({ initial: 'idle', events: [ { name: 'open', from: ['idle', 'closed'], to: 'connecting' }, { name: 'request', from: 'connecting', to: 'waiting' }, { name: 'response', from: 'waiting', to: 'chat' }, { name: 'close', from: ['connecting', 'waiting'], to: 'idle' }, { name: 'close', from: 'chat', to: 'closed' } ], callbacks: { onidle: function(event, from, to) { idleController.show(); }, onconnecting: function(event, from, to) { waitController.show(' '); }, onwaiting: function(event, from, to) { waitController.show(' '); }, onchat: function(event, from, to) { chatController.showChat(); }, onclosed: function(event, from, to) { chatController.showClosed(); }, onopen: function(event, from, to) { initSocket(); }, onrequest: function (event, from, to) { var msg = { type: 'request' }; websocket.send(JSON.stringify(msg)); }, onresponse: function (event, from, to) { chatController.clear(); chatController.addSystemMessage(' - '); }, onclose: function (event, from, to) { chatController.addSystemMessage(' '); } } }); }}) })(jQuery);
Source: https://habr.com/ru/post/205934/
All Articles