tmsperera / headless-chat
Integrate chat on Laravel applications.
Fund package maintenance!
Patreon
Requires
- php: ^8.2
- laravel/framework: ^12|^11
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^9.8
- phpunit/phpunit: ^11.5
README
A flexible, customizable and headless package designed to integrate chat functionality into Laravel applications.
Why Headless Chat?
- Models and database tables can be overridden
- Uses Actions resolved from Service Container
Contents
Installation
-
Install the package via composer.
composer require tmsperera/headless-chat
ℹ️ Package will automatically register itself.
-
Publish migrations and config using:
php artisan vendor:publish --tag=headless-chat
ℹ️ To publish only migrations:
php artisan vendor:publish --tag=headless-chat-migrations
ℹ️ To publish only configurations:
php artisan vendor:publish --tag=headless-chat-config
-
Run migrations.
php artisan migrate
-
Implement your "User" Model from Participant contract.
use TMSPerera\HeadlessChat\Contracts\Participant; class User extends Model implements Participant { ... }
ℹ️ Any Eloquent Model can be used as a Participant
-
Use Chatable trait in User Model.
use TMSPerera\HeadlessChat\Contracts\Participant; use TMSPerera\HeadlessChat\Traits\Chatable; class User extends Model implements Participant { use Chatable; ... }
Usage
Having Chatable trait inside the User model gives you important abilities. And also this package provides standalone Actions to use anywhere your application needs.
Feel free to refer source code to have a better understanding
Send a direct message
Sends a message using the chat package.
Example:
use TMSPerera\HeadlessChat\DataTransferObjects\MessageDto; $sender = User::query()->find(1); $recipient = User::query()->find(2); $message = $sender->createDirectMessage( recipient: $recipient, messageDto: new MessageDto( type: 'text', content: 'Hello!', metadata: [ 'foo' => 'bar' ], ), );
Signature:
/** * @throws InvalidParticipationException */ public function createDirectMessage( Participant $recipient, MessageDto $messageDto, ): Message;
Reply to a message
Headless Chat also supports message replies.
Example:
use TMSPerera\HeadlessChat\DataTransferObjects\MessageDto; $sender = User::query()->find(1); $message = $sender->conversations->messages->first(); $sender->createReplyMessage( parentMessage: $message, messageDto: new MessageDto( type: 'text', content: 'Reply Message', metadata: [ 'foo' => 'bar' ], ), );
Signature:
/** * @throws InvalidParticipationException */ public function createReplyMessage( Message $parentMessage, MessageDto $messageDto, ): Message;
Mark message as read
Marks a message as read.
Example:
$sender = User::query()->find(1); $message = $sender->conversations->messages->first(); $recipient = User::query()->find(2); $recipient->readMessage($message);
Signature:
/** * @throws ReadBySenderException * @throws InvalidParticipationException * @throws MessageAlreadyReadException */ public function readMessage(Message $message): ReadReceipt;
Delete a sent message
Delete a message sent by a Participant.
Example:
$sender = User::query()->find(1); $message = $sender->conversations->messages->first(); $sender->deleteSentMessage($message);
Signature:
/** * @throws InvalidParticipationException * @throws MessageOwnershipException */ public function deleteSentMessage(Message $message): void;
Get conversations
Conversations for a particular Participant can be accessed by an Eloquent Relationship.
Example:
$sender = User::query()->find(1); $sender->conversations;
Signature:
public function conversations(): BelongsToMany;
Get conversations with metrics
Just as conversations, you also can retrieve conversations with useful metrics using conversationsWithMetrics
relation.
Example:
$sender = User::query()->find(1); $sender->conversationsWithMetrics;
Signature:
public function conversationsWithMetrics(): BelongsToMany;
Returns
conversationsWithMetrics
will return a collection of Conversations with each Conversation including special attributes which are...
total_message_count
: Total messages for Conversationread_message_count
: Total read messages for Conversationunread_message_count
: Total unread messages for Conversationlatest_message_at
: Created at of the latest message for Conversation
Get unread conversation count
Returns count of unread conversations for a Participant.
Example:
$sender = User::query()->find(1); $sender->getUnreadConversationCount();
Signature:
public function getUnreadConversationCount(): int;
Group Messages
Headless Chat package is designed to support group chats. Here is how to create group chat...
Create a group conversation
use TMSPerera\HeadlessChat\HeadlessChat; $user1 = User::query()->find(1); $user2 = User::query()->find(1); $conversation = HeadlessChat::createConversation( participants: [$user1, $user2], conversationDto: new ConversationDTO( conversationType: ConversationType::GROUP, ), );
Signature:
/** * @throws ParticipationLimitExceededException */ public static function createConversation( array $participants, ConversationDto $conversationDto, ): Conversation;
Join a group conversation
$user3 = User::query()->find(3); $conversation = Conversation::query()->find(1); $participation = $user3->joinConversation($conversation);
Signature:
/** * @throws ParticipationLimitExceededException * @throws ParticipationAlreadyExistsException */ public function joinConversation( Conversation $conversation, array $participationMetadata = [], ): Participation;
Send message to a group conversation
use TMSPerera\HeadlessChat\DataTransferObjects\MessageDto; $sender = User::query()->find(1); $conversation = Conversation::query()->find(1); $message = $sender->createMessage( conversation: $conversation, messageDto: new MessageDto( type: 'text', content: 'Hello!', metadata: [ 'foo' => 'bar' ], ), );
Signature:
/** * @throws InvalidParticipationException */ public function createMessage( Conversation $conversation, MessageDto $messageDto, ): Message;
Advanced Usage
Override Models
Some applications may not be able to use the default database tables provided by Headless Chat package. In such cases you can swap the database tables or even models to be used by this package.
To swap a database table or model used in package follow the below steps:
-
Publish Headless Chat configurations using.
php artisan vendor:publish --tag=headless-chat-config
-
Modify the published migrations in
create_headless_chat_tables.php
to set custom database table name and foreign key constrains.// Schema::create('messages', function (Blueprint $table) { Schema::create('custom_messages', function (Blueprint $table) { ... }); Schema::create('read_receipts', function (Blueprint $table) { ... $table->foreignId('message_id') // ->constrained(table: 'messages', column: 'id'); ->constrained(table: 'custom_messages', column: 'id'); ... });
-
Create new custom Model extending from the Models defined in Headless Chat package.
use TMSPerera\HeadlessChat\Models\Message; class CustomMessage extends Message { protected $table = 'custom_messages'; }
⚠️ You need to keep relationship names same as in Headless Chat Models in order to function the package. However, the referenced column names of relationships can be modified as per your custom database table columns.
-
Modify
config/headless-chat.php
to point the new model.return [ 'models' => [ 'message' => App\Models\CustomMessage::class, ... ], ... ];
💡 Ultimately, the models are resolved from Laravel Service Container, so you can also override the Model class inside the register
method of your AppServiceProvider
instead of modifying or even publishing the headless-chat
config just as below.
namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Models\CustomMessage; use TMSPerera\HeadlessChat\Models\Message; class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->bind(Message::class, function ($app) { return $app->make(CustomMessage::class); }); } }
Override Actions
All actions used inside Headless Chat package are resolved from Laravel Service Container. If you ever need to modify the behaviour of any Action used in Headless Chat package, you can add a binding inside register
method of your AppServiceProvider
.
namespace App\Providers; use App\Actions\CustomSendDirectMessageAction; use Illuminate\Support\ServiceProvider; use TMSPerera\HeadlessChat\Actions\CreateDirectMessageAction; class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->bind(CreateDirectMessageAction::class, function ($app) { return $app->make(CustomSendDirectMessageAction::class); }); } }