From 69b22788b2806f5f8a6b09e61e9a416a62c5a8b8 Mon Sep 17 00:00:00 2001 From: blued_gear Date: Sun, 24 May 2026 14:58:35 +0000 Subject: [PATCH 1/5] approach with GROUP BY --- src/Controller/Entry/EntryFrontController.php | 13 +-- src/Repository/ContentRepository.php | 100 ++++++++++++------ 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/src/Controller/Entry/EntryFrontController.php b/src/Controller/Entry/EntryFrontController.php index 90f756e3d..e825fa848 100644 --- a/src/Controller/Entry/EntryFrontController.php +++ b/src/Controller/Entry/EntryFrontController.php @@ -65,9 +65,9 @@ public function front( $criteria->fetchCachedItems($this->sqlHelpers, $user); } - $cursorValue = $this->getCursorByCriteria($criteria->sortOption, $cursor); - $cursor2Value = $cursor2 ? $this->getCursorByCriteria(Criteria::SORT_NEW, $cursor2) : null; - $entities = $this->contentRepository->findByCriteriaCursored($criteria, $cursorValue, $cursor2Value); + //$cursorValue = $this->getCursorByCriteria($criteria->sortOption, $cursor); + //$cursor2Value = $cursor2 ? $this->getCursorByCriteria(Criteria::SORT_NEW, $cursor2) : null; + $entities = $this->contentRepository->findByCriteria($criteria); $templatePath = 'content/'; $dataKey = 'results'; @@ -136,9 +136,10 @@ public function magazine( if (null !== $user) { $criteria->fetchCachedItems($this->sqlHelpers, $user); } - $cursorValue = $this->getCursorByCriteria($criteria->sortOption, $cursor); - $cursor2Value = $cursor2 ? $this->getCursorByCriteria(Criteria::SORT_NEW, $cursor2) : null; - $results = $this->contentRepository->findByCriteriaCursored($criteria, $cursorValue, $cursor2Value); + #$cursorValue = $this->getCursorByCriteria($criteria->sortOption, $cursor); + #$cursor2Value = $cursor2 ? $this->getCursorByCriteria(Criteria::SORT_NEW, $cursor2) : null; + #$results = $this->contentRepository->findByCriteriaCursored($criteria, $cursorValue, $cursor2Value); + $results = $this->contentRepository->findByCriteria($criteria); return $this->renderResponse( $request, diff --git a/src/Repository/ContentRepository.php b/src/Repository/ContentRepository.php index a3d9703c4..3dd2619d4 100644 --- a/src/Repository/ContentRepository.php +++ b/src/Repository/ContentRepository.php @@ -82,8 +82,8 @@ public function findByCriteriaCursored(Criteria $criteria, mixed $currentCursor, $query['parameters'], $this->getSecondaryCursorWhereFromCriteria($criteria), $this->getSecondaryCursorWhereFromCriteriaInverted($criteria), - 'c.created_at DESC', - 'c.created_at', + 'sort_date DESC', + 'sort_date', transformer: $this->contentPopulationTransformer, ), $this->getCursorFieldFromCriteria($criteria), @@ -215,21 +215,21 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr $subClauseEntryComment = $repliesCommonWhere. (null === $criteria->cachedUserFollows ? - ' OR EXISTS (SELECT 1 FROM user_follow uf RIGHT OUTER JOIN entry_comment_vote v ON uf.following_id = v.user_id WHERE c.id = v.comment_id AND (uf.follower_id = :loggedInUser OR v.user_id = :loggedInUser) AND v.choice = 1)' : - ' OR EXISTS (SELECT 1 FROM entry_comment_vote v WHERE c.id = v.comment_id AND (v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser) AND v.choice = 1)'); + ' OR EXISTS (SELECT 1 FROM user_follow uf WHERE uf.follower_id = :loggedInUser AND uf.following_id = v.user_id) OR v.user_id = :loggedInUser' : + ' OR v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser'); $subClausePostComment = $repliesCommonWhere. (null === $criteria->cachedUserFollows ? - ' OR EXISTS (SELECT 1 FROM user_follow uf RIGHT OUTER JOIN post_comment_vote v ON uf.following_id = v.user_id WHERE c.id = v.comment_id AND (uf.follower_id = :loggedInUser OR v.user_id = :loggedInUser) AND v.choice = 1)' : - ' OR EXISTS (SELECT 1 FROM post_comment_vote v WHERE c.id = v.comment_id AND (v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser) AND v.choice = 1)'); + ' OR EXISTS (SELECT 1 FROM user_follow uf WHERE uf.follower_id = :loggedInUser AND uf.following_id = v.user_id) OR v.user_id = :loggedInUser' : + ' OR v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser'); $subClausePost = $subClausePost .(null === $criteria->cachedUserFollows ? - ' OR EXISTS (SELECT 1 FROM user_follow uf RIGHT OUTER JOIN post_vote v ON uf.following_id = v.user_id WHERE c.id = v.post_id AND (uf.follower_id = :loggedInUser OR v.user_id = :loggedInUser) AND v.choice = 1)' : - ' OR EXISTS (SELECT 1 FROM post_vote v WHERE c.id = v.post_id AND (v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser) AND v.choice = 1)'); + ' OR EXISTS (SELECT 1 FROM user_follow uf WHERE uf.follower_id = :loggedInUser AND uf.following_id = v.user_id) OR v.user_id = :loggedInUser' : + ' OR v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser'); $subClauseEntry = $subClauseEntry .(null === $criteria->cachedUserFollows ? - ' OR EXISTS (SELECT 1 FROM user_follow uf RIGHT OUTER JOIN entry_vote v ON uf.following_id = v.user_id WHERE c.id = v.entry_id AND (uf.follower_id = :loggedInUser OR v.user_id = :loggedInUser) AND v.choice = 1)' : - ' OR EXISTS (SELECT 1 FROM entry_vote v WHERE c.id = v.entry_id AND (v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser) AND v.choice = 1)'); + ' OR EXISTS (SELECT 1 FROM user_follow uf WHERE uf.follower_id = :loggedInUser AND uf.following_id = v.user_id) OR v.user_id = :loggedInUser' : + ' OR v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser'); } if (null !== $criteria->cachedUserSubscribedMagazines) { @@ -379,7 +379,7 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr $visibilityClauseM, $visibilityClauseC, $allClause, - $addCursor ? '%cursor% OR (%cursor2%)' : '', + $addCursor && !$criteria->includeBoosts ? '%cursor% OR (%cursor2%)' : '', $filterClauseEntry, ]); @@ -401,7 +401,7 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr $visibilityClauseM, $visibilityClauseC, $allClause, - $addCursor ? '%cursor% OR (%cursor2%)' : '', + $addCursor && !$criteria->includeBoosts ? '%cursor% OR (%cursor2%)' : '', $filterClausePost, ]); @@ -422,7 +422,7 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr $visibilityClauseM, $visibilityClauseC, $allClause, - $addCursor ? '%cursor% OR (%cursor2%)' : '', + $addCursor && !$criteria->includeBoosts ? '%cursor% OR (%cursor2%)' : '', $filterClauseComments, ]); @@ -444,7 +444,7 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr $visibilityClauseM, $visibilityClauseC, $allClause, - $addCursor ? '%cursor% OR (%cursor2%)' : '', + $addCursor && !$criteria->includeBoosts ? '%cursor% OR (%cursor2%)' : '', $filterClauseComments, ]); @@ -461,20 +461,54 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr // only join domain if we are explicitly looking at one $domainJoin = $criteria->domain ? 'LEFT JOIN domain d ON d.id = c.domain_id' : ''; - $entrySql = "SELECT c.id, 'entry' as type, c.type as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM entry c + if($criteria->includeBoosts) { + $boostJoinEntry = 'LEFT JOIN entry_vote v ON (c.id = v.entry_id AND v.choice = 1)'; + $boostJoinEntryComment = 'LEFT JOIN entry_comment_vote v ON (c.id = v.comment_id AND v.choice = 1)'; + $boostJoinPost = 'LEFT JOIN post_vote v ON (c.id = v.post_id AND v.choice = 1)'; + $boostJoinPostComment = 'LEFT JOIN post_comment_vote v ON (c.id = v.comment_id AND v.choice = 1)'; + + $sortDateSelect = 'greatest(max(c.created_at), max(v.created_at)) AS sort_date'; + } else { + $boostJoinEntry = ''; + $boostJoinEntryComment = ''; + $boostJoinPost = ''; + $boostJoinPostComment = ''; + + $sortDateSelect = 'c.created_at AS sort_date'; + } + + $entrySql = "SELECT c.id, 'entry' as type, c.type as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id, $sortDateSelect FROM entry c LEFT JOIN magazine m ON c.magazine_id = m.id $domainJoin + $boostJoinEntry $entryWhere"; - $postSql = "SELECT c.id, 'post' as type, 'microblog' as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM post c + $postSql = "SELECT c.id, 'post' as type, 'microblog' as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id, $sortDateSelect FROM post c LEFT JOIN magazine m ON c.magazine_id = m.id + $boostJoinPost $postWhere"; - $entryCommentSql = "SELECT c.id, 'entry_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM entry_comment c + $entryCommentSql = "SELECT c.id, 'entry_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id, $sortDateSelect FROM entry_comment c LEFT JOIN magazine m ON c.magazine_id = m.id + $boostJoinEntryComment $entryCommentWhere"; - $postCommentSql = "SELECT c.id, 'post_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM post_comment c + $postCommentSql = "SELECT c.id, 'post_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id, $sortDateSelect FROM post_comment c LEFT JOIN magazine m ON c.magazine_id = m.id + $boostJoinPostComment $postCommentWhere"; + if($criteria->includeBoosts) { + $entrySql = $entrySql.' GROUP BY c.id'; + $postSql = $postSql.' GROUP BY c.id'; + $entryCommentSql = $entryCommentSql.' GROUP BY c.id'; + $postCommentSql = $postCommentSql.' GROUP BY c.id'; + + if($addCursor) { + $entrySql = $entrySql.' WHERE %cursor% OR (%cursor2%)'; + $postSql = $postSql.' WHERE %cursor% OR (%cursor2%)'; + $entryCommentSql = $entryCommentSql.' WHERE %cursor% OR (%cursor2%)'; + $postCommentSql = $postCommentSql.' WHERE %cursor% OR (%cursor2%)'; + } + } + $innerLimit = $addCursor ? 'LIMIT :innerLimit' : ''; $innerSql = ''; if (Criteria::CONTENT_THREADS === $criteria->content) { @@ -523,7 +557,7 @@ private function getCursorFieldFromCriteria(Criteria $criteria): string Criteria::SORT_HOT => 'ranking', Criteria::SORT_COMMENTED => 'commentCount', Criteria::SORT_ACTIVE => 'lastActive', - default => 'createdAt', + default => 'sort_date', }; } @@ -534,8 +568,8 @@ private function getCursorWhereFromCriteria(Criteria $criteria): string Criteria::SORT_HOT => 'c.ranking < :cursor', Criteria::SORT_COMMENTED => 'c.comment_count < :cursor', Criteria::SORT_ACTIVE => 'c.last_active < :cursor', - Criteria::SORT_OLD => 'c.created_at > :cursor', - default => 'c.created_at < :cursor', + Criteria::SORT_OLD => 'sort_date > :cursor', + default => 'sort_date < :cursor', }; } @@ -546,18 +580,18 @@ private function getCursorWhereInvertedFromCriteria(Criteria $criteria): string Criteria::SORT_HOT => 'c.ranking > :cursor', Criteria::SORT_COMMENTED => 'c.comment_count > :cursor', Criteria::SORT_ACTIVE => 'c.last_active > :cursor', - Criteria::SORT_OLD => 'c.created_at < :cursor', - default => 'c.created_at >= :cursor', + Criteria::SORT_OLD => 'sort_date < :cursor', + default => 'sort_date >= :cursor', }; } private function getSecondaryCursorWhereFromCriteria(Criteria $criteria): string { return match ($criteria->sortOption) { - Criteria::SORT_TOP => 'c.score = :cursor AND c.created_at < :cursor2', - Criteria::SORT_HOT => 'c.ranking = :cursor AND c.created_at < :cursor2', - Criteria::SORT_COMMENTED => 'c.comment_count = :cursor AND c.created_at < :cursor2', - Criteria::SORT_ACTIVE => 'c.last_active = :cursor AND c.created_at < :cursor2', + Criteria::SORT_TOP => 'c.score = :cursor AND sort_date < :cursor2', + Criteria::SORT_HOT => 'c.ranking = :cursor AND sort_date < :cursor2', + Criteria::SORT_COMMENTED => 'c.comment_count = :cursor AND sort_date < :cursor2', + Criteria::SORT_ACTIVE => 'c.last_active = :cursor AND sort_date < :cursor2', default => 'FALSE', }; } @@ -565,10 +599,10 @@ private function getSecondaryCursorWhereFromCriteria(Criteria $criteria): string private function getSecondaryCursorWhereFromCriteriaInverted(Criteria $criteria): string { return match ($criteria->sortOption) { - Criteria::SORT_TOP => 'c.score = :cursor AND c.created_at >= :cursor2', - Criteria::SORT_HOT => 'c.ranking = :cursor AND c.created_at >= :cursor2', - Criteria::SORT_COMMENTED => 'c.comment_count = :cursor AND c.created_at >= :cursor2', - Criteria::SORT_ACTIVE => 'c.last_active = :cursor AND c.created_at >= :cursor2', + Criteria::SORT_TOP => 'c.score = :cursor AND sort_date >= :cursor2', + Criteria::SORT_HOT => 'c.ranking = :cursor AND sort_date >= :cursor2', + Criteria::SORT_COMMENTED => 'c.comment_count = :cursor AND sort_date >= :cursor2', + Criteria::SORT_ACTIVE => 'c.last_active = :cursor AND sort_date >= :cursor2', default => 'FALSE', }; } @@ -608,11 +642,11 @@ private function getOrderings(Criteria $criteria): array switch ($criteria->sortOption) { case Criteria::SORT_OLD: - $orderings[] = 'created_at ASC'; + $orderings[] = 'sort_date ASC'; break; case Criteria::SORT_NEW: default: - $orderings[] = 'created_at DESC'; + $orderings[] = 'sort_date DESC'; } return $orderings; From 6ecd528689ae16727aaffe612fb601ac5c5fb068 Mon Sep 17 00:00:00 2001 From: blued_gear Date: Tue, 26 May 2026 17:39:18 +0000 Subject: [PATCH 2/5] Revert "approach with GROUP BY" This reverts commit 69b22788b2806f5f8a6b09e61e9a416a62c5a8b8. --- src/Controller/Entry/EntryFrontController.php | 13 ++- src/Repository/ContentRepository.php | 100 ++++++------------ 2 files changed, 39 insertions(+), 74 deletions(-) diff --git a/src/Controller/Entry/EntryFrontController.php b/src/Controller/Entry/EntryFrontController.php index e825fa848..90f756e3d 100644 --- a/src/Controller/Entry/EntryFrontController.php +++ b/src/Controller/Entry/EntryFrontController.php @@ -65,9 +65,9 @@ public function front( $criteria->fetchCachedItems($this->sqlHelpers, $user); } - //$cursorValue = $this->getCursorByCriteria($criteria->sortOption, $cursor); - //$cursor2Value = $cursor2 ? $this->getCursorByCriteria(Criteria::SORT_NEW, $cursor2) : null; - $entities = $this->contentRepository->findByCriteria($criteria); + $cursorValue = $this->getCursorByCriteria($criteria->sortOption, $cursor); + $cursor2Value = $cursor2 ? $this->getCursorByCriteria(Criteria::SORT_NEW, $cursor2) : null; + $entities = $this->contentRepository->findByCriteriaCursored($criteria, $cursorValue, $cursor2Value); $templatePath = 'content/'; $dataKey = 'results'; @@ -136,10 +136,9 @@ public function magazine( if (null !== $user) { $criteria->fetchCachedItems($this->sqlHelpers, $user); } - #$cursorValue = $this->getCursorByCriteria($criteria->sortOption, $cursor); - #$cursor2Value = $cursor2 ? $this->getCursorByCriteria(Criteria::SORT_NEW, $cursor2) : null; - #$results = $this->contentRepository->findByCriteriaCursored($criteria, $cursorValue, $cursor2Value); - $results = $this->contentRepository->findByCriteria($criteria); + $cursorValue = $this->getCursorByCriteria($criteria->sortOption, $cursor); + $cursor2Value = $cursor2 ? $this->getCursorByCriteria(Criteria::SORT_NEW, $cursor2) : null; + $results = $this->contentRepository->findByCriteriaCursored($criteria, $cursorValue, $cursor2Value); return $this->renderResponse( $request, diff --git a/src/Repository/ContentRepository.php b/src/Repository/ContentRepository.php index 3dd2619d4..a3d9703c4 100644 --- a/src/Repository/ContentRepository.php +++ b/src/Repository/ContentRepository.php @@ -82,8 +82,8 @@ public function findByCriteriaCursored(Criteria $criteria, mixed $currentCursor, $query['parameters'], $this->getSecondaryCursorWhereFromCriteria($criteria), $this->getSecondaryCursorWhereFromCriteriaInverted($criteria), - 'sort_date DESC', - 'sort_date', + 'c.created_at DESC', + 'c.created_at', transformer: $this->contentPopulationTransformer, ), $this->getCursorFieldFromCriteria($criteria), @@ -215,21 +215,21 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr $subClauseEntryComment = $repliesCommonWhere. (null === $criteria->cachedUserFollows ? - ' OR EXISTS (SELECT 1 FROM user_follow uf WHERE uf.follower_id = :loggedInUser AND uf.following_id = v.user_id) OR v.user_id = :loggedInUser' : - ' OR v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser'); + ' OR EXISTS (SELECT 1 FROM user_follow uf RIGHT OUTER JOIN entry_comment_vote v ON uf.following_id = v.user_id WHERE c.id = v.comment_id AND (uf.follower_id = :loggedInUser OR v.user_id = :loggedInUser) AND v.choice = 1)' : + ' OR EXISTS (SELECT 1 FROM entry_comment_vote v WHERE c.id = v.comment_id AND (v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser) AND v.choice = 1)'); $subClausePostComment = $repliesCommonWhere. (null === $criteria->cachedUserFollows ? - ' OR EXISTS (SELECT 1 FROM user_follow uf WHERE uf.follower_id = :loggedInUser AND uf.following_id = v.user_id) OR v.user_id = :loggedInUser' : - ' OR v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser'); + ' OR EXISTS (SELECT 1 FROM user_follow uf RIGHT OUTER JOIN post_comment_vote v ON uf.following_id = v.user_id WHERE c.id = v.comment_id AND (uf.follower_id = :loggedInUser OR v.user_id = :loggedInUser) AND v.choice = 1)' : + ' OR EXISTS (SELECT 1 FROM post_comment_vote v WHERE c.id = v.comment_id AND (v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser) AND v.choice = 1)'); $subClausePost = $subClausePost .(null === $criteria->cachedUserFollows ? - ' OR EXISTS (SELECT 1 FROM user_follow uf WHERE uf.follower_id = :loggedInUser AND uf.following_id = v.user_id) OR v.user_id = :loggedInUser' : - ' OR v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser'); + ' OR EXISTS (SELECT 1 FROM user_follow uf RIGHT OUTER JOIN post_vote v ON uf.following_id = v.user_id WHERE c.id = v.post_id AND (uf.follower_id = :loggedInUser OR v.user_id = :loggedInUser) AND v.choice = 1)' : + ' OR EXISTS (SELECT 1 FROM post_vote v WHERE c.id = v.post_id AND (v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser) AND v.choice = 1)'); $subClauseEntry = $subClauseEntry .(null === $criteria->cachedUserFollows ? - ' OR EXISTS (SELECT 1 FROM user_follow uf WHERE uf.follower_id = :loggedInUser AND uf.following_id = v.user_id) OR v.user_id = :loggedInUser' : - ' OR v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser'); + ' OR EXISTS (SELECT 1 FROM user_follow uf RIGHT OUTER JOIN entry_vote v ON uf.following_id = v.user_id WHERE c.id = v.entry_id AND (uf.follower_id = :loggedInUser OR v.user_id = :loggedInUser) AND v.choice = 1)' : + ' OR EXISTS (SELECT 1 FROM entry_vote v WHERE c.id = v.entry_id AND (v.user_id IN (:cachedUserFollows) OR v.user_id = :loggedInUser) AND v.choice = 1)'); } if (null !== $criteria->cachedUserSubscribedMagazines) { @@ -379,7 +379,7 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr $visibilityClauseM, $visibilityClauseC, $allClause, - $addCursor && !$criteria->includeBoosts ? '%cursor% OR (%cursor2%)' : '', + $addCursor ? '%cursor% OR (%cursor2%)' : '', $filterClauseEntry, ]); @@ -401,7 +401,7 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr $visibilityClauseM, $visibilityClauseC, $allClause, - $addCursor && !$criteria->includeBoosts ? '%cursor% OR (%cursor2%)' : '', + $addCursor ? '%cursor% OR (%cursor2%)' : '', $filterClausePost, ]); @@ -422,7 +422,7 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr $visibilityClauseM, $visibilityClauseC, $allClause, - $addCursor && !$criteria->includeBoosts ? '%cursor% OR (%cursor2%)' : '', + $addCursor ? '%cursor% OR (%cursor2%)' : '', $filterClauseComments, ]); @@ -444,7 +444,7 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr $visibilityClauseM, $visibilityClauseC, $allClause, - $addCursor && !$criteria->includeBoosts ? '%cursor% OR (%cursor2%)' : '', + $addCursor ? '%cursor% OR (%cursor2%)' : '', $filterClauseComments, ]); @@ -461,54 +461,20 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr // only join domain if we are explicitly looking at one $domainJoin = $criteria->domain ? 'LEFT JOIN domain d ON d.id = c.domain_id' : ''; - if($criteria->includeBoosts) { - $boostJoinEntry = 'LEFT JOIN entry_vote v ON (c.id = v.entry_id AND v.choice = 1)'; - $boostJoinEntryComment = 'LEFT JOIN entry_comment_vote v ON (c.id = v.comment_id AND v.choice = 1)'; - $boostJoinPost = 'LEFT JOIN post_vote v ON (c.id = v.post_id AND v.choice = 1)'; - $boostJoinPostComment = 'LEFT JOIN post_comment_vote v ON (c.id = v.comment_id AND v.choice = 1)'; - - $sortDateSelect = 'greatest(max(c.created_at), max(v.created_at)) AS sort_date'; - } else { - $boostJoinEntry = ''; - $boostJoinEntryComment = ''; - $boostJoinPost = ''; - $boostJoinPostComment = ''; - - $sortDateSelect = 'c.created_at AS sort_date'; - } - - $entrySql = "SELECT c.id, 'entry' as type, c.type as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id, $sortDateSelect FROM entry c + $entrySql = "SELECT c.id, 'entry' as type, c.type as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM entry c LEFT JOIN magazine m ON c.magazine_id = m.id $domainJoin - $boostJoinEntry $entryWhere"; - $postSql = "SELECT c.id, 'post' as type, 'microblog' as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id, $sortDateSelect FROM post c + $postSql = "SELECT c.id, 'post' as type, 'microblog' as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM post c LEFT JOIN magazine m ON c.magazine_id = m.id - $boostJoinPost $postWhere"; - $entryCommentSql = "SELECT c.id, 'entry_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id, $sortDateSelect FROM entry_comment c + $entryCommentSql = "SELECT c.id, 'entry_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM entry_comment c LEFT JOIN magazine m ON c.magazine_id = m.id - $boostJoinEntryComment $entryCommentWhere"; - $postCommentSql = "SELECT c.id, 'post_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id, $sortDateSelect FROM post_comment c + $postCommentSql = "SELECT c.id, 'post_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM post_comment c LEFT JOIN magazine m ON c.magazine_id = m.id - $boostJoinPostComment $postCommentWhere"; - if($criteria->includeBoosts) { - $entrySql = $entrySql.' GROUP BY c.id'; - $postSql = $postSql.' GROUP BY c.id'; - $entryCommentSql = $entryCommentSql.' GROUP BY c.id'; - $postCommentSql = $postCommentSql.' GROUP BY c.id'; - - if($addCursor) { - $entrySql = $entrySql.' WHERE %cursor% OR (%cursor2%)'; - $postSql = $postSql.' WHERE %cursor% OR (%cursor2%)'; - $entryCommentSql = $entryCommentSql.' WHERE %cursor% OR (%cursor2%)'; - $postCommentSql = $postCommentSql.' WHERE %cursor% OR (%cursor2%)'; - } - } - $innerLimit = $addCursor ? 'LIMIT :innerLimit' : ''; $innerSql = ''; if (Criteria::CONTENT_THREADS === $criteria->content) { @@ -557,7 +523,7 @@ private function getCursorFieldFromCriteria(Criteria $criteria): string Criteria::SORT_HOT => 'ranking', Criteria::SORT_COMMENTED => 'commentCount', Criteria::SORT_ACTIVE => 'lastActive', - default => 'sort_date', + default => 'createdAt', }; } @@ -568,8 +534,8 @@ private function getCursorWhereFromCriteria(Criteria $criteria): string Criteria::SORT_HOT => 'c.ranking < :cursor', Criteria::SORT_COMMENTED => 'c.comment_count < :cursor', Criteria::SORT_ACTIVE => 'c.last_active < :cursor', - Criteria::SORT_OLD => 'sort_date > :cursor', - default => 'sort_date < :cursor', + Criteria::SORT_OLD => 'c.created_at > :cursor', + default => 'c.created_at < :cursor', }; } @@ -580,18 +546,18 @@ private function getCursorWhereInvertedFromCriteria(Criteria $criteria): string Criteria::SORT_HOT => 'c.ranking > :cursor', Criteria::SORT_COMMENTED => 'c.comment_count > :cursor', Criteria::SORT_ACTIVE => 'c.last_active > :cursor', - Criteria::SORT_OLD => 'sort_date < :cursor', - default => 'sort_date >= :cursor', + Criteria::SORT_OLD => 'c.created_at < :cursor', + default => 'c.created_at >= :cursor', }; } private function getSecondaryCursorWhereFromCriteria(Criteria $criteria): string { return match ($criteria->sortOption) { - Criteria::SORT_TOP => 'c.score = :cursor AND sort_date < :cursor2', - Criteria::SORT_HOT => 'c.ranking = :cursor AND sort_date < :cursor2', - Criteria::SORT_COMMENTED => 'c.comment_count = :cursor AND sort_date < :cursor2', - Criteria::SORT_ACTIVE => 'c.last_active = :cursor AND sort_date < :cursor2', + Criteria::SORT_TOP => 'c.score = :cursor AND c.created_at < :cursor2', + Criteria::SORT_HOT => 'c.ranking = :cursor AND c.created_at < :cursor2', + Criteria::SORT_COMMENTED => 'c.comment_count = :cursor AND c.created_at < :cursor2', + Criteria::SORT_ACTIVE => 'c.last_active = :cursor AND c.created_at < :cursor2', default => 'FALSE', }; } @@ -599,10 +565,10 @@ private function getSecondaryCursorWhereFromCriteria(Criteria $criteria): string private function getSecondaryCursorWhereFromCriteriaInverted(Criteria $criteria): string { return match ($criteria->sortOption) { - Criteria::SORT_TOP => 'c.score = :cursor AND sort_date >= :cursor2', - Criteria::SORT_HOT => 'c.ranking = :cursor AND sort_date >= :cursor2', - Criteria::SORT_COMMENTED => 'c.comment_count = :cursor AND sort_date >= :cursor2', - Criteria::SORT_ACTIVE => 'c.last_active = :cursor AND sort_date >= :cursor2', + Criteria::SORT_TOP => 'c.score = :cursor AND c.created_at >= :cursor2', + Criteria::SORT_HOT => 'c.ranking = :cursor AND c.created_at >= :cursor2', + Criteria::SORT_COMMENTED => 'c.comment_count = :cursor AND c.created_at >= :cursor2', + Criteria::SORT_ACTIVE => 'c.last_active = :cursor AND c.created_at >= :cursor2', default => 'FALSE', }; } @@ -642,11 +608,11 @@ private function getOrderings(Criteria $criteria): array switch ($criteria->sortOption) { case Criteria::SORT_OLD: - $orderings[] = 'sort_date ASC'; + $orderings[] = 'created_at ASC'; break; case Criteria::SORT_NEW: default: - $orderings[] = 'sort_date DESC'; + $orderings[] = 'created_at DESC'; } return $orderings; From 70d0056e1b92a801ba6ea5d482ff173a9704035c Mon Sep 17 00:00:00 2001 From: blued_gear Date: Tue, 26 May 2026 19:34:06 +0000 Subject: [PATCH 3/5] approach with extra column --- migrations/Version20260526175316.php | 51 +++++++++++++++++++++++ src/Entity/Contracts/VotableInterface.php | 2 + src/Entity/Entry.php | 4 +- src/Entity/EntryComment.php | 3 ++ src/Entity/Post.php | 3 ++ src/Entity/PostComment.php | 3 ++ src/Entity/Traits/VotableTrait.php | 10 +++++ src/Repository/ContentRepository.php | 22 +++++----- src/Service/VoteManager.php | 2 + 9 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 migrations/Version20260526175316.php diff --git a/migrations/Version20260526175316.php b/migrations/Version20260526175316.php new file mode 100644 index 000000000..b87d11d32 --- /dev/null +++ b/migrations/Version20260526175316.php @@ -0,0 +1,51 @@ +addSql('ALTER TABLE entry ADD last_boosted_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT to_timestamp(0)::date'); + $this->addSql('CREATE INDEX entry_last_boosted_at_idx ON entry (last_boosted_at)'); + $this->addSql('ALTER TABLE entry_comment ADD last_boosted_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT to_timestamp(0)::date'); + $this->addSql('CREATE INDEX entry_comment_last_boosted_at_idx ON entry_comment (last_boosted_at)'); + $this->addSql('ALTER TABLE post ADD last_boosted_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT to_timestamp(0)::date'); + $this->addSql('CREATE INDEX post_last_boosted_at_idx ON post (last_boosted_at)'); + $this->addSql('ALTER TABLE post_comment ADD last_boosted_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT to_timestamp(0)::date'); + $this->addSql('CREATE INDEX post_comment_last_boosted_at_idx ON post_comment (last_boosted_at)'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP INDEX entry_last_boosted_at_idx'); + $this->addSql('ALTER TABLE entry DROP last_boosted_at'); + $this->addSql('DROP INDEX entry_comment_last_boosted_at_idx'); + $this->addSql('ALTER TABLE entry_comment DROP last_boosted_at'); + $this->addSql('DROP INDEX post_last_boosted_at_idx'); + $this->addSql('ALTER TABLE post DROP last_boosted_at'); + $this->addSql('DROP INDEX post_comment_last_boosted_at_idx'); + $this->addSql('ALTER TABLE post_comment DROP last_boosted_at'); + } + + public function postUp(Schema $schema): void + { + $this->connection->transactional(function (): void { + $sqlTpl = 'UPDATE $e SET last_boosted_at = greatest((SELECT $e_vote.created_at FROM $e_vote WHERE $e_vote.$fk = $e.id ORDER BY $e_vote.created_at DESC LIMIT 1), created_at);'; + $this->connection->executeStatement(str_replace('$e', 'entry', str_replace('$fk', 'entry_id', $sqlTpl))); + $this->connection->executeStatement(str_replace('$e', 'entry_comment', str_replace('$fk', 'comment_id', $sqlTpl))); + $this->connection->executeStatement(str_replace('$e', 'post', str_replace('$fk', 'post_id', $sqlTpl))); + $this->connection->executeStatement(str_replace('$e', 'post_comment', str_replace('$fk', 'comment_id', $sqlTpl))); + }); + } +} diff --git a/src/Entity/Contracts/VotableInterface.php b/src/Entity/Contracts/VotableInterface.php index 42824bdb3..e99dd1a11 100644 --- a/src/Entity/Contracts/VotableInterface.php +++ b/src/Entity/Contracts/VotableInterface.php @@ -38,4 +38,6 @@ public function countVotes(): int; public function getUserChoice(User $user): int; public function getUserVote(User $user): ?Vote; + + public function updateLastBoostDate(): self; } diff --git a/src/Entity/Entry.php b/src/Entity/Entry.php index accef09f8..f0509052d 100644 --- a/src/Entity/Entry.php +++ b/src/Entity/Entry.php @@ -43,6 +43,7 @@ #[Index(columns: ['comment_count'], name: 'entry_comment_count_idx')] #[Index(columns: ['created_at'], name: 'entry_created_at_idx')] #[Index(columns: ['last_active'], name: 'entry_last_active_at_idx')] +#[Index(columns: ['last_boosted_at'], name: 'entry_last_boosted_at_idx')] #[Index(columns: ['body_ts'], name: 'entry_body_ts_idx')] #[Index(columns: ['title_ts'], name: 'entry_title_ts_idx')] class Entry implements VotableInterface, CommentInterface, DomainInterface, VisibilityInterface, RankingInterface, ReportInterface, FavouriteInterface, ActivityPubActivityInterface, ContentVisibilityInterface @@ -173,8 +174,9 @@ public function __construct( $user->addEntry($this); $this->createdAtTraitConstruct(); - $this->updateLastActive(); + + $this->lastBoostedAt = $this->createdAt; } public function updateLastActive(): void diff --git a/src/Entity/EntryComment.php b/src/Entity/EntryComment.php index c642b1137..05c1a35ef 100644 --- a/src/Entity/EntryComment.php +++ b/src/Entity/EntryComment.php @@ -37,6 +37,7 @@ #[Index(columns: ['up_votes'], name: 'entry_comment_up_votes_idx')] #[Index(columns: ['last_active'], name: 'entry_comment_last_active_at_idx')] #[Index(columns: ['created_at'], name: 'entry_comment_created_at_idx')] +#[Index(columns: ['last_boosted_at'], name: 'entry_comment_last_boosted_at_idx')] #[Index(columns: ['body_ts'], name: 'entry_comment_body_ts_idx')] class EntryComment implements VotableInterface, VisibilityInterface, ReportInterface, FavouriteInterface, ActivityPubActivityInterface { @@ -127,6 +128,8 @@ public function __construct( $this->createdAtTraitConstruct(); $this->updateLastActive(); + + $this->lastBoostedAt = $this->createdAt; } public function updateLastActive(): void diff --git a/src/Entity/Post.php b/src/Entity/Post.php index c7e1f0069..3d813d661 100644 --- a/src/Entity/Post.php +++ b/src/Entity/Post.php @@ -40,6 +40,7 @@ #[Index(columns: ['comment_count'], name: 'post_comment_count_idx')] #[Index(columns: ['created_at'], name: 'post_created_at_idx')] #[Index(columns: ['last_active'], name: 'post_last_active_at_idx')] +#[Index(columns: ['last_boosted_at'], name: 'post_last_boosted_at_idx')] #[Index(columns: ['body_ts'], name: 'post_body_ts_idx')] class Post implements VotableInterface, CommentInterface, VisibilityInterface, RankingInterface, ReportInterface, FavouriteInterface, ActivityPubActivityInterface { @@ -127,6 +128,8 @@ public function __construct( $this->createdAtTraitConstruct(); $this->updateLastActive(); + + $this->lastBoostedAt = $this->createdAt; } public function updateLastActive(): void diff --git a/src/Entity/PostComment.php b/src/Entity/PostComment.php index 2283beef7..16c87a562 100644 --- a/src/Entity/PostComment.php +++ b/src/Entity/PostComment.php @@ -37,6 +37,7 @@ #[Index(columns: ['up_votes'], name: 'post_comment_up_votes_idx')] #[Index(columns: ['last_active'], name: 'post_comment_last_active_at_idx')] #[Index(columns: ['created_at'], name: 'post_comment_created_at_idx')] +#[Index(columns: ['last_boosted_at'], name: 'post_comment_last_boosted_at_idx')] #[Index(columns: ['body_ts'], name: 'post_comment_body_ts_idx')] class PostComment implements VotableInterface, VisibilityInterface, ReportInterface, FavouriteInterface, ActivityPubActivityInterface { @@ -123,6 +124,8 @@ public function __construct(string $body, ?Post $post, User $user, ?PostComment $this->createdAtTraitConstruct(); $this->updateLastActive(); + + $this->lastBoostedAt = $this->createdAt; } public function updateLastActive(): void diff --git a/src/Entity/Traits/VotableTrait.php b/src/Entity/Traits/VotableTrait.php index f553604db..a51222cc3 100644 --- a/src/Entity/Traits/VotableTrait.php +++ b/src/Entity/Traits/VotableTrait.php @@ -28,6 +28,9 @@ trait VotableTrait #[Column(type: 'integer', nullable: true)] public ?int $apShareCount = null; + #[Column(type: 'datetimetz_immutable', nullable: false)] + public ?\DateTimeImmutable $lastBoostedAt; + public function countUpVotes(): int { return $this->apShareCount ?? $this->upVotes; @@ -66,6 +69,13 @@ public function updateVoteCounts(): self return $this; } + public function updateLastBoostDate(): self + { + $this->lastBoostedAt = new \DateTimeImmutable(); + + return $this; + } + public function getUpVotes(): Collection { $this->votes->get(-1); diff --git a/src/Repository/ContentRepository.php b/src/Repository/ContentRepository.php index a3d9703c4..19bf7fa3a 100644 --- a/src/Repository/ContentRepository.php +++ b/src/Repository/ContentRepository.php @@ -461,17 +461,17 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr // only join domain if we are explicitly looking at one $domainJoin = $criteria->domain ? 'LEFT JOIN domain d ON d.id = c.domain_id' : ''; - $entrySql = "SELECT c.id, 'entry' as type, c.type as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM entry c + $entrySql = "SELECT c.id, 'entry' as type, c.type as content_type, c.created_at, c.last_boosted_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM entry c LEFT JOIN magazine m ON c.magazine_id = m.id $domainJoin $entryWhere"; - $postSql = "SELECT c.id, 'post' as type, 'microblog' as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM post c + $postSql = "SELECT c.id, 'post' as type, 'microblog' as content_type, c.created_at, c.last_boosted_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM post c LEFT JOIN magazine m ON c.magazine_id = m.id $postWhere"; - $entryCommentSql = "SELECT c.id, 'entry_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM entry_comment c + $entryCommentSql = "SELECT c.id, 'entry_comment' as type, 'microblog' as content_type, c.created_at, c.last_boosted_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM entry_comment c LEFT JOIN magazine m ON c.magazine_id = m.id $entryCommentWhere"; - $postCommentSql = "SELECT c.id, 'post_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM post_comment c + $postCommentSql = "SELECT c.id, 'post_comment' as type, 'microblog' as content_type, c.created_at, c.last_boosted_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM post_comment c LEFT JOIN magazine m ON c.magazine_id = m.id $postCommentWhere"; @@ -523,7 +523,7 @@ private function getCursorFieldFromCriteria(Criteria $criteria): string Criteria::SORT_HOT => 'ranking', Criteria::SORT_COMMENTED => 'commentCount', Criteria::SORT_ACTIVE => 'lastActive', - default => 'createdAt', + default => $criteria->includeBoosts ? 'lastBoostedAt' : 'createdAt', }; } @@ -534,8 +534,8 @@ private function getCursorWhereFromCriteria(Criteria $criteria): string Criteria::SORT_HOT => 'c.ranking < :cursor', Criteria::SORT_COMMENTED => 'c.comment_count < :cursor', Criteria::SORT_ACTIVE => 'c.last_active < :cursor', - Criteria::SORT_OLD => 'c.created_at > :cursor', - default => 'c.created_at < :cursor', + Criteria::SORT_OLD => $criteria->includeBoosts ? 'c.last_boosted_at > :cursor' : 'c.created_at > :cursor', + default => $criteria->includeBoosts ? 'c.last_boosted_at < :cursor' : 'c.created_at < :cursor', }; } @@ -546,8 +546,8 @@ private function getCursorWhereInvertedFromCriteria(Criteria $criteria): string Criteria::SORT_HOT => 'c.ranking > :cursor', Criteria::SORT_COMMENTED => 'c.comment_count > :cursor', Criteria::SORT_ACTIVE => 'c.last_active > :cursor', - Criteria::SORT_OLD => 'c.created_at < :cursor', - default => 'c.created_at >= :cursor', + Criteria::SORT_OLD => $criteria->includeBoosts ? 'c.last_boosted_at < :cursor' : 'c.created_at < :cursor', + default => $criteria->includeBoosts ? 'c.last_boosted_at >= :cursor' : 'c.created_at >= :cursor', }; } @@ -608,11 +608,11 @@ private function getOrderings(Criteria $criteria): array switch ($criteria->sortOption) { case Criteria::SORT_OLD: - $orderings[] = 'created_at ASC'; + $orderings[] = $criteria->includeBoosts ? 'last_boosted_at ASC' : 'created_at ASC'; break; case Criteria::SORT_NEW: default: - $orderings[] = 'created_at DESC'; + $orderings[] = $criteria->includeBoosts ? 'last_boosted_at DESC' : 'created_at DESC'; } return $orderings; diff --git a/src/Service/VoteManager.php b/src/Service/VoteManager.php index 0be618a20..36f26eb9d 100644 --- a/src/Service/VoteManager.php +++ b/src/Service/VoteManager.php @@ -64,6 +64,7 @@ public function vote(int $choice, VotableInterface $votable, User $user, $rateLi if (VotableInterface::VOTE_UP === $choice && null !== $votable->apShareCount) { ++$votable->apShareCount; + $votable->updateLastBoostDate(); } elseif (VotableInterface::VOTE_DOWN === $choice && null !== $votable->apDislikeCount) { ++$votable->apDislikeCount; } @@ -132,6 +133,7 @@ public function upvote(VotableInterface $votable, User $user): Vote $votable->updateVoteCounts(); + $votable->updateLastBoostDate(); $votable->lastActive = new \DateTime(); if ($votable instanceof PostComment) { From 50264cf10bda89336712653f310959346b88a115 Mon Sep 17 00:00:00 2001 From: blued_gear Date: Tue, 9 Jun 2026 19:43:16 +0000 Subject: [PATCH 4/5] configure psalm --- psalm.xml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 psalm.xml diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 000000000..e9dedfbd5 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + From cf5a25dc37d7a7484845a3f49e240ae0a9b3aaa7 Mon Sep 17 00:00:00 2001 From: blued_gear Date: Tue, 9 Jun 2026 21:24:46 +0000 Subject: [PATCH 5/5] psalm --- psalm.xml | 1 + src/Entity/Contracts/VotableInterface.php | 3 +++ src/Entity/Entry.php | 1 + src/Entity/EntryComment.php | 1 + 4 files changed, 6 insertions(+) diff --git a/psalm.xml b/psalm.xml index e9dedfbd5..c7e8774aa 100644 --- a/psalm.xml +++ b/psalm.xml @@ -19,5 +19,6 @@ + diff --git a/src/Entity/Contracts/VotableInterface.php b/src/Entity/Contracts/VotableInterface.php index e99dd1a11..c5cd37991 100644 --- a/src/Entity/Contracts/VotableInterface.php +++ b/src/Entity/Contracts/VotableInterface.php @@ -39,5 +39,8 @@ public function getUserChoice(User $user): int; public function getUserVote(User $user): ?Vote; + /** + * @psalm-external-mutation-free + */ public function updateLastBoostDate(): self; } diff --git a/src/Entity/Entry.php b/src/Entity/Entry.php index f0509052d..ed041d956 100644 --- a/src/Entity/Entry.php +++ b/src/Entity/Entry.php @@ -176,6 +176,7 @@ public function __construct( $this->createdAtTraitConstruct(); $this->updateLastActive(); + /* @psalm-suppress UninitializedProperty */ $this->lastBoostedAt = $this->createdAt; } diff --git a/src/Entity/EntryComment.php b/src/Entity/EntryComment.php index 05c1a35ef..471030a1b 100644 --- a/src/Entity/EntryComment.php +++ b/src/Entity/EntryComment.php @@ -129,6 +129,7 @@ public function __construct( $this->createdAtTraitConstruct(); $this->updateLastActive(); + /* @psalm-suppress UninitializedProperty */ $this->lastBoostedAt = $this->createdAt; }