jenkins-bot has submitted this change and it was merged. Change subject: Register factory callbacks for value types as well as data types. ......................................................................
Register factory callbacks for value types as well as data types. This allows all kinds of factory callbacks to be registered by value type instead of per property data type. This avoids redundant declaration, and provides a fallback mechanism for new/unknown data types. In addition, this allows extensions to add handling for new value types, not just data types. Note that teh fallback my be applies by DataTypeDefinitions (when using the default RESOLVED_MODE) or by factory classes that are aware of the VT and PT prefixes (using PREFIXED_MODE). Bug: T118499 Change-Id: I0666f1ea97a3a3caf7233f28c880452e891a21b6 --- M client/WikibaseClient.datatypes.php M client/includes/WikibaseClient.php M docs/datatypes.wiki M lib/WikibaseLib.datatypes.php M lib/includes/DataTypeDefinitions.php M lib/includes/formatters/WikibaseValueFormatterBuilders.php M lib/tests/phpunit/DataTypeDefinitionsTest.php M lib/tests/phpunit/formatters/WikibaseValueFormatterBuildersTest.php M repo/Wikibase.php M repo/WikibaseRepo.datatypes.php M repo/includes/WikibaseRepo.php 11 files changed, 292 insertions(+), 419 deletions(-) Approvals: Aude: Looks good to me, approved jenkins-bot: Verified diff --git a/client/WikibaseClient.datatypes.php b/client/WikibaseClient.datatypes.php index e62c5ea..ac41d79 100644 --- a/client/WikibaseClient.datatypes.php +++ b/client/WikibaseClient.datatypes.php @@ -23,7 +23,10 @@ */ use ValueFormatters\FormatterOptions; +use ValueFormatters\StringFormatter; use Wikibase\Client\WikibaseClient; +use Wikibase\Lib\SnakFormatter; +use Wikibase\Lib\UnDeserializableValueFormatter; return call_user_func( function() { // NOTE: 'formatter-factory-callback' callbacks act as glue between the high level interface @@ -32,55 +35,57 @@ // WikibaseValueFormatterBuilders should be used *only* here, program logic should use a // OutputFormatValueFormatterFactory as returned by WikibaseClient::getValueFormatterFactory(). + // NOTE: Factory callbacks are registered below by value type (using the prefix "VT:") or by + // property data type (prefix "PT:"). + return array( - 'commonsMedia' => array( + 'VT:bad' => array( 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { - $factory = WikibaseClient::getDefaultFormatterBuilders(); - return $factory->newCommonsMediaFormatter( $format, $options ); - }, + return $format === SnakFormatter::FORMAT_PLAIN ? new UnDeserializableValueFormatter( $options ) : null; + } ), - 'globe-coordinate' => array( + 'VT:globecoordinate' => array( 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { $factory = WikibaseClient::getDefaultFormatterBuilders(); return $factory->newGlobeCoordinateFormatter( $format, $options ); }, ), - 'monolingualtext' => array( + 'VT:monolingualtext' => array( 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { $factory = WikibaseClient::getDefaultFormatterBuilders(); return $factory->newMonolingualFormatter( $format, $options ); }, ), - 'quantity' => array( + 'VT:quantity' => array( 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { $factory = WikibaseClient::getDefaultFormatterBuilders(); return $factory->newQuantityFormatter( $format, $options ); }, ), - 'string' => array( + 'VT:string' => array( 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { - return null; // rely on formatter for string value type + return $format === SnakFormatter::FORMAT_PLAIN ? new StringFormatter( $options ) : null; }, ), - 'time' => array( - 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { - $factory = WikibaseClient::getDefaultFormatterBuilders(); - return $factory->newTimeFormatter( $format, $options ); - }, - ), - 'url' => array( + 'PT:url' => array( 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { $factory = WikibaseClient::getDefaultFormatterBuilders(); return $factory->newUrlFormatter( $format, $options ); }, ), - 'wikibase-item' => array( + 'PT:commonsMedia' => array( 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { $factory = WikibaseClient::getDefaultFormatterBuilders(); - return $factory->newEntityIdFormatter( $format, $options ); + return $factory->newCommonsMediaFormatter( $format, $options ); }, ), - 'wikibase-property' => array( + 'VT:time' => array( + 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { + $factory = WikibaseClient::getDefaultFormatterBuilders(); + return $factory->newTimeFormatter( $format, $options ); + }, + ), + 'VT:wikibase-entityid' => array( 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { $factory = WikibaseClient::getDefaultFormatterBuilders(); return $factory->newEntityIdFormatter( $format, $options ); diff --git a/client/includes/WikibaseClient.php b/client/includes/WikibaseClient.php index 1ec28e4..331c0d0 100644 --- a/client/includes/WikibaseClient.php +++ b/client/includes/WikibaseClient.php @@ -609,36 +609,11 @@ } /** - * Constructs an array of factory callbacks for ValueFormatters, keyed by property type - * (data type) prefixed with "PT:", or value type prefixed with "VT:". This matches to - * convention used by OutputFormatValueFormatterFactory and DispatchingValueFormatter. - * - * @return callable[] - */ - private function getFormatterFactoryCallbacksByType() { - $callbacks = array(); - - $valueFormatterBuilders = $this->newWikibaseValueFormatterBuilders(); - $valueTypeFormatters = $valueFormatterBuilders->getFormatterFactoryCallbacksByValueType(); - $dataTypeFormatters = $this->dataTypeDefinitions->getFormatterFactoryCallbacks(); - - foreach ( $valueTypeFormatters as $key => $formatter ) { - $callbacks["VT:$key"] = $formatter; - } - - foreach ( $dataTypeFormatters as $key => $formatter ) { - $callbacks["PT:$key"] = $formatter; - } - - return $callbacks; - } - - /** * @return OutputFormatValueFormatterFactory */ private function newValueFormatterFactory() { return new OutputFormatValueFormatterFactory( - $this->getFormatterFactoryCallbacksByType(), + $this->dataTypeDefinitions->getFormatterFactoryCallbacks( DataTypeDefinitions::PREFIXED_MODE ), $this->getContentLanguage(), $this->getLanguageFallbackChainFactory() ); diff --git a/docs/datatypes.wiki b/docs/datatypes.wiki index 6a8823a..1a6a152 100644 --- a/docs/datatypes.wiki +++ b/docs/datatypes.wiki @@ -1,36 +1,54 @@ -This document describes the concept of data types as used by Wikibase. +This document describes the concept of property data types as used by Wikibase. + == Overview == -Data types in Wikibase are rather insubstantial: they are modelled by DataType objects, +Property data types in Wikibase are rather insubstantial: they are modelled by DataType objects, but such objects do not define any functionality of themselves. They merely act as a type safe ID for the data type. -Data types are used to declare which kinds of values can be associated with a Property in a -Snak. For each data type, the following things are defined: +Property data types are used to declare which kinds of values can be associated with a Property +in a Snak. For each data type, the following things are defined: -* the type of DataValue to use for values -* a localized name and description of the data type -* ValueValidators that impose constraints on the allowed values -* A ValueParser for parsing user input -* Formatters for rendering values of the given type to various target formats. -* RDF mappings for representing values of the given type in RDF. +* The type of DataValue to use for values (the "value type"). +* A localized name and description of the data type. +* ValueValidators that impose constraints on the allowed values. +* A ValueParser for parsing user input for a given type of property. +* Formatters for rendering snaks and values of the given type to various target formats. +* RDF mappings for representing snaks and values of the given type in RDF. == Data Type Definitions == -Data types are defined in the global $wgWBRepoDataTypes and $wgWBClientDataTypes arrays, +Property data types are defined in the global $wgWBRepoDataTypes and $wgWBClientDataTypes arrays, respectively. These arrays are constructed at bootstrap time in Wikibase.php resp. WikibaseClient.php based on the information returned when including the files WikibaseLib.datatypes.php, Wikibase.datatypes.php, and WikibaseClient.datatypes.php, respectively. -$wgWBRepoDataTypes and $wgWBClientDataTypes are associative arrays that map data type IDs to a -data type definition record. Each such record has the following fields: -* value-type (repo and client): the data value type ID identifying the low level value type to use for this data type. Logically, the value type defines the structure of the value, while the data type defines constraints on the value. +$wgWBRepoDataTypes and $wgWBClientDataTypes are associative arrays that map property data types +and value types to a set of constructor callbacks (aka factory methods). + +Property data types and value types are used as keys in the $wgWBRepoDataTypes and +$wgWBClientDataTypes. They are distinguished by the prefixes "VT:" and "PT:". For instance, +the string value type would use the key "VT:string", while the url data type would use the +key "PT:url". + +Logically, the value type defines the structure of the value, while the property data +type defines the interpretation of the value. Property data types may impose additional +constraints on the values, or impact how they are rendered or exported. + +Each key is associated with a map that provides the following fields: +* value-type (repo and client): the value type to use for this data type (not used for value type keys). * validator-factory-callback (repo only): a callable that acts as a factory for the list of validators that should be used to check any user supplied values of the given data type. The callable will be called without any arguments, and must return a list of ValueValidator objects. * parser-factory-callback (repo only): a callable that acts as a factory for a ValueParser for this data type. -* formatter-factory-callback (repo and client): (PLANNED) a callable that acts as a factory for ValueFormatters for use with this data type. +* formatter-factory-callback (repo and client): a callable that acts as a factory for ValueFormatters for use with this data type. * rdf-builder-factory-callback (repo only): a callable that acts as a factory for ValueSnakRdfBuilder for use with this data type. +Since for each property data type the associated value type is known, this provides a convenient +fallback mechanism: If a desired callback field isn't defined for a given property data type, +we can fall back to using the callback that is defined for the value type. For example, if +there is no formatter-factory-callback field associated with the PT:url key, we may use the +one defined for VT:string, since the url property data type is based on the string value type. + Extensions that wish to register a data type should use the WikibaseRepoDataTypes resp. WikibaseClientDataTypes hooks to provide additional data type definitions. diff --git a/lib/WikibaseLib.datatypes.php b/lib/WikibaseLib.datatypes.php index 7461f4d..642bee0 100644 --- a/lib/WikibaseLib.datatypes.php +++ b/lib/WikibaseLib.datatypes.php @@ -15,13 +15,13 @@ */ return array( - 'commonsMedia' => array( 'value-type' => 'string' ), - 'globe-coordinate' => array( 'value-type' => 'globecoordinate' ), - 'monolingualtext' => array( 'value-type' => 'monolingualtext' ), - 'quantity' => array( 'value-type' => 'quantity' ), - 'string' => array( 'value-type' => 'string' ), - 'time' => array( 'value-type' => 'time' ), - 'url' => array( 'value-type' => 'string' ), - 'wikibase-item' => array( 'value-type' => 'wikibase-entityid' ), - 'wikibase-property' => array( 'value-type' => 'wikibase-entityid' ), + 'PT:commonsMedia' => array( 'value-type' => 'string' ), + 'PT:globe-coordinate' => array( 'value-type' => 'globecoordinate' ), + 'PT:monolingualtext' => array( 'value-type' => 'monolingualtext' ), + 'PT:quantity' => array( 'value-type' => 'quantity' ), + 'PT:string' => array( 'value-type' => 'string' ), + 'PT:time' => array( 'value-type' => 'time' ), + 'PT:url' => array( 'value-type' => 'string' ), + 'PT:wikibase-item' => array( 'value-type' => 'wikibase-entityid' ), + 'PT:wikibase-property' => array( 'value-type' => 'wikibase-entityid' ), ); diff --git a/lib/includes/DataTypeDefinitions.php b/lib/includes/DataTypeDefinitions.php index 64b4184..ffccd3f 100644 --- a/lib/includes/DataTypeDefinitions.php +++ b/lib/includes/DataTypeDefinitions.php @@ -5,18 +5,22 @@ use Wikimedia\Assert\Assert; /** - * Service that manages data type definition. This is a registry that provides access to - * factory functions for various services associated with data types, such as validators, + * Service that manages property data type definition. This is a registry that provides access to + * factory functions for various services associated with property data types, such as validators, * parsers, and formatters. * - * DataTypeDefinitions provides a one-stop interface for defining data types. Each data type is defined - * using a "data type definition" array. A definition array has the following fields: + * DataTypeDefinitions provides a one-stop interface for defining property data types. + * Each property data type is defined using a "data type definition" array. + * A definition array has the following fields: * - value-type: the value type used with the data type * - validator-factory-callback: a callback for creating validators for the data type, * as used by BuilderBasedDataTypeValidatorFactory. * - parser-factory-callback: a callback for instantiating a parser for the data type * - formatter-factory-callback: a callback for instantiating a formatter for the data type * - rdf-builder-factory-callback: a callback for instantiating a rdf mapping for the data type + * + * DataTypeDefinitions also supports fallback logic based on the value type associated with each + * property data type. * * @see docs/datatypes.wiki * @@ -26,14 +30,29 @@ class DataTypeDefinitions { /** + * Constant for indicating that callback maps should be returned with the "VT:" and "PT:" + * perfixes in the array keys indicating whether the callback applies to a value type or a + * property data type. + */ + const PREFIXED_MODE = 'prefixed'; + + /** + * Constant for indicating that callback maps should be returned for property data types only, + * with no perfixes in the array keys, but with fallbacks for value types merged into the + * definitions for the property data types. + */ + const RESOLVED_MODE = 'resolved'; + + /** * @var array[] */ private $dataTypeDefinitions = array(); /** - * @param array[] $dataTypeDefinitions An associative array mapping data type ids to data type - * definitions. Data type definitions are associative arrays, refer to the class level - * documentation. + * @param array[] $dataTypeDefinitions An associative array mapping property data type ids + * (with the prefix "PT:") and value types (with the prefix "VT:") to data type definitions. + * Each data type definitions are associative arrays, refer to the class level documentation + * for details. */ public function __construct( $dataTypeDefinitions = array() ) { Assert::parameterElementType( 'array', $dataTypeDefinitions, '$dataTypeDefinitions' ); @@ -46,14 +65,17 @@ * If a data type in $dataTypeDefinitions was already defined, the old definition is not * replaced but the definitions are merged. * - * @param array[] $dataTypeDefinitions An associative array mapping data type ids to data type - * definitions. Data type definitions are associative arrays, refer to the class level - * documentation. + * @param array[] $dataTypeDefinitions An associative array mapping property data type ids + * (with the prefix "PT:") and value types (with the prefix "VT:") to data type definitions. + * Each data type definitions are associative arrays, refer to the class level documentation + * for details. */ public function registerDataTypes( array $dataTypeDefinitions ) { Assert::parameterElementType( 'array', $dataTypeDefinitions, '$dataTypeDefinitions' ); foreach ( $dataTypeDefinitions as $id => $def ) { + Assert::parameter( strpos( $id, ':' ), "\$dataTypeDefinitions[$id]", 'Key must start with a prefix like "PT:" or "VT:".' ); + if ( isset( $this->dataTypeDefinitions[$id] ) ) { $this->dataTypeDefinitions[$id] = array_merge( $this->dataTypeDefinitions[$id], @@ -66,17 +88,40 @@ } /** - * @return string[] + * @param array $map + * @param string $prefix + * + * @return array A filtered version of $map that only contains the entries + * with keys that match the prefix $prefix, with that prefix removed. + */ + private function getFilteredByPrefix( $map, $prefix ) { + $filtered = array(); + + foreach ( $map as $key => $value ) { + $ofs = strlen( $prefix ); + if ( strpos( $key, $prefix ) === 0 ) { + $key = substr( $key, $ofs ); + $filtered[$key] = $value; + } + } + + return $filtered; + } + + /** + * @return string[] a list of all registered property data types. */ public function getTypeIds() { - return array_keys( $this->dataTypeDefinitions ); + $ptDefinitions = $this->getFilteredByPrefix( $this->dataTypeDefinitions, 'PT:' ); + return array_keys( $ptDefinitions ); } /** * @param string $field * - * @return array An associative array mapping data type IDs to the value of $field - * given in the original data type definition provided to the constructor. + * @return array An associative array mapping type IDs (with "VT:" or "PT:" prefixes) to the + * value of $field given in the original property data type definition provided to the + * constructor. */ private function getMapForDefinitionField( $field ) { $fieldValues = array(); @@ -91,46 +136,125 @@ } /** - * @return string[] + * Resolves value type fallbacks on the given callback map. For each property data type, + * the corresponding value type is determined. Then, any data type missing from $callbackMap + * is filled in with the value for the corrsponding value type. The resulting array will + * have no PT or VT prefixes. + * + * @param array $callbackMap The callback map to process. + * + * @return array An associative array mapping data type IDs to the value of $field + * given in the original property data type definition provided to the constructor. + * The keys in this array are plain property data type IDs without a prefix. + */ + private function resolveValueTypeFallback( $callbackMap ) { + $resolved = array(); + + foreach ( $this->getValueTypes() as $propertyType => $valueType ) { + $vtKey = "VT:$valueType"; + $ptKey = "PT:$propertyType"; + + if ( isset( $callbackMap[$ptKey] ) ) { + $resolved[ $propertyType ] = $callbackMap[$ptKey]; + } elseif ( isset( $callbackMap[$vtKey] ) ) { + $resolved[ $propertyType ] = $callbackMap[$vtKey]; + } + } + + return $resolved; + } + + /** + * Applies the given mode to the $callbackMap. If $mode is PREFIXED_MODE, $callbackMap is + * returned unchanged. If $mode is RESOLVED_MODE, resolveValueTypeFallback() is applied + * to $callbackMap. The resulting map will have no prefixes in the array keys, and will + * contain entries for all property data types, with value type fallback applied. + * + * @param array $callbackMap + * @param string $mode PREFIXED_MODE or RESOLVED_MODE + * + * return array A version of $callbackMap with $mode applied. + */ + private function applyMode( $callbackMap, $mode ) { + if ( $mode === self::RESOLVED_MODE ) { + return $this->resolveValueTypeFallback( $callbackMap ); + } else { + return $callbackMap; + } + } + + /** + * @return string[] An associative array mapping property data types to value types. */ public function getValueTypes() { - return $this->getMapForDefinitionField( 'value-type' ); + return $this->getFilteredByPrefix( + $this->getMapForDefinitionField( 'value-type' ), + 'PT:' + ); } /** * @see BuilderBasedDataTypeValidatorFactory * + * @param string $mode PREFIXED_MODE to request a callback map with "VT:" and "PT:" prefixes + * for value types and property data types, or RESOLVED_MODE to retrieve a callback map for + * property data types only, with value type fallback applied. + * * @return callable[] */ - public function getValidatorFactoryCallbacks() { - return $this->getMapForDefinitionField( 'validator-factory-callback' ); + public function getValidatorFactoryCallbacks( $mode = self::RESOLVED_MODE ) { + return $this->applyMode( + $this->getMapForDefinitionField( 'validator-factory-callback' ), + $mode + ); } /** * @see ValueParserFactory * + * @param string $mode PREFIXED_MODE to request a callback map with "VT:" and "PT:" prefixes + * for value types and property data types, or RESOLVED_MODE to retrieve a callback map for + * property data types only, with value type fallback applied. + * * @return callable[] */ - public function getParserFactoryCallbacks() { - return $this->getMapForDefinitionField( 'parser-factory-callback' ); + public function getParserFactoryCallbacks( $mode = self::RESOLVED_MODE ) { + return $this->applyMode( + $this->getMapForDefinitionField( 'parser-factory-callback' ), + $mode + ); } /** * @see OutputFormatValueFormatterFactory * + * @param string $mode PREFIXED_MODE to request a callback map with "VT:" and "PT:" prefixes + * for value types and property data types, or RESOLVED_MODE to retrieve a callback map for + * property data types only, with value type fallback applied. + * * @return callable[] */ - public function getFormatterFactoryCallbacks() { - return $this->getMapForDefinitionField( 'formatter-factory-callback' ); + public function getFormatterFactoryCallbacks( $mode = self::RESOLVED_MODE ) { + return $this->applyMode( + $this->getMapForDefinitionField( 'formatter-factory-callback' ), + $mode + ); } /** * @see ValueSnakRdfBuilderFactory * + * @param string $mode PREFIXED_MODE to request a callback map with "VT:" and "PT:" prefixes + * for value types and property data types, or RESOLVED_MODE to retrieve a callback map for + * property data types only, with value type fallback applied. + * * @return callable[] */ - public function getRdfBuilderFactoryCallbacks() { - return $this->getMapForDefinitionField( 'rdf-builder-factory-callback' ); + public function getRdfBuilderFactoryCallbacks( $mode = self::RESOLVED_MODE ) { + return $this->applyMode( + $this->getMapForDefinitionField( 'rdf-builder-factory-callback' ), + $mode + ); } } diff --git a/lib/includes/formatters/WikibaseValueFormatterBuilders.php b/lib/includes/formatters/WikibaseValueFormatterBuilders.php index caf9bb2..16067d0 100644 --- a/lib/includes/formatters/WikibaseValueFormatterBuilders.php +++ b/lib/includes/formatters/WikibaseValueFormatterBuilders.php @@ -89,50 +89,6 @@ $this->entityTitleLookup = $entityTitleLookup; } - /** - * Returns an associative array mapping value types to factory functions. - * This is for use with OutputFormatValueFormatterFactory and DispatchingValueFormatter, - * to allow a fallback from data type based formatter selection to formatting based - * only on the value type. - * - * @todo make formatters for value types configurable using a mechanism similar to the - * one used for data types (see DataTypeDefinitions). - * - * @return callable[] - */ - public function getFormatterFactoryCallbacksByValueType() { - $self = $this; // yay PHP 5.3! - return array( - 'string' => function( $format, FormatterOptions $options ) use ( $self ) { - return $format === SnakFormatter::FORMAT_PLAIN ? new StringFormatter( $options ) : null; - }, - - 'bad' => function( $format, FormatterOptions $options ) use ( $self ) { - return $format === SnakFormatter::FORMAT_PLAIN ? new UnDeserializableValueFormatter( $options ) : null; - }, - - 'globecoordinate' => function( $format, FormatterOptions $options ) use ( $self ) { - return $self->newGlobeCoordinateFormatter( $format, $options ); - }, - - 'quantity' => function( $format, FormatterOptions $options ) use ( $self ) { - return $self->newQuantityFormatter( $format, $options ); - }, - - 'time' => function( $format, FormatterOptions $options ) use ( $self ) { - return $self->newTimeFormatter( $format, $options ); - }, - - 'wikibase-entityid' => function( $format, FormatterOptions $options ) use ( $self ) { - return $self->newEntityIdFormatter( $format, $options ); - }, - - 'monolingualtext' => function( $format, FormatterOptions $options ) use ( $self ) { - return $self->newMonolingualFormatter( $format, $options ); - }, - ); - } - private function newPlainEntityIdFormatter( FormatterOptions $options ) { $labelDescriptionLookup = $this->labelDescriptionLookupFactory->getLabelDescriptionLookup( $options ); return new EntityIdValueFormatter( diff --git a/lib/tests/phpunit/DataTypeDefinitionsTest.php b/lib/tests/phpunit/DataTypeDefinitionsTest.php index aa101de..b952667 100644 --- a/lib/tests/phpunit/DataTypeDefinitionsTest.php +++ b/lib/tests/phpunit/DataTypeDefinitionsTest.php @@ -18,14 +18,17 @@ private function getDataTypeDefinitions() { $definitions = array( - 'foo' => array( + 'VT:FOO' => array( + 'formatter-factory-callback' => 'DataTypeDefinitionsTest::getFooValueFormatter', + 'parser-factory-callback' => 'DataTypeDefinitionsTest::getFooValueParser', + 'rdf-builder-factory-callback' => 'DataTypeDefinitionsTest::getFooRdfBuilder', + ), + 'PT:foo' => array( 'value-type' => 'FOO', 'validator-factory-callback' => 'DataTypeDefinitionsTest::getFooValidators', 'parser-factory-callback' => 'DataTypeDefinitionsTest::getFooParser', - 'formatter-factory-callback' => 'DataTypeDefinitionsTest::getFooFormatter', - 'rdf-builder-factory-callback' => 'DataTypeDefinitionsTest::getFooRdfBuilder', ), - 'bar' => array( + 'PT:bar' => array( 'value-type' => 'BAR', 'formatter-factory-callback' => 'DataTypeDefinitionsTest::getBarFormatter', ) @@ -46,38 +49,70 @@ public function testGetValidatorFactoryCallbacks() { $defs = $this->getDataTypeDefinitions(); - $this->assertEquals( array( 'foo' => 'DataTypeDefinitionsTest::getFooValidators' ), $defs->getValidatorFactoryCallbacks() ); + + $expected = array( 'foo' => 'DataTypeDefinitionsTest::getFooValidators' ); + $this->assertEquals( $expected, $defs->getValidatorFactoryCallbacks() ); + + $expected = array( 'PT:foo' => 'DataTypeDefinitionsTest::getFooValidators' ); + $this->assertEquals( $expected, $defs->getValidatorFactoryCallbacks( DataTypeDefinitions::PREFIXED_MODE ) ); } public function testGetParserFactoryCallbacks() { $defs = $this->getDataTypeDefinitions(); - $this->assertEquals( array( 'foo' => 'DataTypeDefinitionsTest::getFooParser' ), $defs->getParserFactoryCallbacks() ); + + $expected = array( 'foo' => 'DataTypeDefinitionsTest::getFooParser' ); + $this->assertEquals( $expected, $defs->getParserFactoryCallbacks() ); + + $expected = array( + 'PT:foo' => 'DataTypeDefinitionsTest::getFooParser', + 'VT:FOO' => 'DataTypeDefinitionsTest::getFooValueParser', + ); + $this->assertEquals( $expected, $defs->getParserFactoryCallbacks( DataTypeDefinitions::PREFIXED_MODE ) ); + } + + public function testGetFormatterFactoryCallbacks() { + $defs = $this->getDataTypeDefinitions(); + + $expected = array( + 'foo' => 'DataTypeDefinitionsTest::getFooValueFormatter', + 'bar' => 'DataTypeDefinitionsTest::getBarFormatter', + ); + $this->assertEquals( $expected, $defs->getFormatterFactoryCallbacks() ); + + $expected = array( + 'VT:FOO' => 'DataTypeDefinitionsTest::getFooValueFormatter', + 'PT:bar' => 'DataTypeDefinitionsTest::getBarFormatter', + ); + $this->assertEquals( $expected, $defs->getFormatterFactoryCallbacks( DataTypeDefinitions::PREFIXED_MODE ) ); } public function testRegisterDataTypes() { $defs = $this->getDataTypeDefinitions(); $extraTypes = array( - 'bar' => array( + 'VT:FOO' => array( + 'validator-factory-callback' => 'DataTypeDefinitionsTest::getFooValueValidator', + ), + 'PT:bar' => array( 'validator-factory-callback' => 'DataTypeDefinitionsTest::getBarValidators', 'parser-factory-callback' => 'DataTypeDefinitionsTest::getBarParser', ), - 'fuzz' => array( - 'value-type' => 'FUZZ', - 'validator-factory-callback' => 'DataTypeDefinitionsTest::getFuzzValidators', - ) + 'PT:fuzz' => array( + 'value-type' => 'FOO', + ), ); $defs->registerDataTypes( $extraTypes ); $this->assertEquals( array( 'foo', 'bar', 'fuzz' ), $defs->getTypeIds() ); - $this->assertEquals( array( 'foo' => 'FOO', 'bar' => 'BAR', 'fuzz' => 'FUZZ' ), $defs->getValueTypes() ); + $this->assertEquals( array( 'foo' => 'FOO', 'bar' => 'BAR', 'fuzz' => 'FOO' ), $defs->getValueTypes() ); + $actual = $defs->getValidatorFactoryCallbacks(); $this->assertEquals( array( 'foo' => 'DataTypeDefinitionsTest::getFooValidators', 'bar' => 'DataTypeDefinitionsTest::getBarValidators', - 'fuzz' => 'DataTypeDefinitionsTest::getFuzzValidators' ), - $defs->getValidatorFactoryCallbacks() + 'fuzz' => 'DataTypeDefinitionsTest::getFooValueValidator' ), + $actual ); } @@ -87,6 +122,10 @@ array( 'foo' => 'DataTypeDefinitionsTest::getFooRdfBuilder' ), $defs->getRdfBuilderFactoryCallbacks() ); + $this->assertEquals( + array( 'VT:FOO' => 'DataTypeDefinitionsTest::getFooRdfBuilder' ), + $defs->getRdfBuilderFactoryCallbacks( DataTypeDefinitions::PREFIXED_MODE ) + ); } } diff --git a/lib/tests/phpunit/formatters/WikibaseValueFormatterBuildersTest.php b/lib/tests/phpunit/formatters/WikibaseValueFormatterBuildersTest.php index 9ef2a7a..bee3e48 100644 --- a/lib/tests/phpunit/formatters/WikibaseValueFormatterBuildersTest.php +++ b/lib/tests/phpunit/formatters/WikibaseValueFormatterBuildersTest.php @@ -137,28 +137,6 @@ } /** - * @param string $type - * @param string $format - * @param FormatterOptions $options - * - * @return mixed - */ - private function getFormatterForValueType( $type, $format, $options ) { - $builders = $this->newWikibaseValueFormatterBuilders( - $this->getTitleLookup() - ); - - $factories = $builders->getFormatterFactoryCallbacksByValueType(); - $this->assertArrayHasKey( $type, $factories, 'value type ' . $type ); - - $factory = $factories[$type]; - $formatter = call_user_func( $factory, $format, $options ); - - $this->assertInstanceOf( 'ValueFormatters\ValueFormatter', $formatter ); - return $formatter; - } - - /** * @dataProvider provideNewFormatter */ public function testNewFormatter( @@ -330,141 +308,6 @@ } /** - * @dataProvider provideFormatterForValueType - */ - public function testFormatterForValueType( - $format, - FormatterOptions $options, - DataValue $value, - $expected - ) { - $formatter = $this->getFormatterForValueType( $value->getType(), $format, $options ); - - $text = $formatter->format( $value ); - $this->assertRegExp( $expected, $text ); - } - - public function provideFormatterForValueType() { - return array( - // string - 'plain string' => array( - SnakFormatter::FORMAT_PLAIN, - $this->newFormatterOptions(), - new StringValue( '{foo&bar}' ), - '@^\{foo&bar\}$@' - ), - - // bad - 'bad value' => array( - SnakFormatter::FORMAT_PLAIN, - $this->newFormatterOptions(), - new UnDeserializableValue( 'Foo/Bar', 'time', 'evil' ), - '@^.*invalid.*$@' - ), - - // globecoordinate - 'plain coordinate' => array( - SnakFormatter::FORMAT_PLAIN, - $this->newFormatterOptions(), - new GlobeCoordinateValue( new LatLongValue( -55.755786, 37.25633 ), 0.25 ), - '@^55°45\'S, 37°15\'E$@' - ), - - 'coordinate details' => array( - SnakFormatter::FORMAT_HTML_DIFF, - $this->newFormatterOptions(), - new GlobeCoordinateValue( new LatLongValue( -55.755786, 37.25633 ), 0.25 ), - '@^.*55° 45\', 37° 15\'.*$@' - ), - - // quantity - 'localized quantity' => array( - SnakFormatter::FORMAT_PLAIN, - $this->newFormatterOptions( 'de' ), - QuantityValue::newFromNumber( '+123456.789' ), - '@^123\\.456,789$@' - ), - - 'quantity details' => array( - SnakFormatter::FORMAT_HTML_DIFF, - $this->newFormatterOptions( 'de' ), - QuantityValue::newFromNumber( '+123456.789', 'foo' ), - '@^.*123\\.456,789.*foo.*$@' - ), - - // time - 'a month in 1980' => array( - SnakFormatter::FORMAT_PLAIN, - $this->newFormatterOptions(), - new TimeValue( - '+1980-05-01T00:00:00Z', - 0, 0, 0, - TimeValue::PRECISION_MONTH, - 'http://www.wikidata.org/entity/Q1985727' - ), - '/^May 1980$/' - ), - 'a gregorian day in 1520' => array( - SnakFormatter::FORMAT_HTML, - $this->newFormatterOptions(), - new TimeValue( - '+1520-05-01T00:00:00Z', - 0, 0, 0, - TimeValue::PRECISION_DAY, - 'http://www.wikidata.org/entity/Q1985727' - ), - '/^1 May 1520<sup class="wb-calendar-name">Gregorian<\/sup>$/' - ), - 'a julian day in 1980' => array( - SnakFormatter::FORMAT_HTML_DIFF, - $this->newFormatterOptions(), - new TimeValue( - '+1980-05-01T00:00:00Z', - 0, 0, 0, - TimeValue::PRECISION_DAY, - 'http://www.wikidata.org/entity/Q1985786' - ), - '/^.*>1 May 1980<sup class="wb-calendar-name">Julian<\/sup>.*$/' - ), - - //wikibase-entityid - 'plain item label (with language fallback)' => array( - SnakFormatter::FORMAT_PLAIN, - $this->newFormatterOptions( 'de-ch' ), // should fall back to 'de' - new EntityIdValue( new ItemId( 'Q5' ) ), - '@^Name für Q5$@' - ), - 'widget item link (with entity lookup)' => array( - SnakFormatter::FORMAT_HTML, - $this->newFormatterOptions(), - new EntityIdValue( new ItemId( 'Q5' ) ), - '/^<a\b[^>]* href="[^"]*\bQ5">Label for Q5<\/a>.*$/', - ), - 'property link (with entity lookup)' => array( - SnakFormatter::FORMAT_HTML, - $this->newFormatterOptions(), - new EntityIdValue( new PropertyId( 'P5' ) ), - '/^<a\b[^>]* href="[^"]*\bP5">Label for P5<\/a>.*$/', - 'wikibase-property' - ), - - //monolingualtext - 'plain text in english' => array( - SnakFormatter::FORMAT_PLAIN, - $this->newFormatterOptions( 'en' ), - new MonolingualTextValue( 'en', 'Hello World' ), - '/^Hello World$/' - ), - 'html text in german' => array( - SnakFormatter::FORMAT_HTML, - $this->newFormatterOptions( 'en' ), - new MonolingualTextValue( 'de', 'Hallo Welt' ), - '/^.*lang="de".*?>Hallo Welt<.*Deutsch.*$/' - ) - ); - } - - /** * In case WikibaseValueFormatterBuilders doesn't have a EntityTitleLookup it returns * a formatter which doesn't link the entity id. * @@ -551,32 +394,6 @@ new EntityIdValue( new ItemId( 'Q5' ) ), '@>Custom LabelDescriptionLookup<@' ), - ); - } - - public function testGetFormatterFactoryCallbacksByValueType() { - $builders = $this->newWikibaseValueFormatterBuilders(); - - // check for all the required types - $required = array( - 'string', - 'time', - 'globecoordinate', - 'wikibase-entityid', - 'quantity', - 'bad', - 'monolingualtext', - ); - - $actual = array_keys( $builders->getFormatterFactoryCallbacksByValueType() ); - - sort( $required ); - sort( $actual ); - - // check for all the required types, that is, the ones supported by the fallback format - $this->assertEquals( - $required, - $actual ); } diff --git a/repo/Wikibase.php b/repo/Wikibase.php index a9202ca..fd14f8e 100644 --- a/repo/Wikibase.php +++ b/repo/Wikibase.php @@ -132,8 +132,8 @@ * @var callable[] $wgValueParsers Defines parser factory callbacks by parser name (not data type name). * @deprecated use $wgWBRepoDataTypes instead. */ - $wgValueParsers['wikibase-entityid'] = $wgWBRepoDataTypes['wikibase-item']['parser-factory-callback']; - $wgValueParsers['globecoordinate'] = $wgWBRepoDataTypes['globe-coordinate']['parser-factory-callback']; + $wgValueParsers['wikibase-entityid'] = $wgWBRepoDataTypes['VT:wikibase-entityid']['parser-factory-callback']; + $wgValueParsers['globecoordinate'] = $wgWBRepoDataTypes['VT:globecoordinate']['parser-factory-callback']; // 'null' is not a datatype. Kept for backwards compatibility. $wgValueParsers['null'] = function() { diff --git a/repo/WikibaseRepo.datatypes.php b/repo/WikibaseRepo.datatypes.php index 132827b..2e8bdbd 100644 --- a/repo/WikibaseRepo.datatypes.php +++ b/repo/WikibaseRepo.datatypes.php @@ -24,10 +24,13 @@ use DataValues\Geo\Parsers\GlobeCoordinateParser; use ValueFormatters\FormatterOptions; +use ValueFormatters\StringFormatter; use ValueParsers\ParserOptions; use ValueParsers\QuantityParser; use ValueParsers\StringParser; use ValueParsers\ValueParser; +use Wikibase\Lib\SnakFormatter; +use Wikibase\Lib\UnDeserializableValueFormatter; use Wikibase\Rdf\DedupeBag; use Wikibase\Rdf\EntityMentionListener; use Wikibase\Rdf\JulianDateTimeValueCleaner; @@ -62,6 +65,9 @@ // WikibaseValueFormatterBuilders should be used *only* here, program logic should use a // OutputFormatValueFormatterFactory as returned by WikibaseRepo::getValueFormatterFactory(). + // NOTE: Factory callbacks are registered below by value type (using the prefix "VT:") or by + // property data type (prefix "PT:"). + $newEntityIdParser = function( ParserOptions $options ) { $repo = WikibaseRepo::getDefaultInstance(); return new EntityIdValueParser( $repo->getEntityIdParser() ); @@ -74,7 +80,12 @@ }; return array( - 'commonsMedia' => array( + 'VT:bad' => array( + 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { + return $format === SnakFormatter::FORMAT_PLAIN ? new UnDeserializableValueFormatter( $options ) : null; + } + ), + 'PT:commonsMedia' => array( 'validator-factory-callback' => function() { $factory = WikibaseRepo::getDefaultValidatorBuilders(); return $factory->buildStringValidators(); @@ -94,7 +105,7 @@ return new CommonsMediaRdfBuilder( $vocab ); }, ), - 'globe-coordinate' => array( + 'VT:globecoordinate' => array( 'validator-factory-callback' => function() { $factory = WikibaseRepo::getDefaultValidatorBuilders(); return $factory->buildCoordinateValidators(); @@ -117,7 +128,7 @@ return new GlobeCoordinateRdfBuilder( $complexValueHelper ); }, ), - 'monolingualtext' => array( + 'VT:monolingualtext' => array( 'validator-factory-callback' => function() { $factory = WikibaseRepo::getDefaultValidatorBuilders(); return $factory->buildMonolingualTextValidators(); @@ -139,7 +150,7 @@ return new MonolingualTextRdfBuilder(); }, ), - 'quantity' => array( + 'VT:quantity' => array( 'validator-factory-callback' => function() { $factory = WikibaseRepo::getDefaultValidatorBuilders(); return $factory->buildQuantityValidators(); @@ -164,14 +175,14 @@ return new QuantityRdfBuilder( $complexValueHelper ); }, ), - 'string' => array( + 'VT:string' => array( 'validator-factory-callback' => function() { $factory = WikibaseRepo::getDefaultValidatorBuilders(); return $factory->buildStringValidators(); }, 'parser-factory-callback' => $newStringParser, 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { - return null; // rely on formatter for string value type + return $format === SnakFormatter::FORMAT_PLAIN ? new StringFormatter( $options ) : null; }, 'rdf-builder-factory-callback' => function ( $mode, @@ -183,7 +194,7 @@ return new LiteralValueRdfBuilder( null, null ); }, ), - 'time' => array( + 'VT:time' => array( 'validator-factory-callback' => function() { $factory = WikibaseRepo::getDefaultValidatorBuilders(); return $factory->buildTimeValidators(); @@ -209,7 +220,7 @@ return new TimeRdfBuilder( $dateCleaner, $complexValueHelper ); }, ), - 'url' => array( + 'PT:url' => array( 'validator-factory-callback' => function() { $factory = WikibaseRepo::getDefaultValidatorBuilders(); return $factory->buildUrlValidators(); @@ -229,30 +240,10 @@ return new ObjectUriRdfBuilder(); }, ), - 'wikibase-item' => array( + 'VT:wikibase-entityid' => array( 'validator-factory-callback' => function() { $factory = WikibaseRepo::getDefaultValidatorBuilders(); return $factory->buildItemValidators(); - }, - 'parser-factory-callback' => $newEntityIdParser, - 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { - $factory = WikibaseRepo::getDefaultFormatterBuilders(); - return $factory->newEntityIdFormatter( $format, $options ); - }, - 'rdf-builder-factory-callback' => function ( - $mode, - RdfVocabulary $vocab, - RdfWriter $writer, - EntityMentionListener $tracker, - DedupeBag $dedupe - ) { - return new EntityIdRdfBuilder( $vocab, $tracker ); - }, - ), - 'wikibase-property' => array( - 'validator-factory-callback' => function() { - $factory = WikibaseRepo::getDefaultValidatorBuilders(); - return $factory->buildPropertyValidators(); }, 'parser-factory-callback' => $newEntityIdParser, 'formatter-factory-callback' => function( $format, FormatterOptions $options ) { diff --git a/repo/includes/WikibaseRepo.php b/repo/includes/WikibaseRepo.php index bc73b52..a819216 100644 --- a/repo/includes/WikibaseRepo.php +++ b/repo/includes/WikibaseRepo.php @@ -791,66 +791,14 @@ } /** - * @param string $prefix The prefix to add to each key in the array - * @param array $array The array to modify - * - * @return array A copy of $array with $prefix prepended to each array key - */ - private function applyKeyPrefix( $prefix, array $array ) { - $result = array(); - - foreach ( $array as $key => $value ) { - $result[ $prefix . $key ] = $value; - } - - return $result; - } - - /** - * Constructs an array of factory callbacks for ValueFormatters, keyed by property type - * (data type) prefixed with "PT:", or value type prefixed with "VT:". This matches the - * convention used by OutputFormatValueFormatterFactory and DispatchingValueFormatter. - * - * @return callable[] - */ - private function getFormatterFactoryCallbacksByType() { - $valueFormatterBuilders = $this->newWikibaseValueFormatterBuilders(); - $valueTypeFormatters = $valueFormatterBuilders->getFormatterFactoryCallbacksByValueType(); - $dataTypeFormatters = $this->dataTypeDefinitions->getFormatterFactoryCallbacks(); - - $callbacks = array_merge( - $this->applyKeyPrefix( 'VT:', $valueTypeFormatters ), - $this->applyKeyPrefix( 'PT:', $dataTypeFormatters ) - ); - - return $callbacks; - } - - /** * @return OutputFormatValueFormatterFactory */ protected function newValueFormatterFactory() { return new OutputFormatValueFormatterFactory( - $this->getFormatterFactoryCallbacksByType(), + $this->dataTypeDefinitions->getFormatterFactoryCallbacks( DataTypeDefinitions::PREFIXED_MODE ), $this->getDefaultLanguage(), new LanguageFallbackChainFactory() ); - } - - /** - * Constructs an array of factory callbacks for ValueSnakRdfBuilder, keyed by property type - * (data type) prefixed with "PT:", or value type prefixed with "VT:". This matches the - * convention used by ValueSnakRdfBuilderFactory. - * - * @return callable[] - */ - private function getRdfBuilderFactoryCallbacksByType() { - //TODO: provide fallback mappings per data-type with "VT:" prefix. - $dataTypeRdfBuilders = $this->dataTypeDefinitions->getRdfBuilderFactoryCallbacks(); - - $callbacks = $this->applyKeyPrefix( 'PT:', $dataTypeRdfBuilders ); - - return $callbacks; } /** @@ -859,7 +807,7 @@ public function getValueSnakRdfBuilderFactory() { if ( $this->valueSnakRdfBuilderFactory === null ) { $this->valueSnakRdfBuilderFactory = new ValueSnakRdfBuilderFactory( - $this->getRdfBuilderFactoryCallbacksByType() + $this->dataTypeDefinitions->getRdfBuilderFactoryCallbacks( DataTypeDefinitions::PREFIXED_MODE ) ); } -- To view, visit https://gerrit.wikimedia.org/r/253321 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I0666f1ea97a3a3caf7233f28c880452e891a21b6 Gerrit-PatchSet: 7 Gerrit-Project: mediawiki/extensions/Wikibase Gerrit-Branch: master Gerrit-Owner: Daniel Kinzler <daniel.kinz...@wikimedia.de> Gerrit-Reviewer: Addshore <addshorew...@gmail.com> Gerrit-Reviewer: Aude <aude.w...@gmail.com> Gerrit-Reviewer: Daniel Kinzler <daniel.kinz...@wikimedia.de> Gerrit-Reviewer: Hoo man <h...@online.de> Gerrit-Reviewer: JanZerebecki <jan.wikime...@zerebecki.de> Gerrit-Reviewer: Jeroen De Dauw <jeroended...@gmail.com> Gerrit-Reviewer: Jonas Kress (WMDE) <jonas.kr...@wikimedia.de> Gerrit-Reviewer: Thiemo Mättig (WMDE) <thiemo.maet...@wikimedia.de> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits