Skip to content

Commit 68fd617

Browse files
chore: Refactor collections loading with a Preloader
1 parent b0d0da7 commit 68fd617

14 files changed

Lines changed: 507 additions & 112 deletions

File tree

src/controllers/profiles/Opml.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public function show(Request $request): Response
3535
]);
3636
$collections = utils\Sorter::localeSort($collections, 'name');
3737

38+
models\collections\Preloader::for($collections)->timeFiltersFor($user);
39+
3840
return Response::ok('profiles/opml/show.opml.xml.twig', [
3941
'user' => $user,
4042
'collections' => $collections,

src/controllers/streams/Sources.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,17 @@ public function index(Request $request): Response
3737

3838
auth\Access::require($user, 'view', $stream);
3939

40+
$sources = $stream->sources([
41+
'context_user' => $user,
42+
]);
43+
44+
models\collections\Preloader::for($sources)
45+
->publishers()
46+
->countStreamsFor($user);
47+
4048
return Response::ok('streams/sources/index.html.twig', [
4149
'stream' => $stream,
50+
'sources' => $sources,
4251
]);
4352
}
4453

@@ -67,6 +76,11 @@ public function edit(Request $request): Response
6776
'context_user' => $user,
6877
]);
6978

79+
$sources_to_preload = array_merge($followed_sources, $existing_sources);
80+
models\collections\Preloader::for($sources_to_preload)
81+
->publishers()
82+
->countStreamsFor($user);
83+
7084
$suggested_sources = array_udiff(
7185
$followed_sources,
7286
$existing_sources,

src/models/Collection.php

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,6 @@ class Collection
9999
#[Database\Column(computed: true)]
100100
public ?int $number_links = null;
101101

102-
#[Database\Column(computed: true)]
103-
public ?int $number_streams = null;
104-
105-
#[Database\Column(computed: true)]
106-
public ?string $time_filter = null;
107-
108102
public function __construct()
109103
{
110104
$this->id = \Minz\Random::timebased();
@@ -261,25 +255,39 @@ public function preloadOwner(?User $owner): void
261255
$this->memoizeValue('owner', $owner);
262256
}
263257

258+
/**
259+
* Set the publishers of the collection without querying the database.
260+
*
261+
* @see collections\Preloader
262+
*
263+
* @param User[] $publishers
264+
*/
265+
public function preloadPublishers(array $publishers): void
266+
{
267+
$this->memoizeValue('publishers', $publishers);
268+
}
269+
264270
/**
265271
* Return the list of users with write access.
266272
*
267273
* @return User[]
268274
*/
269275
public function publishers(): array
270276
{
271-
$owner = $this->owner();
272-
$shares = $this->shares(['access_type' => 'write']);
277+
return $this->memoize('publishers', function (): array {
278+
$owner = $this->owner();
279+
$shares = $this->shares(['access_type' => 'write']);
273280

274-
$publishers = array_map(function (CollectionShare $share): User {
275-
return $share->user();
276-
}, $shares);
281+
$publishers = array_map(function (CollectionShare $share): User {
282+
return $share->user();
283+
}, $shares);
277284

278-
if ($owner) {
279-
array_unshift($publishers, $owner);
280-
}
285+
if ($owner) {
286+
array_unshift($publishers, $owner);
287+
}
281288

282-
return $publishers;
289+
return $publishers;
290+
});
283291
}
284292

285293
/**
@@ -395,17 +403,48 @@ public function groupForUser(string $user_id): ?Group
395403
/**
396404
* Return the number of streams of the given user in which this collection
397405
* is a source.
398-
*
399-
* This is the same information as the number_streams computed property,
400-
* for a collection that has been loaded on its own.
401406
*/
402407
public function countStreamsByUser(User $user): int
403408
{
404409
return $this->memoize("count_streams_{$user->id}", function () use ($user): int {
405-
return StreamToFollow::countByUserAndSource($user, $this);
410+
$counts = StreamToFollow::countByUserAndSources($user, [$this]);
411+
return $counts[$this->id] ?? 0;
406412
});
407413
}
408414

415+
/**
416+
* Set the number of streams of a user without querying the database.
417+
*
418+
* @see collections\Preloader
419+
*/
420+
public function preloadCountStreamsByUser(User $user, int $count): void
421+
{
422+
$this->memoizeValue("count_streams_{$user->id}", $count);
423+
}
424+
425+
/**
426+
* Return the time filter applied by the given user to this collection, or
427+
* null if the user doesn't follow it.
428+
*/
429+
public function timeFilterByUser(User $user): ?string
430+
{
431+
return $this->memoize("time_filter_{$user->id}", function () use ($user): ?string {
432+
$follows = FollowedCollection::listByUserAndCollections($user, [$this]);
433+
$follow = $follows[$this->id] ?? null;
434+
return $follow?->time_filter;
435+
});
436+
}
437+
438+
/**
439+
* Set the time filter of a user without querying the database.
440+
*
441+
* @see collections\Preloader
442+
*/
443+
public function preloadTimeFilterByUser(User $user, ?string $time_filter): void
444+
{
445+
$this->memoizeValue("time_filter_{$user->id}", $time_filter);
446+
}
447+
409448
/**
410449
* Return the topics attached to the current collection.
411450
*
@@ -457,6 +496,8 @@ public function shareWith(User $user, string $access_type): void
457496
{
458497
$collection_share = new CollectionShare($user->id, $this->id, $access_type);
459498
$collection_share->save();
499+
500+
$this->unmemoize('publishers');
460501
}
461502

462503
/**
@@ -468,6 +509,8 @@ public function unshareWith(User $user): void
468509
'collection_id' => $this->id,
469510
'user_id' => $user->id,
470511
]);
512+
513+
$this->unmemoize('publishers');
471514
}
472515

473516
/**

src/models/CollectionShare.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,47 @@ public function __construct(string $user_id, string $collection_id, string $type
5555
$this->type = $type;
5656
}
5757

58+
/**
59+
* Return the ids of the users the given collections are shared with in
60+
* write access, indexed by the ids of these collections.
61+
*
62+
* The collections shared with nobody are absent from the returned array.
63+
*
64+
* @param Collection[] $collections
65+
*
66+
* @return array<string, string[]>
67+
*/
68+
public static function listWriterIdsByCollections(array $collections): array
69+
{
70+
if (!$collections) {
71+
return [];
72+
}
73+
74+
$collection_ids = array_column($collections, 'id');
75+
$ids_as_question_marks = array_fill(0, count($collection_ids), '?');
76+
$ids_as_question_marks = implode(', ', $ids_as_question_marks);
77+
78+
$sql = <<<SQL
79+
SELECT cs.collection_id, cs.user_id
80+
FROM collection_shares cs
81+
82+
WHERE cs.collection_id IN ({$ids_as_question_marks})
83+
AND cs.type = 'write'
84+
SQL;
85+
86+
$database = Database::get();
87+
$statement = $database->prepare($sql);
88+
$statement->execute($collection_ids);
89+
90+
$user_ids_by_collection_ids = [];
91+
92+
foreach ($statement->fetchAll() as $row) {
93+
$user_ids_by_collection_ids[$row['collection_id']][] = $row['user_id'];
94+
}
95+
96+
return $user_ids_by_collection_ids;
97+
}
98+
5899
/**
59100
* Return the user attached to the CollectionShare
60101
*/

src/models/FollowedCollection.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,37 @@ public static function findOrCreate(User $user, Collection $collection): self
6363
]);
6464
}
6565

66+
/**
67+
* Return the follows of the given user for the given collections, indexed
68+
* by the ids of these collections.
69+
*
70+
* The collections that the user doesn't follow are absent from the
71+
* returned array.
72+
*
73+
* @param Collection[] $collections
74+
*
75+
* @return array<string, self>
76+
*/
77+
public static function listByUserAndCollections(User $user, array $collections): array
78+
{
79+
if (!$collections) {
80+
return [];
81+
}
82+
83+
$follows = self::listBy([
84+
'user_id' => $user->id,
85+
'collection_id' => array_column($collections, 'id'),
86+
]);
87+
88+
$follows_by_collection_ids = [];
89+
90+
foreach ($follows as $follow) {
91+
$follows_by_collection_ids[$follow->collection_id] = $follow;
92+
}
93+
94+
return $follows_by_collection_ids;
95+
}
96+
6697
/**
6798
* Return the streams in which the followed collection is a source.
6899
*

src/models/StreamToFollow.php

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,28 +67,47 @@ public static function findOrCreate(Stream $stream, Collection $source): self
6767
}
6868

6969
/**
70-
* Return the number of streams of the given user in which the source is
71-
* attached.
70+
* Return the numbers of streams of the given user in which the given
71+
* sources are attached, indexed by the ids of these sources.
72+
*
73+
* The sources that are in no stream are absent from the returned array.
74+
*
75+
* @param Collection[] $sources
76+
*
77+
* @return array<string, int>
7278
*/
73-
public static function countByUserAndSource(User $user, Collection $source): int
79+
public static function countByUserAndSources(User $user, array $sources): array
7480
{
81+
if (!$sources) {
82+
return [];
83+
}
84+
85+
$source_ids = array_column($sources, 'id');
86+
$ids_as_question_marks = array_fill(0, count($source_ids), '?');
87+
$ids_as_question_marks = implode(', ', $ids_as_question_marks);
88+
7589
$sql = <<<SQL
76-
SELECT COUNT(*)
90+
SELECT fc.collection_id, COUNT(*) AS count
7791
FROM streams_to_follows sf, followed_collections fc
7892
7993
WHERE sf.follow_id = fc.id
80-
AND fc.user_id = :user_id
81-
AND fc.collection_id = :source_id
94+
AND fc.user_id = ?
95+
AND fc.collection_id IN ({$ids_as_question_marks})
96+
97+
GROUP BY fc.collection_id
8298
SQL;
8399

84100
$database = Database::get();
85101
$statement = $database->prepare($sql);
86-
$statement->execute([
87-
'user_id' => $user->id,
88-
'source_id' => $source->id,
89-
]);
102+
$statement->execute([$user->id, ...$source_ids]);
103+
104+
$counts = [];
105+
106+
foreach ($statement->fetchAll() as $row) {
107+
$counts[$row['collection_id']] = intval($row['count']);
108+
}
90109

91-
return intval($statement->fetchColumn());
110+
return $counts;
92111
}
93112

94113
/**

0 commit comments

Comments
 (0)