Skip to content

Commit

Permalink
+ private method (un)serializeNextPageCursor() to figure out the va…
Browse files Browse the repository at this point in the history
…lue of `$this->orderByField` and post id on the topmost post after orderBy for each post type, then base64 encoding its numeric value and merge into a cursor string, and vice versa

* expose the returned value of `$this->serializeNextPageCursor()` in `$this->queryResultPages` @ `setResult()`
* inline variable `$getPostsAndIDTuple()` @ `fillWithParentPost()`
@ be/Http/PostsQuery/BaseQuery.php
  • Loading branch information
n0099 committed Dec 23, 2022
1 parent 5947f4a commit 44be32e
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 13 deletions.
69 changes: 57 additions & 12 deletions be/app/Http/PostsQuery/BaseQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Pagination\CursorPaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use JetBrains\PhpStorm\ArrayShape;
use TbClient\Wrapper\PostContentWrapper;

Expand Down Expand Up @@ -74,11 +75,54 @@ protected function setResult(int $fid, Collection $queries): void
Helper::abortAPIIf(40401, $postKeyByTypePluralName->every(static fn (Collection $i) => $i->isEmpty()));
$this->queryResult = ['fid' => $fid, ...$postKeyByTypePluralName];
$this->queryResultPages = [
'nextCursor' => $this->serializeNextPageCursor($postKeyByTypePluralName),
'hasMorePages' => self::unionPageStats($paginators, 'hasMorePages',
static fn (Collection $v) => $v->filter()->count() !== 0) // Collection->filter() will remove false values
];
}

private function serializeNextPageCursor(Collection $postKeyByTypePluralName): string
{
return collect(Helper::POST_TYPES_PLURAL)
->flip()->replace($postKeyByTypePluralName) // reorder the keys to match the order of Helper::POST_TYPES_PLURAL
->mapWithKeys(static fn (Collection $posts, string $type) =>
[array_flip(Helper::POST_TYPE_TO_PLURAL)[$type] => $posts->last()]) // [singularPostTypeName => lastPostInResult]
->map(fn (PostModel $post, string $typePluralName) => [ // [postID, orderByField]
$post->getAttribute(Helper::POST_TYPE_TO_ID[$typePluralName]),
$post->getAttribute($this->orderByField)
])
->map(static fn (array $tuple) => collect($tuple)
->each(static function (int $value): void {
// pack('P') only supports positive integers
Helper::abortAPIIf(50002, $value < 0);
})
->map(static fn (int $value, int $index): string =>
str_replace(['+', '/', '='], ['-', '_', ''], // https://en.wikipedia.org/wiki/Base64#URL_applications
base64_encode(
rtrim( // remove trailing 0x00
pack('P', $value), // unsigned int64 with little endian byte order
"\x00"))))
->join(','))
->join(',');
}

private static function unserializeNextPageCursor(string $nextCursor): array
{
return array_combine(Helper::POST_TYPES, Str::of($nextCursor)
->explode(',')
->map(static fn (string $cursorBase64): int =>
((array)(
unpack('P',
str_pad( // re-add removed trailing 0x00
base64_decode(
str_replace(['-', '_'], ['+', '/'], $cursorBase64) // https://en.wikipedia.org/wiki/Base64#URL_applications
), 8, "\x00"))
))[1]) // the returned array of unpack() will starts index from 1
->chunk(2) // split six values into three post type pairs
->map(static fn (Collection $i) => $i->values()) // reorder keys
->toArray());
}

/**
* Union builders pagination $unionMethodName data by $unionStatement
*
Expand All @@ -104,24 +148,25 @@ private static function unionPageStats(Collection $paginators, string $unionMeth
])] public function fillWithParentPost(): array
{
$result = $this->queryResult;
/**
* @param string $postIDName
* @return array{0: array<int>, 1: Collection<PostModel>}
*/
$getPostsAndIDTuple = static function (string $postIDName) use ($result): array {
$postTypePluralName = Helper::POST_ID_TO_TYPE_PLURAL[$postIDName];
return \array_key_exists($postTypePluralName, $result)
? [$result[$postTypePluralName], $result[$postTypePluralName]->pluck($postIDName)->toArray()]
: [collect(), []];
};
/** @var array<int> $tids */
/** @var array<int> $pids */
/** @var array<int> $spids */
/** @var Collection<ThreadModel> $threads */
/** @var Collection<ReplyModel> $replies */
/** @var Collection<SubReplyModel> $subReplies */
[[, $tids], [$replies, $pids], [$subReplies, $spids]] =
array_map(static fn (string $postIDName) => $getPostsAndIDTuple($postIDName), Helper::POST_ID);
[[, $tids], [$replies, $pids], [$subReplies, $spids]] = array_map(
/**
* @param string $postIDName
* @return array{0: array<int>, 1: Collection<PostModel>}
*/
static function (string $postIDName) use ($result): array {
$postTypePluralName = Helper::POST_ID_TO_TYPE_PLURAL[$postIDName];
return \array_key_exists($postTypePluralName, $result)
? [$result[$postTypePluralName], $result[$postTypePluralName]->pluck($postIDName)->toArray()]
: [collect(), []];
},
Helper::POST_ID
);

/** @var int $fid */
$fid = $result['fid'];
Expand Down
2 changes: 1 addition & 1 deletion be/app/Http/PostsQuery/SearchQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function query(QueryParams $params): self
/** @var Collection<string, Builder> $queries key by post type */
$queries = collect(PostModelFactory::getPostModelsByFid($fid))
->only($params->getUniqueParamValue('postTypes'))
->map(function (PostModel $postModel, string $postType) use ($params, &$cachedUserQuery): Builder {
->map(function (PostModel $postModel) use ($params, &$cachedUserQuery): Builder {
$postQuery = $postModel->newQuery();
foreach ($params->omit() as $param) { // omit nothing to get all params
// even when $cachedUserQuery[$param->name] is null, it will still pass as a reference to the array item
Expand Down

0 comments on commit 44be32e

Please sign in to comment.