wikimedia/mediawiki-extensions-Wikibase

View on GitHub
lib/includes/Formatters/DispatchingSnakFormatter.php

Summary

Maintainability
A
35 mins
Test Coverage
<?php

declare( strict_types = 1 );

namespace Wikibase\Lib\Formatters;

use InvalidArgumentException;
use ValueFormatters\FormattingException;
use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup;
use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookupException;
use Wikibase\DataModel\Snak\Snak;

/**
 * DispatchingSnakFormatter will format a Snak by delegating the formatting to an appropriate
 * SnakFormatter based on the snak type or the associated property's data type.
 *
 * @license GPL-2.0-or-later
 * @author Daniel Kinzler
 */
class DispatchingSnakFormatter implements SnakFormatter {

    /**
     * @var string One of the SnakFormatter::FORMAT_... constants.
     */
    private $format;

    /**
     * @var PropertyDataTypeLookup
     */
    private $dataTypeLookup;

    /**
     * @var SnakFormatter[]
     */
    private $formattersByDataType;

    /**
     * @var SnakFormatter[]
     */
    private $formattersBySnakType;

    /**
     * @param string $format The output format generated by this formatter. All SnakFormatters
     *  provided via $formattersBySnakType and $formattersByDataType must be safe for this
     *  output format. This is checked by comparing the $format with what each SnakFormatter
     *  returns from getFormat(). MIME parameters are ignored for this check, so FORMAT_HTML
     *  is considered compatible with FORMAT_HTML_DIFF, etc.
     * @param PropertyDataTypeLookup $dataTypeLookup
     * @param SnakFormatter[] $formattersBySnakType An associative array mapping snak types
     *  to SnakFormatter objects. If no formatter is defined for the a given snak type,
     *  $formattersByDataType will be checked for a SnakFormatter for the snak's data type.
     * @param SnakFormatter[] $formattersByDataType An associative array mapping data types
     *  to SnakFormatter objects. If no formatter is defined for the a given data type,
     *  the "*" key in this array is checked for a default formatter.
     *
     * @throws InvalidArgumentException If any of the given formatters is incompatible
     *         with $format. Formats are assumed to be represented by MIME types,
     *         MIME parameters are ignored.
     */
    public function __construct(
        string $format,
        PropertyDataTypeLookup $dataTypeLookup,
        array $formattersBySnakType,
        array $formattersByDataType
    ) {
        $this->assertFormatterArray( $format, $formattersBySnakType );
        $this->assertFormatterArray( $format, $formattersByDataType );

        $this->format = $format;
        $this->dataTypeLookup = $dataTypeLookup;
        $this->formattersBySnakType = $formattersBySnakType;
        $this->formattersByDataType = $formattersByDataType;
    }

    /**
     * @param string $format MIME type
     * @param SnakFormatter[] $formatters
     *
     * @throws InvalidArgumentException
     */
    private function assertFormatterArray( $format, array $formatters ) {
        foreach ( $formatters as $type => $formatter ) {
            if ( !is_string( $type ) ) {
                throw new InvalidArgumentException( 'formatter array must map type IDs to formatters.' );
            }

            if ( !( $formatter instanceof SnakFormatter ) ) {
                throw new InvalidArgumentException( 'formatter array must contain instances of SnakFormatter.' );
            }

            // Ignore MIME parameters when checking output format. We only care that the base format
            // is the same, so we can assume that all formatters apply the correct escaping and are
            // safe to use.
            if ( $this->getBaseFormat( $formatter->getFormat() ) !== $this->getBaseFormat( $format ) ) {
                throw new InvalidArgumentException( 'The formatter supplied for ' . $type
                    . ' produces ' . $formatter->getFormat() . ', but we expect ' . $format . '.' );
            }
        }
    }

    /**
     * @param string $format MIME type
     *
     * @return string MIME type with parameters stripped.
     */
    private function getBaseFormat( $format ) {
        return preg_replace( '/ *;.*$/', '', $format );
    }

    /**
     * @param Snak $snak
     *
     * @throws PropertyDataTypeLookupException
     * @return string The Snak's data type
     */
    private function getSnakDataType( Snak $snak ) {
        return $this->dataTypeLookup->getDataTypeIdForProperty( $snak->getPropertyId() );
        // @todo: wrap the PropertyDataTypeLookupException, but make sure ErrorHandlingSnakFormatter still handles it.
    }

    /**
     * @see SnakFormatter::formatSnak
     *
     * Formats the given Snak by finding an appropriate formatter among the ones supplied
     * to the constructor, and applying it.
     *
     * @param Snak $snak
     *
     * @throws FormattingException
     * @throws PropertyDataTypeLookupException
     * @return string The formatted snak value, in the format specified by getFormat().
     */
    public function formatSnak( Snak $snak ) {
        $snakType = $snak->getType();

        if ( isset( $this->formattersBySnakType[$snakType] ) ) {
            $formatter = $this->formattersBySnakType[$snakType];
            return $formatter->formatSnak( $snak );
        }

        $dataType = $this->getSnakDataType( $snak );

        if ( isset( $this->formattersByDataType["PT:$dataType"] ) ) {
            $formatter = $this->formattersByDataType["PT:$dataType"];
            return $formatter->formatSnak( $snak );
        }

        if ( isset( $this->formattersByDataType['*'] ) ) {
            $formatter = $this->formattersByDataType['*'];
            return $formatter->formatSnak( $snak );
        }

        throw new FormattingException( "No formatter found for snak type $snakType and data type $dataType" );
    }

    /**
     * @see SnakFormatter::getFormat
     *
     * @return string One of the SnakFormatter::FORMAT_... constants.
     */
    public function getFormat() {
        return $this->format;
    }

}