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