Merge pull request #40 from toneflix-forks/master

Emit events and allow deleting replies when comments are deleted.
This commit is contained in:
Di
2024-10-11 18:41:00 +02:00
committed by GitHub
16 changed files with 263 additions and 71 deletions
+30 -4
View File
@@ -47,7 +47,7 @@ php artisan vendor:publish --provider="BeyondCode\Comments\CommentsServiceProvid
To let your models be able to receive comments, add the `HasComments` trait to the model classes. To let your models be able to receive comments, add the `HasComments` trait to the model classes.
``` php ```php
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@@ -146,10 +146,36 @@ $comment = BeyondCode\Comments\Comment::first();
$comment->commentAsUser($user, "Hey there!"); $comment->commentAsUser($user, "Hey there!");
``` ```
#### Deleting Replies
When you delete a comment, you may optionally want to delete all it's nested comments (replies). To do optionally enable this feature, set the `delete_replies_along_comments` config property in the `configs/comments.php` file to `true`.
### Events
When a new comment is added the `BeyondCode\Comments\Events\CommentAdded` event will be dispatched.
When a comment is deleted the `BeyondCode\Comments\Events\CommentDeleted` event will be dispatched.
you can listen to these event and do other things as your requirements call for.
_App\Providers\EventServiceProvider_
```php
use BeyondCode\Comments\Events\CommentAdded;
use BeyondCode\Comments\Events\CommentDeleted;
use App\Listeners\InformAdmin;
protected $listen = [
CommentAdded::class => [
InformAdmin::class,
],
CommentDeleted::class => [
InformAdmin::class,
],
];
```
### Testing ### Testing
``` bash ```bash
composer test composer test
``` ```
@@ -167,8 +193,8 @@ If you discover any security related issues, please email marcel@beyondco.de ins
## Credits ## Credits
- [Marcel Pociot](https://github.com/mpociot) - [Marcel Pociot](https://github.com/mpociot)
- [All Contributors](../../contributors) - [All Contributors](../../contributors)
## License ## License
+1 -1
View File
@@ -35,7 +35,7 @@
} }
}, },
"scripts": { "scripts": {
"test": "vendor/bin/phpunit", "test": "XDEBUG_MODE=coverage vendor/bin/phpunit",
"test-coverage": "vendor/bin/phpunit --coverage-html coverage" "test-coverage": "vendor/bin/phpunit --coverage-html coverage"
}, },
"config": { "config": {
+5 -1
View File
@@ -15,4 +15,8 @@ return [
*/ */
'user_model' => null, 'user_model' => null,
]; /**
* Determines if replies will be deleted when comments are deleted
*/
'delete_replies_along_comments' => false,
];
+24 -19
View File
@@ -1,28 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" <phpunit
backupGlobals="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupStaticAttributes="false" bootstrap="vendor/autoload.php"
colors="true" backupGlobals="false"
verbose="true" backupStaticAttributes="false"
convertErrorsToExceptions="true" colors="true"
convertNoticesToExceptions="true" verbose="true"
convertWarningsToExceptions="true" convertErrorsToExceptions="true"
processIsolation="false" convertNoticesToExceptions="true"
stopOnFailure="false"> convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<include>
<directory suffix=".php">src/</directory>
</include>
<report>
<clover outputFile="build/logs/clover.xml" />
<html outputDirectory="build/coverage" />
<text outputFile="build/coverage.txt" />
</report>
</coverage>
<testsuites> <testsuites>
<testsuite name="BeyondCode Test Suite"> <testsuite name="BeyondCode Test Suite">
<directory>tests</directory> <directory>tests</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<logging> <logging>
<log type="junit" target="build/report.junit.xml"/> <junit outputFile="build/report.junit.xml" />
<log type="coverage-html" target="build/coverage" />
<log type="coverage-text" target="build/coverage.txt"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging> </logging>
</phpunit> </phpunit>
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
bootstrap="vendor/autoload.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
verbose="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="BeyondCode Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<logging>
<log type="junit" target="build/report.junit.xml" />
<log type="coverage-html" target="build/coverage" />
<log type="coverage-text" target="build/coverage.txt" />
<log type="coverage-clover" target="build/logs/clover.xml" />
</logging>
</phpunit>
+26 -6
View File
@@ -2,9 +2,11 @@
namespace BeyondCode\Comments; namespace BeyondCode\Comments;
use BeyondCode\Comments\Events\CommentAdded;
use BeyondCode\Comments\Events\CommentDeleted;
use BeyondCode\Comments\Traits\HasComments;
use Exception; use Exception;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use BeyondCode\Comments\Traits\HasComments;
class Comment extends Model class Comment extends Model
{ {
@@ -13,13 +15,32 @@ class Comment extends Model
protected $fillable = [ protected $fillable = [
'comment', 'comment',
'user_id', 'user_id',
'is_approved' 'is_approved',
]; ];
protected $casts = [ protected $casts = [
'is_approved' => 'boolean' 'is_approved' => 'boolean',
]; ];
public static function boot(): void
{
parent::boot();
static::deleting(function (self $model) {
if (config('comments.delete_replies_along_comments')) {
$model->comments()->delete();
}
});
static::deleted(function (self $model) {
CommentDeleted::dispatch($model);
});
static::created(function (self $model) {
CommentAdded::dispatch($model);
});
}
public function scopeApproved($query) public function scopeApproved($query)
{ {
return $query->where('is_approved', true); return $query->where('is_approved', true);
@@ -43,7 +64,7 @@ class Comment extends Model
return $this; return $this;
} }
public function disapprove() public function disapprove()
{ {
$this->update([ $this->update([
@@ -59,11 +80,10 @@ class Comment extends Model
return config('comments.user_model'); return config('comments.user_model');
} }
if (!is_null(config('auth.providers.users.model'))) { if (! is_null(config('auth.providers.users.model'))) {
return config('auth.providers.users.model'); return config('auth.providers.users.model');
} }
throw new Exception('Could not determine the commentator model name.'); throw new Exception('Could not determine the commentator model name.');
} }
} }
-1
View File
@@ -16,7 +16,6 @@ class CommentsServiceProvider extends ServiceProvider
__DIR__.'/../config/config.php' => config_path('comments.php'), __DIR__.'/../config/config.php' => config_path('comments.php'),
], 'config'); ], 'config');
if (! class_exists('CreateCommentsTable')) { if (! class_exists('CreateCommentsTable')) {
$this->publishes([ $this->publishes([
__DIR__.'/../database/migrations/create_comments_table.php.stub' => database_path('migrations/'.date('Y_m_d_His', time()).'_create_comments_table.php'), __DIR__.'/../database/migrations/create_comments_table.php.stub' => database_path('migrations/'.date('Y_m_d_His', time()).'_create_comments_table.php'),
+3 -5
View File
@@ -2,14 +2,12 @@
namespace BeyondCode\Comments\Contracts; namespace BeyondCode\Comments\Contracts;
interface Commentator interface Commentator
{ {
/** /**
* Check if a comment for a specific model needs to be approved. * Check if a comment for a specific model needs to be approved.
* @param mixed $model *
* @return bool * @param mixed $model
*/ */
public function needsCommentApproval($model): bool; public function needsCommentApproval($model): bool;
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace BeyondCode\Comments\Events;
use BeyondCode\Comments\Comment;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CommentAdded implements ShouldDispatchAfterCommit
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public Comment $comment,
) {
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace BeyondCode\Comments\Events;
use BeyondCode\Comments\Comment;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CommentDeleted implements ShouldDispatchAfterCommit
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public Comment $comment,
) {
}
}
+3 -4
View File
@@ -2,16 +2,15 @@
namespace BeyondCode\Comments\Traits; namespace BeyondCode\Comments\Traits;
trait CanComment trait CanComment
{ {
/** /**
* Check if a comment for a specific model needs to be approved. * Check if a comment for a specific model needs to be approved.
* @param mixed $model *
* @return bool * @param mixed $model
*/ */
public function needsCommentApproval($model): bool public function needsCommentApproval($model): bool
{ {
return true; return true;
} }
} }
+2 -7
View File
@@ -2,9 +2,8 @@
namespace BeyondCode\Comments\Traits; namespace BeyondCode\Comments\Traits;
use Illuminate\Database\Eloquent\Model;
use BeyondCode\Comments\Contracts\Commentator; use BeyondCode\Comments\Contracts\Commentator;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
trait HasComments trait HasComments
@@ -22,7 +21,6 @@ trait HasComments
/** /**
* Attach a comment to this model. * Attach a comment to this model.
* *
* @param string $comment
* @return \Illuminate\Database\Eloquent\Model * @return \Illuminate\Database\Eloquent\Model
*/ */
public function comment(string $comment) public function comment(string $comment)
@@ -33,8 +31,6 @@ trait HasComments
/** /**
* Attach a comment to this model as a specific user. * Attach a comment to this model as a specific user.
* *
* @param Model|null $user
* @param string $comment
* @return \Illuminate\Database\Eloquent\Model * @return \Illuminate\Database\Eloquent\Model
*/ */
public function commentAsUser(?Model $user, string $comment) public function commentAsUser(?Model $user, string $comment)
@@ -51,5 +47,4 @@ trait HasComments
return $this->comments()->save($comment); return $this->comments()->save($comment);
} }
}
}
+77 -9
View File
@@ -2,9 +2,12 @@
namespace BeyondCode\Comments\Tests; namespace BeyondCode\Comments\Tests;
use BeyondCode\Comments\Events\CommentAdded;
use BeyondCode\Comments\Events\CommentDeleted;
use BeyondCode\Comments\Tests\Models\ApprovedUser; use BeyondCode\Comments\Tests\Models\ApprovedUser;
use BeyondCode\Comments\Tests\Models\Post; use BeyondCode\Comments\Tests\Models\Post;
use Illuminate\Foundation\Auth\User; use Illuminate\Foundation\Auth\User;
use Illuminate\Support\Facades\Event;
class CommentsTest extends TestCase class CommentsTest extends TestCase
{ {
@@ -12,7 +15,7 @@ class CommentsTest extends TestCase
public function users_without_commentator_interface_do_not_get_approved() public function users_without_commentator_interface_do_not_get_approved()
{ {
$post = Post::create([ $post = Post::create([
'title' => 'Some post' 'title' => 'Some post',
]); ]);
$post->comment('this is a comment'); $post->comment('this is a comment');
@@ -26,7 +29,7 @@ class CommentsTest extends TestCase
public function models_can_store_comments() public function models_can_store_comments()
{ {
$post = Post::create([ $post = Post::create([
'title' => 'Some post' 'title' => 'Some post',
]); ]);
$post->comment('this is a comment'); $post->comment('this is a comment');
@@ -42,7 +45,7 @@ class CommentsTest extends TestCase
public function comments_without_users_have_no_relation() public function comments_without_users_have_no_relation()
{ {
$post = Post::create([ $post = Post::create([
'title' => 'Some post' 'title' => 'Some post',
]); ]);
$comment = $post->comment('this is a comment'); $comment = $post->comment('this is a comment');
@@ -59,7 +62,7 @@ class CommentsTest extends TestCase
auth()->login($user); auth()->login($user);
$post = Post::create([ $post = Post::create([
'title' => 'Some post' 'title' => 'Some post',
]); ]);
$comment = $post->comment('this is a comment'); $comment = $post->comment('this is a comment');
@@ -73,7 +76,7 @@ class CommentsTest extends TestCase
$user = User::first(); $user = User::first();
$post = Post::create([ $post = Post::create([
'title' => 'Some post' 'title' => 'Some post',
]); ]);
$comment = $post->commentAsUser($user, 'this is a comment'); $comment = $post->commentAsUser($user, 'this is a comment');
@@ -87,7 +90,7 @@ class CommentsTest extends TestCase
$user = User::first(); $user = User::first();
$post = Post::create([ $post = Post::create([
'title' => 'Some post' 'title' => 'Some post',
]); ]);
$comment = $post->comment('this is a comment'); $comment = $post->comment('this is a comment');
@@ -105,7 +108,7 @@ class CommentsTest extends TestCase
$user = User::first(); $user = User::first();
$post = Post::create([ $post = Post::create([
'title' => 'Some post' 'title' => 'Some post',
]); ]);
$comment = $post->comment('this is a comment'); $comment = $post->comment('this is a comment');
@@ -120,7 +123,7 @@ class CommentsTest extends TestCase
$user = ApprovedUser::first(); $user = ApprovedUser::first();
$post = Post::create([ $post = Post::create([
'title' => 'Some post' 'title' => 'Some post',
]); ]);
$comment = $post->commentAsUser($user, 'this is a comment'); $comment = $post->commentAsUser($user, 'this is a comment');
@@ -134,7 +137,7 @@ class CommentsTest extends TestCase
$user = ApprovedUser::first(); $user = ApprovedUser::first();
$post = Post::create([ $post = Post::create([
'title' => 'Some post' 'title' => 'Some post',
]); ]);
$post->comment('this comment is not approved'); $post->comment('this comment is not approved');
@@ -145,4 +148,69 @@ class CommentsTest extends TestCase
$this->assertSame('this comment is approved', $post->comments()->approved()->first()->comment); $this->assertSame('this comment is approved', $post->comments()->approved()->first()->comment);
} }
/** @test */
public function comments_are_deleted_when_posts_are_deleted()
{
$post = Post::create([
'title' => 'Some post',
]);
$comment = $post->comment('this comment will be deleted');
$post->delete();
$this->assertFalse($comment->exists());
}
/** @test */
public function replies_are_deleted_when_post_comments_are_deleted()
{
config(['comments.delete_replies_along_comments' => true]);
$post = Post::create([
'title' => 'Some post',
]);
$comment = $post->comment('this comment will be deleted');
$reply = $comment->comment('this comment will be deleted too');
$comment->delete();
$this->assertFalse($reply->exists());
}
/** @test */
public function comment_added_event_is_dispatched_when_comment_is_created()
{
Event::fake([CommentAdded::class]);
$post = Post::create([
'title' => 'Some post',
]);
$comment = $post->comment('this comment is added');
Event::assertDispatched(CommentAdded::class, function ($event) use ($comment) {
return $event->comment->is($comment);
});
}
/** @test */
public function comment_deleted_event_is_dispatched_when_comment_is_deleted()
{
Event::fake([CommentDeleted::class]);
$post = Post::create([
'title' => 'Some post',
]);
$comment = $post->comment('this comment is added');
$comment->delete();
Event::assertDispatched(CommentDeleted::class, function ($event) use ($comment) {
return $event->comment->is($comment);
});
}
} }
+4 -6
View File
@@ -2,22 +2,20 @@
namespace BeyondCode\Comments\Tests\Models; namespace BeyondCode\Comments\Tests\Models;
use Illuminate\Foundation\Auth\User;
use BeyondCode\Comments\Contracts\Commentator; use BeyondCode\Comments\Contracts\Commentator;
use Illuminate\Foundation\Auth\User;
class ApprovedUser extends User implements Commentator class ApprovedUser extends User implements Commentator
{ {
protected $table = 'users'; protected $table = 'users';
/** /**
* Check if a comment for a specific model needs to be approved. * Check if a comment for a specific model needs to be approved.
* @param mixed $model *
* @return bool * @param mixed $model
*/ */
public function needsCommentApproval($model): bool public function needsCommentApproval($model): bool
{ {
return false; return false;
} }
}
}
+10 -1
View File
@@ -10,4 +10,13 @@ class Post extends Model
use HasComments; use HasComments;
protected $guarded = []; protected $guarded = [];
}
public static function boot(): void
{
parent::boot();
static::deleting(function (self $model) {
$model->comments()->delete();
});
}
}
+5 -7
View File
@@ -2,13 +2,12 @@
namespace BeyondCode\Comments\Tests; namespace BeyondCode\Comments\Tests;
use Illuminate\Foundation\Auth\User;
use Illuminate\Database\Schema\Blueprint;
use BeyondCode\Comments\CommentsServiceProvider; use BeyondCode\Comments\CommentsServiceProvider;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Foundation\Auth\User;
abstract class TestCase extends \Orchestra\Testbench\TestCase abstract class TestCase extends \Orchestra\Testbench\TestCase
{ {
public function setUp(): void public function setUp(): void
{ {
parent::setUp(); parent::setUp();
@@ -38,7 +37,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
protected function setUpDatabase() protected function setUpDatabase()
{ {
include_once __DIR__ . '/../database/migrations/create_comments_table.php.stub'; include_once __DIR__.'/../database/migrations/create_comments_table.php.stub';
(new \CreateCommentsTable())->up(); (new \CreateCommentsTable())->up();
@@ -54,8 +53,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
User::forceCreate([ User::forceCreate([
'name' => 'User', 'name' => 'User',
'email' => 'user@email.com', 'email' => 'user@email.com',
'password' => 'test' 'password' => 'test',
]); ]);
} }
}
}