User:Daniel Kinzler (WMDE)/MCR-SlotRoleHandler

Sketch of the interface for SlotRoleHandler and SlotRoleRegistry, as well as SlotParserOutputProvider, RenderedRevision, and RevisionRenderer:

SlotRoleHandler
Considerations:

SlotRoleHandler is a base class. It can be extended to override some behaviors. This 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.

Interface:


 * getRole returns the role name this SlotRoleHandler is associated with.
 * getRawHtml( SlotRecord $record, SlotParserOutputProvider $provider, ParserOutput $target = null ).
 * Returns HTML for the content of record, wrapped in a div, perhaps together with some kind of heading. Calling code is expected to combine this HTML with the HTML from other slots and then pass it to target->setText.
 * $provider is a SlotParserOutputProvider used to acquire a ParserOutput object for the slot's content, if this is needed to generate the HTML. This is typically but not necessarily the case. Rationale: if a ParserOutput object was already previously created by the caller (e.g. for use by hook handlers or to determine DataUpdates), it should be re-used. Similarly, if a ParserOutput object is later needed by the caller, the one used for getRawHtml should be re-used. This follows the in-place caching approach of the current architecture. The alternative would be to introduce an in-process parser service that returns a ParserOutput for a ParserOutput and a RevisionRecord, and use that.
 * If $target is passed to getRawHtml, all the necessary head-items and directives will be registered in it, but no link tracking data is copied. Calling code is expected to do that. This enforces a separation of meta-data needed for display from derived data to be stored in the database.
 * getSlotHeadingText: 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.
 * __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.

SlotRoleRegistry
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.

Interface:


 * defineRole( $role, $defaultModel, callable $instantiator ):
 * $role the name of the role to define.
 * $defaultModel the default content model to use for this role. May be overwritten, especially for the main slot.
 * $instantiator a callback returning a SlotRoleHandler
 * we may want to split this into defineCustomRole( $role, $defaultModel, 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, $title ) Provides a handler for the given role. Note that the same role may have different handlers depending on the page title - particularly for the main slot (maybe only for the main slot).
 * returns a SlotRoleHandler.
 * $role the role to provide a SlotHandler for.
 * $title could be a PageIdentity (to be introduced) instead of a LinkTarget. Rationale: allows the SlotRoleHandler for the main slot to be overwritten for some pages (perhaps based on namespace).
 * getDefaultContentModel( $role, LinkTarget $title ) returns the default model to use for the given slot on the given page. This should only be used when creating a new page, otherwise a slot will just keep the model it has in the latest revision. The default content model returned here may be different from the one registered with the role, specially for the main slot.
 * we could instead make getDefaultContentModel( $title ) a method of the SlotRoleHandler.
 * we may also want isSupportedContentModel( $role, LinkTarget $title ). Many slots will only support one very specific model, while some may support a wide range of models.
 * 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.

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

Interface:


 * getParserOutput 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::getParserOutput, 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, RevisionOutputCombiner $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 RevisionOutputCombiner service to use to construct 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].

RevisionRenderer
Stateless service that acts as a factory for RenderedRevision instances.

Interface:


 * makeRevisionOutput( RenderedRevision $renderedRev ) see RevisionOutputCombiner below.
 * 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->getParserOutput.

RevisionOutputCombiner
TODO: better name.

Interface that exposes makeRevisionOutput to RenderedRevision while avoiding a circular dependency between RevisionRenderer and RenderedRevision.

Interface:


 * makeRevisionOutput( $renderedRev ) combines the HTML for all slots into a single ParserOutput object. Relies on SlotRoleHandler::getRawHtml internally.