Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.77% covered (success)
96.77%
30 / 31
85.71% covered (warning)
85.71%
6 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ActorCache
96.77% covered (success)
96.77%
30 / 31
85.71% covered (warning)
85.71%
6 / 7
12
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getActor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getActorId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 add
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 remove
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 clear
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCachedValue
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\User;
8
9/**
10 * Simple in-memory cache for UserIdentity objects indexed by user ID,
11 * actor ID and user name.
12 *
13 * We cannot just use MapCacheLRU for this because of eviction semantics:
14 * we need to be able to remove UserIdentity from the cache even if
15 * user ID or user name has changed, so we track the most accessed VALUES
16 * in the cache, not keys, and evict them alongside with all their indexes.
17 *
18 * @since 1.37
19 * @internal For use by ActorStore
20 * @ingroup User
21 */
22class ActorCache {
23
24    /** @var string Key by actor ID */
25    public const KEY_ACTOR_ID = 'actorId';
26
27    /** @var string Key by user ID */
28    public const KEY_USER_ID = 'userId';
29
30    /** @var string Key by user name */
31    public const KEY_USER_NAME = 'name';
32
33    private int $maxSize;
34
35    /**
36     * @var array[][] Contains 3 keys, KEY_ACTOR_ID, KEY_USER_ID, and KEY_USER_NAME,
37     * each of which has a value of an array of arrays with actor ids and UserIdentity objects,
38     * keyed with the corresponding actor id/user id/user name
39     */
40    private $cache = [ self::KEY_ACTOR_ID => [], self::KEY_USER_NAME => [], self::KEY_USER_ID => [] ];
41
42    /**
43     * @param int $maxSize hold up to this many UserIdentity values
44     */
45    public function __construct( int $maxSize ) {
46        $this->maxSize = $maxSize;
47    }
48
49    /**
50     * Get user identity which has $keyType equal to $keyValue
51     * @param string $keyType one of self::KEY_* constants.
52     * @param string|int $keyValue
53     * @return UserIdentity|null
54     */
55    public function getActor( string $keyType, $keyValue ): ?UserIdentity {
56        return $this->getCachedValue( $keyType, $keyValue )['actor'] ?? null;
57    }
58
59    /**
60     * Get actor ID of the user which has $keyType equal to $keyValue.
61     * @param string $keyType one of self::KEY_* constants.
62     * @param string|int $keyValue
63     * @return int|null
64     */
65    public function getActorId( string $keyType, $keyValue ): ?int {
66        return $this->getCachedValue( $keyType, $keyValue )['actorId'] ?? null;
67    }
68
69    /**
70     * Add $actor with $actorId to the cache.
71     * @param int $actorId
72     * @param UserIdentity $actor
73     */
74    public function add( int $actorId, UserIdentity $actor ) {
75        while ( count( $this->cache[self::KEY_ACTOR_ID] ) >= $this->maxSize ) {
76            $evictId = array_key_first( $this->cache[self::KEY_ACTOR_ID] );
77            $this->remove( $this->cache[self::KEY_ACTOR_ID][$evictId]['actor'] );
78        }
79        $value = [ 'actorId' => $actorId, 'actor' => $actor ];
80        $this->cache[self::KEY_ACTOR_ID][$actorId] = $value;
81        $userId = $actor->getId( $actor->getWikiId() );
82        if ( $userId ) {
83            $this->cache[self::KEY_USER_ID][$userId] = $value;
84        }
85        $this->cache[self::KEY_USER_NAME][$actor->getName()] = $value;
86    }
87
88    public function remove( UserIdentity $actor ) {
89        $oldByName = $this->cache[self::KEY_USER_NAME][$actor->getName()] ?? null;
90        $oldByUserId = $this->cache[self::KEY_USER_ID][$actor->getId( $actor->getWikiId() )] ?? null;
91        if ( $oldByName ) {
92            unset( $this->cache[self::KEY_USER_ID][$oldByName['actor']->getId( $oldByName['actor']->getWikiId() )] );
93            unset( $this->cache[self::KEY_ACTOR_ID][$oldByName['actorId']] );
94        }
95        if ( $oldByUserId ) {
96            unset( $this->cache[self::KEY_USER_NAME][$oldByUserId['actor']->getName()] );
97            unset( $this->cache[self::KEY_ACTOR_ID][$oldByUserId['actorId']] );
98        }
99        unset( $this->cache[self::KEY_USER_NAME][$actor->getName()] );
100        unset( $this->cache[self::KEY_USER_ID][$actor->getId( $actor->getWikiId() )] );
101    }
102
103    /**
104     * Remove everything from the cache.
105     * @internal
106     */
107    public function clear() {
108        $this->cache = [ self::KEY_ACTOR_ID => [], self::KEY_USER_NAME => [], self::KEY_USER_ID => [] ];
109    }
110
111    /**
112     * @param string $keyType one of self::KEY_* constants.
113     * @param string|int $keyValue
114     * @return array|null [ 'actor' => UserIdentity, 'actorId' => int ]
115     */
116    private function getCachedValue( string $keyType, $keyValue ): ?array {
117        if ( isset( $this->cache[$keyType][$keyValue] ) ) {
118            $cached = $this->cache[$keyType][$keyValue];
119            $actorId = $cached['actorId'];
120            // Record the actor with $actorId was recently used.
121            $item = $this->cache[self::KEY_ACTOR_ID][$actorId];
122            unset( $this->cache[self::KEY_ACTOR_ID][$actorId] );
123            $this->cache[self::KEY_ACTOR_ID][$actorId] = $item;
124            return $cached;
125        }
126        return null;
127    }
128}