From f9123178d6ed311b9abb97564b711abd03bd2079 Mon Sep 17 00:00:00 2001 From: n0099 Date: Sun, 13 Oct 2024 00:09:41 +0000 Subject: [PATCH] + classes `BasePostKey`, `PostKeyWithParent`, `(Sub)?Reply` & `Thread` @ `App\DTO\PostKey` * replace `selectCurrentAndParentPostID()` to abstract `selectPostKeyDTO()` @ `App\Repository\Post\PostRepository`, also affects `App\PostsQuery\BaseQuery` & `App\PostsQuery\CursorCodec->encodeNextCursor()` * move the selecting field `$this->orderByField` on query builder from `setResult()` to `App\Repository\Post\PostRepository->selectPostKeyDTO()` * split variables `$(subR|r)eplies` by its twice assignments @ `fillWithParentPost()` @ `BaseQuery` * move the assign to `$this->orderByField` before the invoking to `App\Repository\Post\PostRepository->selectPostKeyDTO()` @ `(Index|Search)Query->query()` @ `App\PostsQuery` @ be --- be/src/DTO/PostKey/BasePostKey.php | 12 +++++ be/src/DTO/PostKey/PostKeyWithParent.php | 13 ++++++ be/src/DTO/PostKey/Reply.php | 7 +++ be/src/DTO/PostKey/SubReply.php | 14 ++++++ be/src/DTO/PostKey/Thread.php | 7 +++ be/src/PostsQuery/BaseQuery.php | 44 ++++++++++--------- be/src/PostsQuery/CursorCodec.php | 4 +- be/src/PostsQuery/IndexQuery.php | 21 +++++---- be/src/PostsQuery/SearchQuery.php | 19 ++++---- be/src/Repository/Post/PostRepository.php | 10 +---- be/src/Repository/Post/ReplyRepository.php | 8 ++++ be/src/Repository/Post/SubReplyRepository.php | 8 ++++ be/src/Repository/Post/ThreadRepository.php | 8 ++++ 13 files changed, 124 insertions(+), 51 deletions(-) create mode 100644 be/src/DTO/PostKey/BasePostKey.php create mode 100644 be/src/DTO/PostKey/PostKeyWithParent.php create mode 100644 be/src/DTO/PostKey/Reply.php create mode 100644 be/src/DTO/PostKey/SubReply.php create mode 100644 be/src/DTO/PostKey/Thread.php diff --git a/be/src/DTO/PostKey/BasePostKey.php b/be/src/DTO/PostKey/BasePostKey.php new file mode 100644 index 00000000..1547d8ec --- /dev/null +++ b/be/src/DTO/PostKey/BasePostKey.php @@ -0,0 +1,12 @@ +, - * replies: ?Collection, - * subReplies: ?Collection + * threads: ?Collection, + * replies: ?Collection, + * subReplies: ?Collection * } */ private array $queryResult; @@ -65,9 +68,6 @@ protected function setResult( $this->stopwatch->start('setResult'); $orderedQueries = $queries->map(fn(QueryBuilder $qb, string $postType): QueryBuilder => $qb - // we don't have to select the post ID - // since it's already selected by invokes of PostRepository->selectCurrentAndParentPostID() - ->addSelect("t.$this->orderByField") ->addOrderBy("t.$this->orderByField", $this->orderByDesc === true ? 'DESC' : 'ASC') // cursor paginator requires values of orderBy column are unique // if not it should fall back to other unique field (here is the post ID primary key) @@ -151,20 +151,19 @@ public function fillWithParentPost(): array /** @var Collection $tids */ /** @var Collection $pids */ /** @var Collection $spids */ - /** @var Collection $replies */ - /** @var Collection $subReplies */ - [[, $tids], [$replies, $pids], [$subReplies, $spids]] = array_map( - /** - * @param string $postIDName - * @return array{0: Collection, 1: Collection} - */ - static function (string $postIDName) use ($result): array { - $postTypePluralName = Helper::POST_ID_TO_TYPE_PLURAL[$postIDName]; + /** @var Collection $replyKeys */ + /** @var Collection $subReplyKeys */ + [[, $tids], [$replyKeys, $pids], [$subReplyKeys, $spids]] = array_map( + /** @return array{0: Collection, 1: Collection} */ + static function (string $postTypePluralName) use ($result): array { return \array_key_exists($postTypePluralName, $result) - ? [$result[$postTypePluralName], $result[$postTypePluralName]->pluck($postIDName)] + ? [ + $result[$postTypePluralName], + $result[$postTypePluralName]->map(fn(BasePostKey $postKey) => $postKey->postId) + ] : [collect(), collect()]; }, - Helper::POST_ID, + Helper::POST_TYPES_PLURAL, ); /** @var int $fid */ @@ -173,7 +172,10 @@ static function (string $postIDName) use ($result): array { $this->stopwatch->start('fillWithThreadsFields'); /** @var Collection $parentThreadsID parent tid of all replies and their sub replies */ - $parentThreadsID = $replies->pluck('tid')->concat($subReplies->pluck('tid'))->unique(); + $parentThreadsID = $replyKeys + ->map(fn(ReplyKey $postKey) => $postKey->parentPostId) + ->concat($subReplyKeys->map(fn(SubReplyKey $postKey) => $postKey->tid)) + ->unique(); /** @var Collection $threads */ $threads = collect($postModels['thread']->getPosts($parentThreadsID->concat($tids))) ->each(static fn(Thread $thread) => @@ -182,14 +184,16 @@ static function (string $postIDName) use ($result): array { $this->stopwatch->start('fillWithRepliesFields'); /** @var Collection $parentRepliesID parent pid of all sub replies */ - $parentRepliesID = $subReplies->pluck('pid')->unique(); + $parentRepliesID = $subReplyKeys->map(fn(SubReplyKey $postKey) => $postKey->parentPostId)->unique(); $allRepliesId = $parentRepliesID->concat($pids); + /** @var Collection $replies */ $replies = collect($postModels['reply']->getPosts($allRepliesId)) ->each(static fn(Reply $reply) => $reply->setIsMatchQuery($pids->contains($reply->getPid()))); $this->stopwatch->stop('fillWithRepliesFields'); $this->stopwatch->start('fillWithSubRepliesFields'); + /** @var Collection $subReplies */ $subReplies = collect($postModels['subReply']->getPosts($spids)); $this->stopwatch->stop('fillWithSubRepliesFields'); diff --git a/be/src/PostsQuery/CursorCodec.php b/be/src/PostsQuery/CursorCodec.php index d9f42c16..f7c2d371 100644 --- a/be/src/PostsQuery/CursorCodec.php +++ b/be/src/PostsQuery/CursorCodec.php @@ -2,6 +2,7 @@ namespace App\PostsQuery; +use App\DTO\PostKey\BasePostKey; use App\Helper; use Illuminate\Support\Collection; use Illuminate\Support\Str; @@ -21,8 +22,7 @@ public function encodeNextCursor(Collection $postsKeyByTypePluralName, string $o Helper::POST_TYPE_PLURAL_TO_SINGULAR[$type] => $posts->last(), // null when no posts ]) // [singularPostTypeName => lastPostInResult] ->filter() // remove post types that have no posts - ->map(fn(array $post, string $typePluralName) => - [$post[Helper::POST_TYPE_TO_ID[$typePluralName]], $post[$orderByField]]) + ->map(fn(BasePostKey $post) => [$post->postId, $post->orderByFieldValue]) ->map(static fn(array $cursors) => collect($cursors) ->map(static function (int|string $cursor): string { if ($cursor === 0) { // quick exit to keep 0 as is diff --git a/be/src/PostsQuery/IndexQuery.php b/be/src/PostsQuery/IndexQuery.php index 0a728b51..681e2065 100644 --- a/be/src/PostsQuery/IndexQuery.php +++ b/be/src/PostsQuery/IndexQuery.php @@ -41,6 +41,15 @@ public function query(QueryParams $params, ?string $cursor): void /** @var array $postTypes */ $postTypes = $flatParams['postTypes']; + if ($flatParams['orderBy'] === 'default') { + $this->orderByField = 'postedAt'; // order by postedAt to prevent posts out of order when order by post ID + if (\array_key_exists('fid', $flatParams) && $postIDParam->count() === 0) { // query by fid only + $this->orderByDesc = true; + } elseif ($hasPostIDParam) { // query by post ID (with or without fid) + $this->orderByDesc = false; + } + } + /** * @param int $fid * @return Collection key by post type @@ -48,7 +57,7 @@ public function query(QueryParams $params, ?string $cursor): void $getQueryBuilders = fn(int $fid): Collection => collect($this->postRepositoryFactory->newForumPosts($fid)) ->only($postTypes) - ->transform(static fn(PostRepository $repository) => $repository->selectCurrentAndParentPostID()); + ->transform(fn(PostRepository $repository) => $repository->selectPostKeyDTO($this->orderByField)); $getFidByPostIDParam = function (string $postIDName, int $postID): int { $postExistencesKeyByFid = collect($this->forumRepository->getOrderedForumsId()) ->mapWithKeys(fn(int $fid) => [$fid => $this->postRepositoryFactory @@ -88,20 +97,10 @@ public function query(QueryParams $params, ?string $cursor): void $qb->where("t.$postIDParamName = :postIDParamValue") ->setParameter('postIDParamValue', $postIDParamValue)); } - if (array_diff($postTypes, Helper::POST_TYPES) !== []) { $queries = $queries->only($postTypes); } - if ($flatParams['orderBy'] === 'default') { - $this->orderByField = 'postedAt'; // order by postedAt to prevent posts out of order when order by post ID - if (\array_key_exists('fid', $flatParams) && $postIDParam->count() === 0) { // query by fid only - $this->orderByDesc = true; - } elseif ($hasPostIDParam) { // query by post ID (with or without fid) - $this->orderByDesc = false; - } - } - $this->setResult($fid, $queries, $cursor, $hasPostIDParam ? $postIDParamName : null); } } diff --git a/be/src/PostsQuery/SearchQuery.php b/be/src/PostsQuery/SearchQuery.php index 0c84176b..54fead54 100644 --- a/be/src/PostsQuery/SearchQuery.php +++ b/be/src/PostsQuery/SearchQuery.php @@ -26,13 +26,22 @@ public function query(QueryParams $params, ?string $cursor): void { /** @var int $fid */ $fid = $params->getUniqueParamValue('fid'); + + $orderByParam = $params->pick('orderBy')[0]; + $this->orderByField = $orderByParam->value; + $this->orderByDesc = $orderByParam->getSub('direction'); + if ($this->orderByField === 'default') { + $this->orderByField = 'postedAt'; + $this->orderByDesc = true; + } + /** @var array $cachedUserQueryResult key by param name */ $cachedUserQueryResult = []; /** @var Collection $queries key by post type */ $queries = collect($this->postRepositoryFactory->newForumPosts($fid)) ->only($params->getUniqueParamValue('postTypes')) ->map(function (PostRepository $repository) use ($params, &$cachedUserQueryResult): QueryBuilder { - $postQuery = $repository->selectCurrentAndParentPostID(); + $postQuery = $repository->selectPostKeyDTO($this->orderByField); foreach ($params->omit() as $paramIndex => $param) { // omit nothing to get all params // even when $cachedUserQueryResult[$param->name] is null // it will still pass as a reference to the array item @@ -47,14 +56,6 @@ public function query(QueryParams $params, ?string $cursor): void return $postQuery; }); - $orderByParam = $params->pick('orderBy')[0]; - $this->orderByField = $orderByParam->value; - $this->orderByDesc = $orderByParam->getSub('direction'); - if ($this->orderByField === 'default') { - $this->orderByField = 'postedAt'; - $this->orderByDesc = true; - } - $this->setResult($fid, $queries, $cursor); } diff --git a/be/src/Repository/Post/PostRepository.php b/be/src/Repository/Post/PostRepository.php index e7a8d7d5..8a41d1aa 100644 --- a/be/src/Repository/Post/PostRepository.php +++ b/be/src/Repository/Post/PostRepository.php @@ -3,7 +3,6 @@ namespace App\Repository\Post; use App\Entity\Post\Post; -use App\Helper; use App\Repository\RepositoryWithSplitFid; use Doctrine\ORM\QueryBuilder; @@ -13,14 +12,7 @@ */ abstract class PostRepository extends RepositoryWithSplitFid { - public function selectCurrentAndParentPostID(): QueryBuilder - { - return $this->createQueryBuilder('t')->select(collect(Helper::POST_TYPE_TO_ID) - ->slice(0, array_search($this->getTableNameSuffix(), Helper::POST_TYPES, true) + 1) - ->values() - ->map(static fn(string $postIDField) => "t.$postIDField") - ->all()); - } + abstract public function selectPostKeyDTO(string $orderByField): QueryBuilder; abstract public function getPosts(array|\ArrayAccess $postsId): array; diff --git a/be/src/Repository/Post/ReplyRepository.php b/be/src/Repository/Post/ReplyRepository.php index 3b18e17d..a7151ff4 100644 --- a/be/src/Repository/Post/ReplyRepository.php +++ b/be/src/Repository/Post/ReplyRepository.php @@ -2,8 +2,10 @@ namespace App\Repository\Post; +use App\DTO\PostKey\Reply as ReplyKey; use App\Entity\Post\Reply; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\DependencyInjection\Attribute\Exclude; @@ -21,6 +23,12 @@ protected function getTableNameSuffix(): string return 'reply'; } + public function selectPostKeyDTO(string $orderByField): QueryBuilder + { + return $this->createQueryBuilder('t') + ->select('new ' . ReplyKey::class . "(t.tid, t.pid, '$orderByField', t.$orderByField)"); + } + public function getPosts(array|\ArrayAccess $postsId): array { $dql = 'SELECT t FROM App\Entity\Post\Reply t WHERE t.pid IN (:pid)'; diff --git a/be/src/Repository/Post/SubReplyRepository.php b/be/src/Repository/Post/SubReplyRepository.php index af28cb8c..87d3e80d 100644 --- a/be/src/Repository/Post/SubReplyRepository.php +++ b/be/src/Repository/Post/SubReplyRepository.php @@ -2,8 +2,10 @@ namespace App\Repository\Post; +use App\DTO\PostKey\SubReply as SubReplyKey; use App\Entity\Post\SubReply; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\DependencyInjection\Attribute\Exclude; @@ -21,6 +23,12 @@ protected function getTableNameSuffix(): string return 'subReply'; } + public function selectPostKeyDTO(string $orderByField): QueryBuilder + { + return $this->createQueryBuilder('t') + ->select('new ' . SubReplyKey::class . "(t.tid, t.pid, t.spid, '$orderByField', t.$orderByField)"); + } + public function getPosts(array|\ArrayAccess $postsId): array { $dql = 'SELECT t FROM App\Entity\Post\SubReply t WHERE t.spid IN (:spid)'; diff --git a/be/src/Repository/Post/ThreadRepository.php b/be/src/Repository/Post/ThreadRepository.php index 6d54f9a2..0afc8adc 100644 --- a/be/src/Repository/Post/ThreadRepository.php +++ b/be/src/Repository/Post/ThreadRepository.php @@ -2,8 +2,10 @@ namespace App\Repository\Post; +use App\DTO\PostKey\Thread as ThreadKey; use App\Entity\Post\Thread; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\DependencyInjection\Attribute\Exclude; @@ -21,6 +23,12 @@ protected function getTableNameSuffix(): string return 'thread'; } + public function selectPostKeyDTO(string $orderByField): QueryBuilder + { + return $this->createQueryBuilder('t') + ->select('new ' . ThreadKey::class . "(t.tid, '$orderByField', t.$orderByField)"); + } + public function getPosts(array|\ArrayAccess $postsId): array { $dql = 'SELECT t FROM App\Entity\Post\Thread t WHERE t.tid IN (:tid)';