Page MenuHomePhabricator

Refactor patrol logic into Patrol service class
Open, Needs TriagePublic

Description

@Reedy pointed out some code smell with the patrol feature in T257005.

The implementation of the patrol feature is distributed across a variety of core classes:

  • The update API is in RecentChange (doMarkPatrolled, reallyMarkPatrolled), but it only uses public methods of RecentChange so it could be moved elsewhere.
  • The User methods raised in T257005 could similarly be moved.
  • There is ChangesList::isUnpatrolled(), a public static method called from 6 different classes, apparently just put in ChangesList for lack of a better place.
  • RevisionStore::getRcIdIfUnpatrolled() has a todo "This is overly specific, so move or kill this method"
  • PatrolLog is just a holder for a public static method with more patrol-related logic. It's a helper for RecentChange::doMarkPatrolled() which is also called by the PageTriage extension.
  • RollbackPage has a todo comment asking for its 5 lines of patrol-related code to be moved to EditPage::attemptSave(). Same for WikiPage::doUserEditContent().
  • IRCColourfulRCFeedFormatter and MachineReadableRCFeedFormatter need the patrol-related config globals to help them interpret a RecentChange object. A patrol class could help them with that.

I think changes list special pages are mostly OK, except that they call things that will be moved. The aim is to get rid of direct access to the patrol-related config variables, and to reduce the size of massive classes like User and RecentChange by giving patrol code its own place to live.

Other UI code:

  • The patrol footer UI is in Article::showPatrolFooter(), and it uses a cache, necessitating a public static method Article::purgePatrolFooterCache() to invalidate that cache when a file is uploaded. It could use the checkKeys option to WANObjectCache::getWithSetCallback(). A backend method in a patrol class would purge the check key, implicitly invalidating the patrol footer cache, so that the file upload code would not have to know about the UI.
  • DifferenceEngine builds links to MarkpatrolledAction in markPatrolledLink().
  • PatrolLogFormatter is pretty conventional and could probably stay where it is.

Proposed interface summary:

<?php

namespace MediaWiki;

use MediaWiki\Config\ServiceOptions;
use MediaWiki\Permissions\Authority;
use MediaWiki\User\UserIdentity;
use RecentChange;

class Patrol {
	public function __construct( ServiceOptions $options ) {}

	// Storage
	public function markPatrolled( RecentChange $change, Authority $performer,
		$auto = false, $tags = null ) {}
	public function reallyMarkPatrolled( RecentChange $rc ) {}
	public function recordLogEvent( $rc, $auto, UserIdentity $user, $tags = null ) {} // formerly PatrolLog::record()

	// Retrieval
	public function isChangeUnpatrolled( RecentChange $rc, Authority $user ) {}
	public function isRowUnpatrolled( $rcRow, Authority $user ) {}
	public function getRcIdIfUnpatrolled( RecentChange $rc ) {}
	public function isChangePatrollable( RecentChange $rc ) {} // for IRCColourfulRCFeedFormatter

	// Config
	public function isRCPatrolEnabled() {}
	public function isNewPagesPatrolEnabled() {}
	public function isFilePatrolEnabled() {}

	// Authorization
	public function isRCPatrolAllowed( Authority $authority ) {
		return $this->isRCPatrolEnabled() && $authority->isAllowedAny( 'patrol', 'patrolmarks' );
	}
	public function isNewPagesPatrolAllowed( Authority $authority ) {}
	public function isFilePatrolAllowed( Authority $authority ) {}
}

Maybe you could argue for splitting this up into more classes. Maybe the storage methods in particular should be split off to avoid the need to inject a LoadBalancer for the rest of it.

I used "NewPages" in the proposed methods above instead of NP. I can see why the author used NP, but RC is a common abbreviation whereas NP is not.

Event Timeline

Pchelolo subscribed.

after a brief conversation with @tstarling over IRC, I'm taking this into expedition - this fit very nicely into our agenda of slaying the monsters

Krinkle renamed this task from Patrol refactor to Refactor patrol logic into Patrol service class.May 20 2021, 3:29 AM
Krinkle added a project: MediaWiki-Patrolling.
Krinkle awarded a token.

Many methods on the proposed service take a RecentChange and an Authority. This makes me think that it may be convenient to model this as a "command" or "entity", with a factory for creating them.

This is mostly a matter of style though. If we would never want to pass such objects around, we can also just stick with the stateless service.