Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
76.47% covered (warning)
76.47%
13 / 17
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RevisionStoreCacheRecord
76.47% covered (warning)
76.47%
13 / 17
75.00% covered (warning)
75.00%
3 / 4
8.83
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getVisibility
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 loadFreshRow
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
3.79
1<?php
2/**
3 * A RevisionStoreRecord loaded from the cache.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23namespace MediaWiki\Revision;
24
25use MediaWiki\CommentStore\CommentStoreComment;
26use MediaWiki\Page\PageIdentity;
27use MediaWiki\Permissions\Authority;
28use MediaWiki\User\UserIdentity;
29
30/**
31 * A cached RevisionStoreRecord.  Ensures that changes performed "behind the back"
32 * of the cache do not cause the revision record to deliver stale data.
33 *
34 * @internal
35 * @since 1.33
36 */
37class RevisionStoreCacheRecord extends RevisionStoreRecord {
38
39    /**
40     * @var null|callable ( int $revId ): [ int $rev_deleted, UserIdentity $user ]
41     */
42    private $mCallback;
43
44    /**
45     * @note Avoid calling this constructor directly. Use the appropriate methods
46     * in RevisionStore instead.
47     *
48     * @param callable $callback Callback for loading data.
49     *        Signature: function ( int $revId ): [ int $rev_deleted, UserIdentity $user ]
50     * @param PageIdentity $page The page this RevisionRecord is associated with.
51     * @param UserIdentity $user
52     * @param CommentStoreComment $comment
53     * @param \stdClass $row A row from the revision table. Use RevisionStore::getQueryInfo() to build
54     *        a query that yields the required fields.
55     * @param RevisionSlots $slots The slots of this revision.
56     * @param false|string $wikiID Relevant wiki id or self::LOCAL for the current one.
57     */
58    public function __construct(
59        callable $callback,
60        PageIdentity $page,
61        UserIdentity $user,
62        CommentStoreComment $comment,
63        $row,
64        RevisionSlots $slots,
65        $wikiID = self::LOCAL
66    ) {
67        parent::__construct( $page, $user, $comment, $row, $slots, $wikiID );
68        $this->mCallback = $callback;
69    }
70
71    /**
72     * Overridden to ensure that we return a fresh value and not a cached one.
73     *
74     * @return int
75     */
76    public function getVisibility() {
77        if ( $this->mCallback ) {
78            $this->loadFreshRow();
79        }
80        return parent::getVisibility();
81    }
82
83    /**
84     * Overridden to ensure that we return a fresh value and not a cached one.
85     *
86     * @param int $audience
87     * @param Authority|null $performer
88     *
89     * @return UserIdentity The identity of the revision author, null if access is forbidden.
90     */
91    public function getUser( $audience = self::FOR_PUBLIC, ?Authority $performer = null ) {
92        if ( $this->mCallback ) {
93            $this->loadFreshRow();
94        }
95        return parent::getUser( $audience, $performer );
96    }
97
98    /**
99     * Load a fresh row from the database to ensure we return updated information
100     *
101     * @throws RevisionAccessException if the row could not be loaded
102     */
103    private function loadFreshRow() {
104        [ $freshRevDeleted, $freshUser ] = call_user_func( $this->mCallback, $this->mId );
105
106        // Set to null to ensure we do not make unnecessary queries for subsequent getter calls,
107        // and to allow the closure to be freed.
108        $this->mCallback = null;
109
110        if ( $freshRevDeleted !== null && $freshUser !== null ) {
111            $this->mDeleted = intval( $freshRevDeleted );
112            $this->mUser = $freshUser;
113        } else {
114            throw new RevisionAccessException(
115                'Unable to load fresh row for rev_id: {rev_id}',
116                [ 'rev_id' => $this->mId ]
117            );
118        }
119    }
120
121}