MoonShine Commentable for MoonShine Laravel admin panel
dissnik/moonshine-commentable adds a comment field for MoonShine resources and a small comment domain for Eloquent models.
- PHP 8.1+
- MoonShine 4
- Laravel application with Eloquent models
Install the package:
composer require dissnik/moonshine-commentablePublish package files:
php artisan vendor:publish --tag=moonshine-commentable-migrations
php artisan vendor:publish --tag=moonshine-commentable-config
php artisan vendor:publish --tag=moonshine-commentable-lang
php artisan vendor:publish --tag=moonshine-commentable-assetsRun migrations:
php artisan migrateThe package creates two tables:
commentscomment_reads
Add the contract and trait to the model that should store comments:
<?php
namespace App\Models;
use DissNik\MoonShineCommentable\Contracts\CommentableContract;
use DissNik\MoonShineCommentable\Traits\HasComments;
use Illuminate\Database\Eloquent\Model;
class Post extends Model implements CommentableContract
{
use HasComments;
}The authenticated author should implement CommenterContract:
<?php
namespace App\Models;
use DissNik\MoonShineCommentable\Contracts\CommenterContract;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements CommenterContract
{
public function getCommenterName(): string
{
return $this->name;
}
public function getCommenterAvatar(): ?string
{
return $this->avatar_url;
}
}By default the package uses CommenterContract::getCommenterName() and getCommenterAvatar() for author presentation. You can also override this in config through the presenters.author_name and presenters.author_avatar callbacks.
Add the field to the resource that manages the commentable model:
<?php
namespace App\MoonShine\Resources;
use App\Models\Post;
use DissNik\MoonShineCommentable\Fields\Comment;
use MoonShine\Laravel\Resources\ModelResource;
class PostResource extends ModelResource
{
protected string $model = Post::class;
public function fields(): array
{
return [
// ...
Comment::make('Comments', 'comments'),
];
}
}Comments are shown after the main model is saved, because the field needs the record key and morph type.
The HasComments trait adds the following relations:
comments(): MorphManycommentReads(): MorphMany
You can create a comment from code:
$post->comment('First comment', $user);
$post->comment('Reply to comment #10', $user, 10);
$post->comment('Comment with payload', $user, payload: ['attachments' => 1]);The package no longer accepts the old self-passed signature such as comment($post, ...).
The default comment model stores:
commentable_idcommentable_typeauthor_idauthor_typeparent_idtextpayload
The package also stores read markers for each reader and commentable model.
Available methods from HasComments:
$post->markCommentsAsRead($user);
$post->hasUnreadComments($user);
$post->unreadCommentsCount($user);There are also query scopes:
Post::query()
->withUnreadCommentsState($user)
->withUnreadCommentsCount($user)
->get();By default these scopes expose:
has_unread_commentsunread_comments_count
You can pass custom column names as the second argument.
The published config file is config/moonshine-commentable.php.
return [
'models' => [
'comment' => DissNik\MoonShineCommentable\Models\Comment::class,
'comment_read' => DissNik\MoonShineCommentable\Models\CommentRead::class,
],
'policies' => [
'comment' => DissNik\MoonShineCommentable\Policies\CommentPolicy::class,
],
'commentables' => [
'resolver' => null,
'authorize' => null,
],
'presenters' => [
'author_name' => null,
'author_avatar' => null,
],
'moonshine' => [
'register_resource' => true,
'resource' => DissNik\MoonShineCommentable\Resources\CommentResource::class,
'pages' => [
'index' => DissNik\MoonShineCommentable\Resources\Pages\CommentIndexPage::class,
'form' => DissNik\MoonShineCommentable\Resources\Pages\CommentFormPage::class,
],
'events' => [
'comment_added' => 'moonshine-commentable:comment-added',
],
],
'ui' => [
'height' => '600px',
'threshold' => 200,
],
'transport' => [
'mode' => 'polling',
'signals' => [
'comment_created' => 'comment.created',
],
'payload' => [
'version' => 1,
],
'publisher' => null,
'polling' => [
'interval' => null,
],
],
];Use these keys to replace the default classes:
models.commentmodels.comment_readpolicies.commentcommentables.resolvercommentables.authorizepresenters.author_namepresenters.author_avatar
Use commentables.resolver to lock comment lookup to your own host models instead of trusting raw commentable_id and commentable_type request values.
Use commentables.authorize to decide whether the current actor may view, create, reply, update, or delete comments for a resolved host model.
If your host model implements CommentableAccessContract, the package can use that directly when no config callback is provided.
Use these keys to customize MoonShine integration:
moonshine.register_resourcemoonshine.resourcemoonshine.pages.indexmoonshine.pages.formmoonshine.events.comment_added
By default the package registers its CommentResource in MoonShine and keeps it out of the menu.
ui.heightcontrols the maximum height of the comments listui.thresholdcontrols the auto-scroll threshold
The transport section is intended for projects that want to publish comment events to their own infrastructure.
transport.modetransport.signals.comment_createdtransport.payload.versiontransport.publishertransport.polling.interval
If you want to publish comment events to WebSocket, broadcast, queue, or any other transport, implement CommentPublisherContract:
<?php
namespace App\Comments;
use DissNik\MoonShineCommentable\Contracts\CommentPayloadContract;
use DissNik\MoonShineCommentable\Contracts\CommentPublisherContract;
class RealtimeCommentPublisher implements CommentPublisherContract
{
public function publish(CommentPayloadContract $payload): void
{
// Publish $payload->toArray() to your transport
}
}Then register it in the config:
'transport' => [
'publisher' => App\Comments\RealtimeCommentPublisher::class,
],The payload contains:
- signal name
- payload version
- transport name
- comment data
- commentable model identifier
- author identifier
If you need a different resource or page implementation, point the config to your own classes:
'moonshine' => [
'resource' => App\MoonShine\Resources\CommentResource::class,
'pages' => [
'index' => App\MoonShine\Pages\CommentIndexPage::class,
'form' => App\MoonShine\Pages\CommentFormPage::class,
],
],The default policy allows:
- view: delegated to the configured commentable authorization seam
- create: allowed at the model policy layer, with host authorization enforced during comment creation
- reply: delegated to the configured commentable authorization seam
- update: only for the comment author and only when the host authorization seam allows it
- delete: only for the comment author and only when the host authorization seam allows it
Replace policies.comment if your application needs different rules.
The package includes:
- language files in
lang/enandlang/ru - published assets in
public/vendor/moonshine-commentable
Run the package tests with:
./vendor/bin/phpunit