/
UserRecommendedBooks.php
100 lines (81 loc) · 2.56 KB
/
UserRecommendedBooks.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?php
namespace Queries;
use Book;
use Illuminate\Database\Eloquent\Collection;
use StuDocu\CacheableEntities\Contracts\Cacheable;
use StuDocu\CacheableEntities\Contracts\SerializableCacheable;
use StuDocu\CacheableEntities\Exceptions\CorruptSerializedCacheValueException;
use User;
/**
* @phpstan-type ReturnStructure Collection<int, Book>
* @phpstan-type SerializedStructure array<int>
*
* @implements Cacheable<ReturnStructure>
* @implements SerializableCacheable<ReturnStructure, SerializedStructure>
*/
class UserRecommendedBooks implements Cacheable, SerializableCacheable
{
public const DEFAULT_LIMIT = 8;
public function __construct(
protected readonly User $user,
protected readonly array $followedBookIds = [],
protected readonly int $limit = self::DEFAULT_LIMIT,
) {
}
public function getCacheTTL(): int
{
return 3600 * 24;
}
public function getCacheKey(): string
{
return "users:{$this->user->id}:books:recommended.v1";
}
public function get(): Collection
{
// -> Fetching recommendation logic can go here.
$books = Collection::empty();
$this->eagerLoadRelations($books);
return $books;
}
/**
* @param Collection<Book> $value
* @return array<int>
*/
public function serialize(mixed $value): array
{
return $value->pluck('id')->all();
}
/**
* @return ReturnStructure
*
* @throws CorruptSerializedCacheValueException
*/
public function unserialize(mixed $value, mixed $default = null): Collection
{
// Corrupt format cached
if (! is_array($value)) {
throw new CorruptSerializedCacheValueException();
}
if (empty($value)) {
return Collection::empty();
}
$followedBooksFastAccess = array_flip($this->followedBookIds);
$value = collect($value)
// Exclude studylists already followed after being cached.
->reject(fn (int $studylistId) => array_key_exists($studylistId, $followedBooksFastAccess));
// Were all previously computed recommendations followed? Then compute a new value.
if ($value->isEmpty()) {
throw new CorruptSerializedCacheValueException();
}
$books = Book::query()->findMany($value);
$this->eagerLoadRelations($books);
return $books;
}
/**
* @param ReturnStructure $books
*/
private function eagerLoadRelations(Collection $books): void
{
$books->loadMissing('author');
}
}