Skip to content

Commit abb4c84

Browse files
chore: Refactor memoization of time-consuming methods
1 parent 43ed1f3 commit abb4c84

File tree

5 files changed

+110
-55
lines changed

5 files changed

+110
-55
lines changed

src/forms/collections/EditCollectionGroup.php

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,25 @@
1414
*/
1515
class EditCollectionGroup extends BaseForm
1616
{
17+
use utils\Memoizer;
18+
1719
#[Form\Field(transform: 'trim')]
1820
public string $name = '';
1921

2022
public int $group_name_max_length = models\Group::NAME_MAX_LENGTH;
2123

22-
/** @var ?models\Group[] */
23-
private ?array $cache_groups = null;
24-
2524
/**
2625
* @return models\Group[]
2726
*/
2827
public function groups(): array
2928
{
30-
if ($this->cache_groups === null) {
29+
return $this->memoize('groups', function (): array {
3130
$user = $this->user();
3231
$groups = models\Group::listBy([
3332
'user_id' => $user->id,
3433
]);
35-
$this->cache_groups = utils\Sorter::localeSort($groups, 'name');
36-
}
37-
38-
return $this->cache_groups;
34+
return utils\Sorter::localeSort($groups, 'name');
35+
});
3936
}
4037

4138
/**

src/forms/collections/ShareCollection.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use App\forms\BaseForm;
66
use App\models;
7+
use App\utils;
78
use Minz\Form;
89
use Minz\Request;
910
use Minz\Translatable;
@@ -17,6 +18,8 @@
1718
*/
1819
class ShareCollection extends BaseForm
1920
{
21+
use utils\Memoizer;
22+
2023
#[Form\Field]
2124
#[Validable\Presence(
2225
message: new Translatable('The type is required.'),
@@ -30,15 +33,11 @@ class ShareCollection extends BaseForm
3033
#[Form\Field(transform: 'trim')]
3134
public string $user_id = '';
3235

33-
private ?models\User $cache_user = null;
34-
3536
public function user(): models\User
3637
{
37-
if ($this->cache_user === null) {
38-
$this->cache_user = models\User::require($this->user_id);
39-
}
40-
41-
return $this->cache_user;
38+
return $this->memoize('user', function (): models\User {
39+
return models\User::require($this->user_id);
40+
});
4241
}
4342

4443
/**

src/forms/links/CollectionsSelector.php

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
*/
1515
trait CollectionsSelector
1616
{
17+
use utils\Memoizer;
18+
1719
/** @var string[] */
1820
#[Form\Field(bind: false)]
1921
public array $collection_ids = [];
@@ -24,9 +26,6 @@ trait CollectionsSelector
2426

2527
public int $collection_name_max_length = models\Collection::NAME_MAX_LENGTH;
2628

27-
/** @var ?array<string, models\Collection[]> */
28-
private ?array $cache_collection_values = null;
29-
3029
public function user(): models\User
3130
{
3231
$user = $this->options->get('user');
@@ -48,45 +47,41 @@ public function user(): models\User
4847
*/
4948
public function collectionsValues(): array
5049
{
51-
if ($this->cache_collection_values !== null) {
52-
return $this->cache_collection_values;
53-
}
54-
55-
$user = $this->user();
50+
return $this->memoize('collections_values', function (): array {
51+
$user = $this->user();
5652

57-
$collection_values = [];
53+
$collection_values = [];
5854

59-
$groups = models\Group::listBy(['user_id' => $user->id]);
60-
$groups = utils\Sorter::localeSort($groups, 'name');
55+
$groups = models\Group::listBy(['user_id' => $user->id]);
56+
$groups = utils\Sorter::localeSort($groups, 'name');
6157

62-
$collections = $user->collections();
63-
$collections = utils\Sorter::localeSort($collections, 'name');
64-
$groups_to_collections = utils\Grouper::groupBy($collections, 'group_id');
58+
$collections = $user->collections();
59+
$collections = utils\Sorter::localeSort($collections, 'name');
60+
$groups_to_collections = utils\Grouper::groupBy($collections, 'group_id');
6561

66-
$shared_collections = $user->sharedCollections(options: [
67-
'access_type' => 'write',
68-
]);
69-
$shared_collections = utils\Sorter::localeSort($shared_collections, 'name');
62+
$shared_collections = $user->sharedCollections(options: [
63+
'access_type' => 'write',
64+
]);
65+
$shared_collections = utils\Sorter::localeSort($shared_collections, 'name');
7066

71-
// Add the collections with no groups first.
72-
$collection_values[''] = $groups_to_collections[''] ?? [];
67+
// Add the collections with no groups first.
68+
$collection_values[''] = $groups_to_collections[''] ?? [];
7369

74-
// Add the collections with groups then
75-
foreach ($groups as $group) {
76-
if (isset($groups_to_collections[$group->id])) {
77-
$collection_values[$group->name] = $groups_to_collections[$group->id];
70+
// Add the collections with groups then
71+
foreach ($groups as $group) {
72+
if (isset($groups_to_collections[$group->id])) {
73+
$collection_values[$group->name] = $groups_to_collections[$group->id];
74+
}
7875
}
79-
}
8076

81-
// Finally, add the shared collections like it was a distinct group.
82-
if ($shared_collections) {
83-
$share_group_name = _('Shared with me');
84-
$collection_values[$share_group_name] = $shared_collections;
85-
}
86-
87-
$this->cache_collection_values = $collection_values;
77+
// Finally, add the shared collections like it was a distinct group.
78+
if ($shared_collections) {
79+
$share_group_name = _('Shared with me');
80+
$collection_values[$share_group_name] = $shared_collections;
81+
}
8882

89-
return $collection_values;
83+
return $collection_values;
84+
});
9085
}
9186

9287
/**

src/forms/links/RepairLink.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use App\forms\BaseForm;
66
use App\models;
7+
use App\utils;
78
use Minz\Form;
89
use Minz\Translatable;
910
use Minz\Validable;
@@ -14,6 +15,8 @@
1415
*/
1516
class RepairLink extends BaseForm
1617
{
18+
use utils\Memoizer;
19+
1720
#[Form\Field(transform: '\SpiderBits\Url::sanitize')]
1821
#[Validable\Presence(
1922
message: new Translatable('The link is required.'),
@@ -26,15 +29,11 @@ class RepairLink extends BaseForm
2629
#[Form\Field]
2730
public bool $force_sync = false;
2831

29-
private ?string $cache_url_cleared = null;
30-
3132
public function urlCleared(): string
3233
{
33-
if ($this->cache_url_cleared === null) {
34-
$this->cache_url_cleared = \SpiderBits\ClearUrls::clear($this->url);
35-
}
36-
37-
return $this->cache_url_cleared;
34+
return $this->memoize('url_cleared', function (): string {
35+
return \SpiderBits\ClearUrls::clear($this->url);
36+
});
3837
}
3938

4039
public function hasDetectedTrackers(): bool

src/utils/Memoizer.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace App\utils;
4+
5+
/**
6+
* The Memoizer trait can be used to cache the results of time-consuming functions.
7+
*
8+
* It wraps a function in a "memoize" callback. When the memoize() method is
9+
* called, it checks if the result is present in the cache. If it's not, it
10+
* calls the callback and caches the result.
11+
*
12+
* use App\models;
13+
* use App\utils;
14+
*
15+
* class MyClass
16+
* {
17+
* use utils\Memoizer;
18+
*
19+
* public function timeConsumingMethod(): array
20+
* {
21+
* return $this->memoize('my_cache_key', function (): array {
22+
* return models\User::listAll();
23+
* });
24+
* }
25+
* }
26+
*
27+
* In this example, `MyClass::timeConsumingMethod` can be called several time,
28+
* but users will be fetched from the database only the first time.
29+
*
30+
* @author Marien Fressinaud <dev@marienfressinaud.fr>
31+
* @license http://www.gnu.org/licenses/agpl-3.0.en.html AGPL
32+
*/
33+
trait Memoizer
34+
{
35+
/** @var array<string, mixed> */
36+
private array $memoizer_cache = [];
37+
38+
/**
39+
* Cache and return the result of a callback.
40+
*
41+
* @template T of mixed
42+
*
43+
* @param callable(): T $callback
44+
*
45+
* @return T
46+
*/
47+
protected function memoize(string $key, callable $callback): mixed
48+
{
49+
if (!array_key_exists($key, $this->memoizer_cache)) {
50+
$this->memoizer_cache[$key] = $callback();
51+
}
52+
53+
return $this->memoizer_cache[$key];
54+
}
55+
56+
/**
57+
* Remove a result from the cache.
58+
*/
59+
protected function unmemoize(string $key): void
60+
{
61+
if (array_key_exists($key, $this->memoizer_cache)) {
62+
unset($this->memoizer_cache[$key]);
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)