Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
62.86% covered (warning)
62.86%
22 / 35
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
BlockCacheKey
62.86% covered (warning)
62.86%
22 / 35
42.86% covered (danger)
42.86%
3 / 7
43.60
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 requestEquals
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
4.07
 matchesStored
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 getPartialKey
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 isUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 dumpWeakRef
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 __toString
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace MediaWiki\Block;
4
5use MediaWiki\Request\WebRequest;
6use MediaWiki\User\UserIdentity;
7use WeakReference;
8
9/**
10 * @internal For use by BlockManager
11 */
12class BlockCacheKey {
13    /** Whether the key includes a non-null request */
14    private bool $hasRequest;
15    /** A reference to the WebRequest or null */
16    private ?WeakReference $requestRef;
17    /** A reference to the UserIdentity */
18    private WeakReference $userRef;
19    /** Whether the UserIdentity has zero user ID */
20    private bool $isAnon;
21    /** The part of the key indicating whether to do queries against the primary DB */
22    private bool $fromPrimary;
23
24    /**
25     * @param WebRequest|null $request
26     * @param UserIdentity $user
27     * @param bool $fromPrimary
28     */
29    public function __construct( ?WebRequest $request, UserIdentity $user, bool $fromPrimary ) {
30        $this->requestRef = $request ? WeakReference::create( $request ) : null;
31        $this->hasRequest = (bool)$request;
32        $this->userRef = WeakReference::create( $user );
33        $this->isAnon = $user->getId() === 0;
34        $this->fromPrimary = $fromPrimary;
35    }
36
37    /**
38     * Compare the request part of the key with another key
39     *
40     * @param BlockCacheKey $other
41     * @return bool
42     */
43    private function requestEquals( self $other ): bool {
44        if ( $this->hasRequest !== $other->hasRequest ) {
45            return false;
46        } elseif ( $this->hasRequest ) {
47            return $this->requestRef->get()
48                && $this->requestRef->get() === $other->requestRef->get();
49        } else {
50            return true;
51        }
52    }
53
54    /**
55     * Determine whether a new key matches a stored key sufficiently well to
56     * allow the stored cache entry to be returned.
57     *
58     * If a WeakReference in either key has expired, that is considered as
59     * a mismatch.
60     *
61     * @param BlockCacheKey $storedKey
62     * @return bool
63     */
64    public function matchesStored( self $storedKey ): bool {
65        return $this->requestEquals( $storedKey )
66            && $this->userRef->get()
67            && $this->userRef->get() === $storedKey->userRef->get()
68            && (
69                ( $this->fromPrimary === $storedKey->fromPrimary )
70                // If we have data from the primary, allow it to be returned
71                // when replica data is requested.
72                || ( !$this->fromPrimary && $storedKey->fromPrimary )
73            );
74    }
75
76    /**
77     * Get the bucket for the cache entry associated with this key.
78     * Entries with the same partial key will replace each other in the cache.
79     *
80     * @return string
81     */
82    public function getPartialKey(): string {
83        if ( $this->hasRequest ) {
84            return 'req';
85        }
86        return $this->isAnon ? 'anon' : 'user';
87    }
88
89    /**
90     * Determine whether the specified user is the user stored in the key. This
91     * is used for cache invalidation.
92     *
93     * If the WeakReference has expired, it is not possible to serve a cache
94     * hit for the user, so that is considered to be a mismatch.
95     *
96     * @param UserIdentity $user
97     * @return bool
98     */
99    public function isUser( UserIdentity $user ): bool {
100        return $this->userRef->get() === $user;
101    }
102
103    /**
104     * @param WeakReference|null $ref
105     * @return string
106     */
107    private static function dumpWeakRef( ?WeakReference $ref ): string {
108        if ( $ref === null ) {
109            return 'none';
110        } else {
111            $obj = $ref->get();
112            if ( $obj ) {
113                return '#' . spl_object_id( $obj );
114            } else {
115                return 'expired';
116            }
117        }
118    }
119
120    /**
121     * Convert to a string, for debugging
122     *
123     * @return string
124     */
125    public function __toString(): string {
126        return 'BlockCacheKey{' .
127            'request=' . self::dumpWeakRef( $this->requestRef ) . ',' .
128            'user=' . self::dumpWeakRef( $this->userRef ) . ',' .
129            ( $this->fromPrimary ? 'primary' : 'replica' ) .
130            '}';
131    }
132}