🏝 If you like the content of this blog, I would really appreciate your vote in Tuple's Send an Open Source Developer on Vacation contest. Just search for "Freek Van der Herten" in the nominees list and cast your vote. 🙌

😀 This holiday won't only be a nice reward for me, but also for my girlfriend who always gives me a lot of time working on open source packages and blog posts.

A package to add comments to your Laravel app

Original – by Freek Van der Herten – 10 minute read

I'm proud to announce that our team has released a new premium package called Laravel Comments. Using this package, you can set up a comments section in your Laravel app in no time.

We've made a nice, errr I mean epic launch movie to get you in the right mood.

Laravel Comments includes a Livewire component to render comments. Here's what it looks like:

screenshot

Of course, we wrote extensive documentation, covering every aspect of Laravel Comments.

It comes with batteries included:

  • comments can be nested
  • emoji reactions
  • notifications to all participants when a new comment is posted
  • an inline approval flow for new comments
  • markdown editing and code highlighting
  • endlessly customisable

In this blog post, I'd like to introduce the package to you.

I'm using the package to allow comments on this very blog post you are reading. If you want to immediately see the package in action, head down to the comments. Feel free to say hi there 🙂

Why we built the package

A few weeks ago, we launched our latest course titled Writing Readable PHP. In that course, you get opinionated tips on what makes good code. We wanted to let course participants share their opinion with the other participants and us.

We looked for a commenting solution that worked out of the box. Unfortunately, we didn't find anything. All existing solutions either:

  • provide no UI
  • inject a criminal amount of JavaScript
  • look ugly
  • could not be integrated well into a Laravel app
  • don't respect your privacy

We decided to build our comments package that feels at home in any Laravel app. Here's what it looks like on our course page:

screenshot

While we were making the course, the package, which didn't contain any UI at the point, was available in a public repo for a short time. Because we were delighted with how well it worked, we decided to let several team members polish it and provide it as a paid premium package.

Exploring the comments component

Let's take the component for a spin! After you've followed all installation instructions, this is how you can display the comments component:

<livewire:comments :model="$post"/>

That model prop is mandatory; this is the model you're commenting on. In this case, it's a blog post.

This is what it looks like in the browser:

screenshot

No comments yet! Let's now type something there. The component accepts markdown, and it will even highlight code examples.

screenshot

The code highlighting is highlighted by our Shiki-php package and is capable of highlighting hundreds of languages.

Of course, when you've made a typo, you can, unlike on Twitter, still edit the comment.

screenshot

Next to comments, you can also react with an emoji to comments. In the package config file, you can specify which emojis are allowed. By default, we only enabled a few very positive ones. Here's what it looks like when a few people have reacted to a comment:

screenshot

Out of the box, the package also has an approval flow. If this optional feature is enabled, all comments made by non-admins will need to be approved. If a non-admin makes a new comment, a little message will be displayed that the comment isn't publicly displayed yet.

screenshot

Admins will get a mail that contains links to approve or reject the new comment directly.

screenshot

Alternatively, an admin can also view the comments and see the thread's approve/reject buttons inline.

screenshot

You like dark mode? We got that covered too!

screenshot

The package can also send notifications to anyone participating in commenting on a model. Let's assume that the admin approves the comment and adds a new one.

screenshot

In that case, User 1 will get a notification that a new reply was added.

screenshot

If a user doesn't want to get notified, they can opt-out of getting notifications via the small notification prefs menu in the top right corner of the comments component.

screenshot

There are also various ways to customise the component. You can add a' read-only' attribute if you just want to show the comments, but don't let anyone make any notifications.

<livewire:comments read-only :model="$post"/>

This is what that looks like:

screenshot

By default, the component will allow two levels of comments (the second level is called "replies"). If you only want one level of comments, pass the no-replies attribute.

<livewire:comments no-replies :model="$post"/>

screenshot

The default behaviour is to order comments chronologically, so the newest are at the bottom. If you would like the newest ones to be on top, pass the newest-first attribute.

<livewire:comments newest-first read-only :model="$post"/>

Here's what that looks like:

screenshot

In the extensive documentation, you can learn more cool little features our component has. We've also created a demo application that showcases how you can use the package (note that you'll need to have a Laravel Comments license to run composer install).

Building your own UI

Under the hood, Laravel Comments consists of two separate packages. The spatie/laravel-livewire-comments contains the Livewire component. That Livewire component is built upon a headless package, called spatie/laravel-comments, which you’ll also get access to. That core package contains all models, notifications, and logic that can serve as the basis for a UI built on any front-end technology you like.

Anything you can with the Livewire component is also easily achievable using code.

Comments will always be associated with Eloquent models. To allow an Eloquent model to have comments associated with it, use the Spatie\Comments\HasComments trait on it.

use Illuminate\Database\Eloquent\Model;
use Spatie\Comments\Models\Concerns\HasComments;

class Post extends Model
{
    use HasComments;
}

Working with comments

You can call the comment method to create a new comment.

$post->comment("I've got a feeling");

Behind the scenes, a new Spatie\Comments\Comment model will be saved. The comment will be associated with the $post and the currently logged in user.

You can associate the comment with another user by passing it as a second argument.

$anotherUser = User::whereFirst('email', 'paul@beatles.com');

$post->comment("I've got a feeling", $anotherUser);

Because the Comment model itself uses the HasComments trait, you can create a nested comment by calling comment on a Comment.

$comment = $post->comment("I've got a feeling");

$nestedComment = $comment->comment("It keeps me on my toes");

You can use the commentator relationship to determine who commented.

$user = $comment->commentator;

Working with reactions

To create a reaction, call the reaction method on the comment.

$post->comment('Everybody pulled their socks up');

$comment->react('😍');

Behind the scenes, a Spatie\Comments\Reaction will be stored associated with the comment and the currently logged in user.

You can pass that user as a second argument to create a reaction for another user.

$anotherUser = User::whereFirst('email', 'john@beatles.com');

$comment->react('😍', $anotherUser);

A single user may have multiple reactions to a comment, but each reaction will be unique.

$post->comment('Everybody pulled their socks up');

$comment
    ->react('😍')
    ->react('👍')
    ->react('👍'); // will not be stored as 👍 was already added as a reaction by the current user;

You can retrieve all reactions by using a comment's reactions relation.

$comment->reactions;

The reactions will be returned in a Spatie\Comments\Models\Collections\ReactionCollection. That collection has a summary method that will return a summary of all reactions.

$summary = $comment->reaction->summary() // returns a Illuminate\Support\Collection;

The $summary will contain an item per unique reaction. Each item in $summary has these keys:

  • reaction: the reaction itself, e.g. 😍
  • count: the number of users that gave this reaction to the comment
  • commentator_reacted: a boolean that indicates whatever the currently logged in user gave this reaction

You can pass a User model to the summary method. The commentator_reacted in the return collection will be true when the given user has given that particular reaction.

Approving new comments

In the section above, you saw that the package can be configured so that admins need to approve new comments before they are displayed publicly.

All new comments will need to be approved if you set automatically_approve_all_comments in the comments config file to false.

Optionally, you can make this more fine-grained. Imagine that you only want to approve comments for users less than three months in your system.

To get started, you must use a custom Comment model. Create a class of your own and let it extend Spatie\Comments\Models\Comment.

use Spatie\Comments\Models\Comment;

class CustomComment extends Comment
{
    // ...
}

Next, you must register that custom comment class in the comments config file.

// config/comments.php

return [
    'models' => [
        'comment' => CustomComment::class
    ],
]

With that in place, you can now add a method shouldBeAutomaticallyApproved to the model that will contain the logic to determine if a comment should be approved.

use Spatie\Comments\Models\Comment;

class CustomComment extends Comment
{
    public function shouldBeAutomaticallyApproved(): bool
    {
   
        // $this->commentator is the user that created the comment
        if ($this->commentator->created_at->diffInMonths() < 3) {
            return false;
        }

        // automatically approve the comment is the user that
        // created the comment can also approve it
        return $this->getApprovingUsers()->contains($currentUser);
    }
}

Let's take a look at another example. You might want to only automatically approve comments if they don't contain specific words.

use Illuminate\Support\Str;
use Spatie\Comments\Models\Comment;

class CustomComment extends Comment
{
    public function shouldBeAutomaticallyApproved(): bool
    {
        if (Str::contains($this->original_text, [
            'bad-word', 
            'another-bad-word'
        ])) {
            return false;
        }
   
        return $this->getApprovingUsers()->contains($currentUser);
    }
}

You can see that we've set this up in a very flexible way. Various aspects of the package can be customised in a similar way. Check our docs to learn more.

In closing

We had a lot of fun building Laravel Comments. Again, we are amazed by how nice it feels building stuff powered by Livewire; it's a gift that keeps on giving.

If you want to know more about our Laravel Comment package, head over the promotional site, the docs, or the example app.

This isn't the first package that our team has built. On our company website, check out all our open source packages in this long list. If you want to support us, consider picking up Laravel Comments or any of our paid products.

What do you think about Laravel Comments? Let us know by... leaving a comment in the comments below! 🥁

Stay up to date with all things Laravel, PHP, and JavaScript.

Follow me on Twitter. I regularly tweet out programming tips, and what I myself have learned in ongoing projects.

Every month I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

avatar

love this! <3

👍 3
💅 1
avatar

Thank you for this great package and for all the effort in the php community

Want to join the conversation? Log in or create an account to post a comment.