Create a Chat application for your multiple Models
What to learn how to make a package like this? https://leanpub.com/laravel-package-development
- Introduction
- Installation
- Usage
- Adding the ability to participate to a Model
- Enable the routes
- Get participant details
- Creating a conversation
- Get a conversation by Id
- Update conversation details
- Send a text message
- Send a message of custom type
- Get a message by id
- Get message sender
- Mark a message as read
- Mark whole conversation as read
- Unread messages count
- Delete a message
- Cleanup Deleted Messages
- Clear a conversation
- Get participant conversations
- Get a conversation between two participants
- Get common conversations among participants
- Remove participants from a conversation
- Add participants to a conversation
- Get messages in a conversation
- Get messages with cursor pagination
- Get public conversations for discovery
- Get recent messages
- Get participants in a conversation
- Get participation entry for a Model in a conversation
- Update participation settings
- Data Transformers
- Message Encryption
- License
Checkout a simple Demo Application
This package allows you to add a chat system to your Laravel ^5.4 application
From the command line, run:
composer require musonza/chat
Publish the assets:
php artisan vendor:publish --provider="Musonza\Chat\ChatServiceProvider"
This will publish database migrations and a configuration file musonza_chat.php in the Laravel config folder.
See musonza_chat.php for configuration
Run the migrations:
php artisan migrate
You can mix Models as participants. For instance you can have Parents, Students and Professors models communicating
Add the Musonza\Chat\Traits\Messageable trait to any Model you want to participate in Conversations
For example, let's say we want out Bot model to chat with other Models:
use Illuminate\Database\Eloquent\Model;
use Musonza\Chat\Traits\Messageable;
class Bot extends Model
{
use Messageable;
}The package includes routes for conversations, conversation participants, and messaging. These routes are hidden by default. To enable them, change should_load_routes to true, but make sure to configure the middleware correctly.
Since we allow Models with data that differ in structure to chat, we may want a uniform way to represent the participant details in a uniform way.
You can get the details as follows:
$participantModel->getParticipantDetails();Assuming you have a column name for your model, this returns a default array ['name' => 'column_value']
You can however, customize this for your needs by adding an Eloquent Accessor that returns an array
with as much as you need to your model as follows:
public function getParticipantDetailsAttribute()
{
return [
'name' => $this->someValue,
'foo' => 'bar',
];
}You can start a conversation by passing an array of Models as participants
$participants = [$model1, $model2,..., $modelN];
$conversation = Chat::createConversation($participants);You may want to classify conversations as private or public
$participants = [$model1, $model2,..., $modelN];
// Create a private conversation
$conversation = Chat::createConversation($participants)->makePrivate();
// Create a public conversation
$conversation = Chat::createConversation($participants)->makePrivate(false);
// Create a direct message
// Make direct conversation after creation
$conversation = Chat::createConversation($participants)->makeDirect();
// Specify intent for direct conversation before creation
$conversation = Chat::makeDirect()->createConversation($participants);Note: You will not be able to add additional participants to a direct conversation. Additionally you can't remove a participant from a direct conversation.
$conversation = Chat::conversations()->getById($id);$data = ['title' => 'PHP Channel', 'description' => 'PHP Channel Description'];
$conversation->update(['data' => $data]);$message = Chat::message('Hello')
->from($model)
->to($conversation)
->send();The default message type is text. If you want to specify custom type you can call the type() function as below:
$message = Chat::message('http://example.com/img')
->type('image')
->from($model)
->to($conversation)
->send();Sometimes you might want to add details about a message. For example, when the message type is an attachment, and you want to add details such as attachment's filename, and attachment's file url, you can call the data() function and pass your data as an array.
$message = Chat::message('Attachment 1')
->type('attachment')
->data(['file_name' => 'post_image.jpg', 'file_url' => 'http://example.com/post_img.jpg'])
->from($model)
->to($conversation)
->send();$message = Chat::messages()->getById($id);$sendModel = $message->sender;Chat::message($message)->setParticipant($participantModel)->markRead();Chat::message($message)->setParticipant($participantModel)->toggleFlag();
Chat::message($message)->setParticipant($participantModel)->flagged(); // trueChat::conversation($conversation)->setParticipant($participantModel)->readAll();$unreadCount = Chat::messages()->setParticipant($participantModel)->unreadCount();Chat::conversation($conversation)->setParticipant($participantModel)->unreadCount();Chat::message($message)->setParticipant($participantModel)->delete();What to cleanup when all participants have deleted a $message or $conversation?
Listen for \Musonza\Chat\Eventing\AllParticipantsDeletedMessage and
\Musonza\Chat\Eventing\AllParticipantsClearedConversation
Chat::conversation($conversation)->setParticipant($participantModel)->clear();Chat::conversations()->setPaginationParams(['sorting' => 'desc'])
->setParticipant($participantModel)
->limit(1)
->page(1)
->get();$conversation = Chat::conversations()->between($participantModel1, $participantModel2);$conversations = Chat::conversations()->common($participants);$participants is an array of participant Models
/* removing one user */
Chat::conversation($conversation)->removeParticipants([$participantModel]);/* removing multiple participants */
Chat::conversation($conversation)->removeParticipants([$participantModel, $participantModel2,...,$participantModelN]);/* add one user */
Chat::conversation($conversation)->addParticipants([$participantModel]);/* add multiple participants */
Chat::conversation($conversation)->addParticipants([$participantModel, $participantModel2]);Chat::conversation($conversation)->setParticipant($participantModel)->getMessages()For real-time chat applications, cursor-based pagination is recommended over offset-based pagination. This prevents duplicate messages when new messages arrive between page loads.
// Get first page
$messages = Chat::conversation($conversation)
->setParticipant($participantModel)
->setCursorPaginationParams([
'perPage' => 25,
'sorting' => 'asc',
])
->getMessagesWithCursor();
// Get next page using cursor from previous response
$nextCursor = $messages->nextCursor()?->encode();
$moreMessages = Chat::conversation($conversation)
->setParticipant($participantModel)
->setCursorPaginationParams([
'perPage' => 25,
'sorting' => 'asc',
'cursor' => $nextCursor,
])
->getMessagesWithCursor();API Endpoint: GET /conversations/{id}/messages-cursor
Query parameters:
participant_id(required)participant_type(required)perPage(optional, default: 25)sorting(optional:ascordesc)cursor(optional - from previous response'snext_cursor)
The response includes next_cursor and prev_cursor for navigation.
// private conversations
$conversations = Chat::conversations()->setParticipant($participantModel)->isPrivate()->get();
// public conversations
$conversations = Chat::conversations()->setParticipant($participantModel)->isPrivate(false)->get();
// direct conversations / messages
$conversations = Chat::conversations()->setParticipant($participantModel)->isDirect()->get();
// all conversations
$conversations = Chat::conversations()->setParticipant($participantModel)->get();You can list public conversations without being a participant. This is useful for building discovery pages where users can browse and join public chat rooms (similar to Telegram public groups).
// Get all public conversations (no participant required)
$conversations = Chat::conversations()
->isPrivate(false)
->limit(10)
->page(1)
->get();Note: This only works for public conversations. Attempting to list private conversations without setting a participant will throw an
InvalidConversationListException.
$messages = Chat::conversations()->setParticipant($participantModel)->limit(25)->page(1)->get();There are a few ways you can achieve pagination
You can specify the limit and page as above using the respective functions or as below:
$paginated = Chat::conversations()->setParticipant($participant)
->setPaginationParams([
'page' => 3,
'perPage' => 10,
'sorting' => "desc",
'columns' => [
'*'
],
'pageName' => 'test'
])
->get();
You don't have to specify all the parameters. If you leave the parameters out, default values will be used.
$paginated above will return Illuminate\Pagination\LengthAwarePaginator
To get the conversations simply call $paginated->items()
Tip: For paginating messages in real-time chat applications, consider using cursor pagination instead. Cursor pagination prevents duplicate messages when new messages arrive between page loads.
$participants = $conversation->getParticipants();Chat::conversation($conversation)->getParticipation($model);Set Conversation settings for participant (example: mute_mentions, mute_conversation)
$settings = ['mute_mentions' => true];
Chat::conversation($conversation)
->getParticipation($this->alpha)
->update(['settings' => $settings]);Need to have more control on the data returned from the package routes? You can specify your own Model transformers and take advantage of Fractal.
All you need to do is specify the location of your transformers in the configuration
file musonza_chat.php as follows:
/**
* Model Transformers
*/
'transformers' => [
'conversation' => \MyApp\Transformers\ConversationTransformer::class,
'message' => \MyApp\Transformers\MessageTransformer::class,
'participant' => \MyApp\Transformers\ParticipantTransformer::class,
]Note: This only applies to responses from package routes.
You can optionally encrypt message bodies at rest using Laravel's built-in encryption. When enabled, messages are encrypted using AES-256-CBC via Laravel's Crypt facade.
To enable encryption, set the encrypt_messages option in your musonza_chat.php config file:
'encrypt_messages' => true,How it works:
- New messages are automatically encrypted before being stored in the database
- Messages are automatically decrypted when retrieved via the model
- Existing unencrypted messages remain readable (hybrid mode)
- An
is_encryptedcolumn tracks whether each message is encrypted
// Sending works the same - encryption is transparent
$message = Chat::message('Secret message')
->from($user)
->to($conversation)
->send();
// Reading works the same - decryption is automatic
echo $message->body; // "Secret message"Note: Encryption uses your application's
APP_KEY. If you change yourAPP_KEY, previously encrypted messages will become unreadable.
Chat is open-sourced software licensed under the MIT license