diff --git a/app/Helpers/HasAuthor.php b/app/Helpers/HasAuthor.php index c5b0bbab6..ef88c8b95 100644 --- a/app/Helpers/HasAuthor.php +++ b/app/Helpers/HasAuthor.php @@ -15,6 +15,8 @@ public function author(): User public function authoredBy(User $author) { $this->authorRelation()->associate($author); + + $this->unsetRelation('authorRelation'); } public function authorRelation(): BelongsTo diff --git a/app/Helpers/HasLikes.php b/app/Helpers/HasLikes.php index 04154653a..1ac544b85 100644 --- a/app/Helpers/HasLikes.php +++ b/app/Helpers/HasLikes.php @@ -8,35 +8,50 @@ trait HasLikes { + /** + * @return \Illuminate\Database\Eloquent\Collection + */ + public function likes() + { + return $this->likesRelation; + } + protected static function bootHasLikes() { static::deleting(function ($model) { - $model->likes()->delete(); + $model->likesRelation()->delete(); + + $model->unsetRelation('likesRelation'); }); } public function likedBy(User $user) { - $this->likes()->create(['user_id' => $user->id()]); + $this->likesRelation()->create(['user_id' => $user->id()]); + + $this->unsetRelation('likesRelation'); } public function dislikedBy(User $user) { - optional($this->likes()->where('user_id', $user->id())->first())->delete(); - } + optional($this->likesRelation()->where('user_id', $user->id())->first())->delete(); - public function likes(): MorphMany - { - return $this->morphMany(Like::class, 'likeable'); + $this->unsetRelation('likesRelation'); } - public function isLikedBy(User $user): bool + /** + * It's important to name the relationship the same as the method because otherwise + * eager loading of the polymorphic relationship will fail on queued jobs. + * + * @see https://github.com/laravelio/laravel.io/issues/350 + */ + public function likesRelation(): MorphMany { - return $this->likes()->where('user_id', $user->id())->exists(); + return $this->morphMany(Like::class, 'likesRelation', 'likeable_type', 'likeable_id'); } - public function likesCount(): int + public function isLikedBy(User $user): bool { - return $this->likes()->count(); + return $this->likesRelation()->where('user_id', $user->id())->exists(); } } diff --git a/app/Helpers/HasTags.php b/app/Helpers/HasTags.php index 9c3ba2d8a..fb2455384 100644 --- a/app/Helpers/HasTags.php +++ b/app/Helpers/HasTags.php @@ -22,20 +22,19 @@ public function syncTags(array $tags) { $this->save(); $this->tagsRelation()->sync($tags); + + $this->unsetRelation('tagsRelation'); } public function removeTags() { $this->tagsRelation()->detach(); + + $this->unsetRelation('tagsRelation'); } public function tagsRelation(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable')->withTimestamps(); } - - public function hasTags(): bool - { - return $this->tagsRelation()->count() > 0; - } } diff --git a/app/Helpers/ReceivesReplies.php b/app/Helpers/ReceivesReplies.php index dcf7b4213..c69e708c7 100644 --- a/app/Helpers/ReceivesReplies.php +++ b/app/Helpers/ReceivesReplies.php @@ -30,6 +30,8 @@ public function deleteReplies() foreach ($this->repliesRelation()->get() as $reply) { $reply->delete(); } + + $this->unsetRelation('repliesRelation'); } /** @@ -53,9 +55,4 @@ public function isConversationOld(): bool return $this->createdAt()->lt($sixMonthsAgo); } - - public function repliesCount(): int - { - return $this->repliesRelation()->count(); - } } diff --git a/app/Models/Article.php b/app/Models/Article.php index d06649eed..173434ee1 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -50,6 +50,15 @@ final class Article extends Model 'shared_at', ]; + /** + * {@inheritdoc} + */ + protected $with = [ + 'authorRelation', + 'likesRelation', + 'tagsRelation', + ]; + public function id(): int { return $this->id; @@ -242,17 +251,17 @@ public function scopeRecent(Builder $query): Builder public function scopePopular(Builder $query): Builder { - return $query->withCount('likes') - ->orderBy('likes_count', 'desc') + return $query->withCount('likesRelation') + ->orderBy('likes_relation_count', 'desc') ->orderBy('submitted_at', 'desc'); } public function scopeTrending(Builder $query): Builder { - return $query->withCount(['likes' => function ($query) { + return $query->withCount(['likesRelation' => function ($query) { $query->where('created_at', '>=', now()->subWeek()); }]) - ->orderBy('likes_count', 'desc') + ->orderBy('likes_relation_count', 'desc') ->orderBy('submitted_at', 'desc'); } diff --git a/app/Models/Reply.php b/app/Models/Reply.php index df143dbb4..a447175c7 100644 --- a/app/Models/Reply.php +++ b/app/Models/Reply.php @@ -37,7 +37,7 @@ final class Reply extends Model * {@inheritdoc} */ protected $with = [ - 'likes', + 'likesRelation', ]; public function id(): int diff --git a/app/Models/Thread.php b/app/Models/Thread.php index 67bacfcaa..9796fa2ce 100644 --- a/app/Models/Thread.php +++ b/app/Models/Thread.php @@ -59,6 +59,16 @@ final class Thread extends Model implements ReplyAble, SubscriptionAble, Feedabl 'subject', ]; + /** + * {@inheritdoc} + */ + protected $with = [ + 'authorRelation', + 'likesRelation', + 'repliesRelation', + 'tagsRelation', + ]; + public function id(): int { return $this->id; @@ -184,6 +194,7 @@ public static function feedQuery(): Builder { return static::with([ 'solutionReplyRelation', + 'likesRelation', 'repliesRelation', 'repliesRelation.authorRelation', 'tagsRelation', diff --git a/app/Models/User.php b/app/Models/User.php index bfcb155fb..1820d0b9b 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -214,20 +214,6 @@ public function series(): HasMany return $this->hasMany(Series::class, 'author_id'); } - /** - * @todo Make this work with Eloquent instead of a collection - */ - public function countSolutions(): int - { - return $this->replies()->filter(function (Reply $reply) { - if ($reply->replyAble() instanceof Thread) { - return $reply->replyAble()->isSolutionReply($reply); - } - - return false; - })->count(); - } - public static function findByUsername(string $username): self { return static::where('username', $username)->firstOrFail(); @@ -251,6 +237,20 @@ public function delete() parent::delete(); } + /** + * @todo Make this work with Eloquent instead of a collection + */ + public function countSolutions(): int + { + return $this->replies()->filter(function (Reply $reply) { + if ($reply->replyAble() instanceof Thread) { + return $reply->replyAble()->isSolutionReply($reply); + } + + return false; + })->count(); + } + public function scopeMostSolutions(Builder $query) { return $query->withCount(['replyAble as most_solutions' => function ($query) { diff --git a/database/migrations/2021_03_10_161050_add_index_to_replyable_type.php b/database/migrations/2021_03_10_161050_add_index_to_replyable_type.php new file mode 100644 index 000000000..51be699dc --- /dev/null +++ b/database/migrations/2021_03_10_161050_add_index_to_replyable_type.php @@ -0,0 +1,15 @@ +index('replyable_type'); + }); + } +} diff --git a/database/schema/mysql-schema.dump b/database/schema/mysql-schema.dump index 5caa97abd..643058465 100644 --- a/database/schema/mysql-schema.dump +++ b/database/schema/mysql-schema.dump @@ -145,6 +145,7 @@ CREATE TABLE `replies` ( PRIMARY KEY (`id`), KEY `replies_author_id_index` (`author_id`), KEY `replies_replyable_id_index` (`replyable_id`), + KEY `replies_replyable_type_index` (`replyable_type`), CONSTRAINT `replies_author_id_foreign` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; @@ -323,3 +324,4 @@ INSERT INTO `migrations` VALUES (55,'2020_07_16_185353_add_twitter_columns',1); INSERT INTO `migrations` VALUES (56,'2020_10_01_093001_add_email_verified_at_column_to_users',1); INSERT INTO `migrations` VALUES (57,'2020_11_03_205735_add_uuid_to_failed_jobs_table',1); INSERT INTO `migrations` VALUES (58,'2020_11_22_194212_create_schedule_monitor_tables',1); +INSERT INTO `migrations` VALUES (59,'2021_03_10_161050_add_index_to_replyable_type',2); diff --git a/resources/views/forum/_thread.blade.php b/resources/views/forum/_thread.blade.php index 78289e535..e254dd122 100644 --- a/resources/views/forum/_thread.blade.php +++ b/resources/views/forum/_thread.blade.php @@ -8,9 +8,9 @@
{{ $user->countThreads() }} {{ Str::plural('thread', $user->countThreads()) }}
+{{ $countedThreads = $user->countThreads() }} {{ Str::plural('thread', $countedThreads) }}
{{ $user->countReplies() }} {{ Str::plural('reply', $user->countReplies()) }}
+{{ $countedReplies = $user->countReplies() }} {{ Str::plural('reply', $countedReplies) }}
{{ $user->countSolutions() }} {{ Str::plural('solution', $user->countSolutions()) }}
+{{ $countedSolutions = $user->countSolutions() }} {{ Str::plural('solution', $countedSolutions) }}
{{ $user->countArticles() }} {{ Str::plural('article', $user->countArticles()) }}
+{{ $countedArticles = $user->countArticles() }} {{ Str::plural('article', $countedArticles) }}