User:Daniel Kinzler (WMDE)/MCR-SlotRoleHandler


Sketch of the interface for SlotRoleHandler and SlotRoleRegistry (task T194046), as well as SlotParserOutputProvider, RenderedRevision, and RevisionRenderer:



SlotRoleHandler is a base class. It can be extended to override some behaviors. Making this a concrete class allows us to add more methods later, without making a breaking change. Since most slots do not need custom behavior, this seems a good approach.

The below design assumes that a given SlotRoleHandler instance may support multiple content models. The default SlotRoleHandler implementation should indeed work for any model.


  • getRole() returns the role name this SlotRoleHandler is associated with.
  • getSlotOutput( RevisionRecord $rev, ParserOptions $options, $mode = self::FOR_COMBINED_OUTPUT ) {.
    • Returns ParserOutput representing the slot's content. The content may be for "solo" display or for being combined with other slot's output, or be "blind" output with no HTML, depending on the $mode" parameter. The ParserOutput is required to contain all meta-information for use in HTML head and HTTP header as well as all tracking information to be written to the database.
    • $rev The revision to retrieve the content from. The content rendered is the one returned by $rev->getContent( $this->getRole() ). The User object returned by $options->getUser() is used for audience checks [CONFIRM; this may impact the cache key]. The whole revision is supplied since in the future, the rendering of a slot may depend on the content of other slots. Note that the rendering may depend on the title (resp namespace) the revision belongs to; if this is the case, $rev->getPageAsLinktarget() is called to get the necessary information.
    • $options ParserOptions to use for rendering. Among other things, they provide access to the User access to use for audience checks, and may provide a predictive revision ID for use with unsaved revisions. [TBD: should setCurrentRevisionCallback() be called on $options always? Can we provide good default behavior for setSpeculativeRevIdCallback() too?]
    • $mode controls whether the output is generated for a single-slot view, for being combiend with other slots' output, or just for secondary updates.
  • getSlotHeading(): Message. Returns a message to be used as the text of the section header for the slot, e.g. when showing diffs for multiple slots.
  • getOutputPlacementHints() returns an associative array of hints that tell calling code where on the page the HTML returned by getRawHtml should be placed. Calling code is free to ignore these hints.
  • MAYBE getOutputContribution( Content $content, SlotParserOutputProvider $provider ), combining the functionality of getRawHtml() and getOutputPlacementHints():
    • Returns an associative array with the following fields:
      • html: HTML representing the slot's content.
      • placement: associative array of hints that tell calling code where on the page the HTML, with will known fields like:
        • area: center, top, bottom, etc.
        • weight: -1 for prepend, +1 for append, +2 for append after all +1, etc.
  • getDefaultContentModel( LinkTarget $title ) returns the default model to use for this slot on the given page.
  • supportsRedirects() whether this slot can contain redirects. Returns false per default for non-main slots.
  • getArticleCountMethod() corresponds to $wgArticleCountMethod. Returns per default 'none' for non-main slots, to indicate that the slot doesn't impact countability.
  • __construct( $role, MessageLocalizer $localizer )
    • $role the name of the role handled by this handler.
    • $localizer used to determine the heading text. They message key is constructed using the role name as a suffix.

We will probably add more methods later, e.g. for determining whether content "counts" as an article, whether it constitutes a redirect, etc.


The main slot is special in some ways, so we'll want a dedicated subclass.


  • getRole() returns "main"
  • getSlotOutput( RevisionRecord $rev, ParserOptions $options, $mode = self::FOR_COMBINED_OUTPUT ) returns the main slot's output. Since the page's title is available via $rev->getPageAsLinkTarget(), behavior can differ based on the title/namespace/page type. Since the content's model is available, behavior may also differ based on that. A mechanism for extensions to control the behavior of the main slot based on model or title is beyond the scope of this spec.
  • getSlotHeading(): Returns a message saying "main content" or "main slot", for use e.g. on diff pages showing diffs of multiple slots, etc.
  • getOutputPlacementHints() returns the equivalent of "front and center".
  • getDefaultContentModel( LinkTarget $title ) returns ContentHandler::getDefaultModelFor( $title ). This is the main reason for this class to exist.
  • supportsRedirects() Returns true.
  • getArticleCountMethod() returns $wgArticleCountMethod.


Note: this differs from the original design in that we assume that the a SlotRoleHandler is determined based on the role and the title. Typically, only the role is relevant, but in some cases, especially for the main slot, the handler may be overwritten, perhaps based on the namespace (or page type). The original idea was to assume that a SlotRoleHandler is determined based on the role and content model, which meant that for an existing page, we needed getRoleHandler( SlotRecord ), while for creating a new page, we needed getDefaultRoleHandler( $role, LinkTarget $title ). That however binds a given SlotRoleHandler to a single content model, which seems unnecessary.


  • defineRole( $role, callable $instantiator ):
    • $role the name of the role to define.
    • $instantiator a callback returning a SlotRoleHandler. Will get a
    • we may want to split this into defineCustomRole( $role, callable $instantiator ) and defineRole( $role, $defaultModel, $outputPlacementHints ). This makes it easier for extensions that want to define a new slot but do not need to override any behavior, which would be the usual case.
  • getRoleHandler( $role ) Provides a handler for the given role.
    • returns a SlotRoleHandler.
    • $role the role to provide a SlotHandler for.
  • getAllowedRoles( $title ): returns a list of roles supported for the given title. This informs the UI layer which roles should be offered when creating or editing the page. This does not guarantee that no other slots may exist on the page. If additional slots exist, they must at least be "looped through" all edits, and must be accessible when addressed explicitly.
  • getRequiredRoles( $title ): returns a list of roles required for the given title. This informs the UI which roles to require during user interaction. This does not guarantee that no revisions exist on the page that do not have all of these slots. All operations must still be possible if any of the slots missing (except the main slot).
  • getDefinedRoles() returns the list of roles defined by calling defineRole().
  • getKnownRoles() returns the list of known roles, including the ones returned by getDefinedRoles(), and roles that exist in the database. This means the SlotRoleRegistery needs access to the appropriate NameTableStore instance.


A RenderedRevision provides (cached, lazy) access to a ParserOutput object that represents the revision's content rendered for some audience. Implements SlotParserOutputProvider.


  • getRevisionParserOutput() returns a ParserOutput that combines the output of the slots of the revision, as generated by RevisionRenderer. The ParserOutput is generated when this emthod is first called, and then cached internally.
  • getSlotParserOutput( $role, $generateHtml = true ) returns a ParserOutput for the given slot. This ParserOutput is constructed by calling Revision::getRevisionParserOutput, and then cached for later use.
  • getParserOptions(): returns the ParserOptions supplied to the constructor.
  • getRevision(): returns the RevisionRecord supplied to the constructor.
  • POSSIBLY: setRevision( RevisionRecord $rec ): updates the RevisionRecord associated with this RenderedRevision; only allowed if the new RevisionRecord has the same content (resp hash) as the old one. May keep or purge ParserOutput cached inside the RenderedRevision, depending on whether the revision ID changed (that is, become known after saving) and whether the ParserOutput depended on the revision ID or other properties of the revision.
  • __construct( RevisionRecord $revision, ParserOptions $options, callable $outputCombiner, User $forUser = null )
    • $revision is the revision this RenderedRevision represents the rendering of.
    • $options the parser options to use when constructing ParserOutput.
    • $outputCombiner the callback for constructing the combined ParserOutput.
    • $forUser the target audience, for access restrictions. Rationale: Needed because this calls RevisionRecord::getContent() internally, which performs an audience check. Supplying a user causes the FOR_USER mode to be used instead of FOR_PUBLIC, FOR_USER is needed e.g. when a sysop looks at a deleted page. RAW mode is not supported, since a a rendering always has an audience, and RAW is intended for situations where there is none (e.g. internal copying of data) [VERIFY].


Stateless service that acts as a factory for RenderedRevision instances.


  • getRenderedRevision( RevisionRecord $rev, ParserOptions $options = null, User $forUser = null ): RenderedRevision. See RenderedRevision::__construct.
    • $rev is the revision to render
    • $options the options to use. If not given and $forUser is not given, canonical options will be used. If $forUser is given, user-specific options will be used.
    • $forUser a user, for access permissions and custom parser options. Optional.

We may want to introduce an additional method, getRevisionOutput() or renderRevison(), to use instead of getRenderedRevision()->getRevisionParserOutput().