Thiemo Mättig (WMDE) has uploaded a new change for review. https://gerrit.wikimedia.org/r/290930
Change subject: Introduce FragmentedEntityIdBuilder and "fragmented-entity-id-builder" ...................................................................... Introduce FragmentedEntityIdBuilder and "fragmented-entity-id-builder" This is meant as a clean implementation for the hack you can see in https://gerrit.wikimedia.org/r/#/c/290461/1/lib/includes/Store/Sql/SqlEntityInfoBuilder.php This goes along with https://github.com/wmde/WikibaseDataModel/pull/670 but is not an exact mirror of the Int32EntityId interface proposed there. This builder is much more general and also allows constructing all kinds of entity IDs that contain fixed parts that never change for that entity type (e.g. "File:Filename.jpg" where "File:" is a fixed prefix, but the suffix is not a number). The new stuff is not yet used in this patch. It's meant to replace all usages of LegacyIdInterpreter::newIdFromTypeAndNumber as well as remaining …::newFromNumber when it makes sense. Bug: T136294 Change-Id: I9b541240ccbfb22425ab48b7de9c635be14dfc6d --- M docs/entitytypes.wiki M lib/includes/EntityTypeDefinitions.php A lib/includes/FragmentedEntityIdBuilder.php M lib/tests/phpunit/EntityTypeDefinitionsTest.php A lib/tests/phpunit/FragmentedEntityIdBuilderTest.php 5 files changed, 241 insertions(+), 8 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Wikibase refs/changes/30/290930/1 diff --git a/docs/entitytypes.wiki b/docs/entitytypes.wiki index 8cc5727..85b4c73 100644 --- a/docs/entitytypes.wiki +++ b/docs/entitytypes.wiki @@ -46,9 +46,13 @@ : a callable that returns a DispatchableDeserializer instance, with the first and only argument being a DeserializerFactory ; entity-id-pattern (repo and client) -: a regular expression that matches serialized entity ids +: a regular expression that matches serialized entity IDs ; entity-id-builder (repo and client) -: a callable that returns an EntityId instance for a given entity id serialization +: a callable that returns an EntityId instance for a given entity ID serialization +; entity-id-fragment-builder (repo and client) +: a callable that returns an EntityId instance for a given unique fragment of an entity ID + serialization. Only entity types with IDs that are constructed from a static prefix and a unique + suffix can and should specify this. ; view-factory-callback (repo only) : a callable that returns an EntityView instance, with the arguments being a language code, a LabelDescriptionLookup, a LanguageFallbackChain and an EditSectionGenerator diff --git a/lib/includes/EntityTypeDefinitions.php b/lib/includes/EntityTypeDefinitions.php index 6c540ea..91895ef 100644 --- a/lib/includes/EntityTypeDefinitions.php +++ b/lib/includes/EntityTypeDefinitions.php @@ -16,6 +16,8 @@ * * @licence GNU GPL v2+ * @author Bene* < [email protected] > + * @author Adrian Heine <[email protected]> + * @author Thiemo Mättig */ class EntityTypeDefinitions { @@ -31,8 +33,8 @@ * @throws InvalidArgumentException */ public function __construct( array $entityTypeDefinitions ) { - foreach ( $entityTypeDefinitions as $id => $def ) { - if ( !is_string( $id ) || !is_array( $def ) ) { + foreach ( $entityTypeDefinitions as $type => $def ) { + if ( !is_string( $type ) || !is_array( $def ) ) { throw new InvalidArgumentException( '$entityTypeDefinitions must be a map from string to arrays' ); } } @@ -50,14 +52,14 @@ /** * @param string $field * - * @return mixed + * @return array */ private function getMapForDefinitionField( $field ) { - $fieldValues = array(); + $fieldValues = []; - foreach ( $this->entityTypeDefinitions as $id => $def ) { + foreach ( $this->entityTypeDefinitions as $type => $def ) { if ( isset( $def[$field] ) ) { - $fieldValues[$id] = $def[$field]; + $fieldValues[$type] = $def[$field]; } } @@ -142,4 +144,13 @@ return $result; } + /** + * @return callable[] An array mapping entity type identifiers to callables capable of turning + * unique entity ID serialization fragments into EntityId objects. Not guaranteed to contain + * all entity types. + */ + public function getEntityIdFragmentBuilders() { + return $this->getMapForDefinitionField( 'entity-id-fragment-builder' ); + } + } diff --git a/lib/includes/FragmentedEntityIdBuilder.php b/lib/includes/FragmentedEntityIdBuilder.php new file mode 100644 index 0000000..96ea405 --- /dev/null +++ b/lib/includes/FragmentedEntityIdBuilder.php @@ -0,0 +1,74 @@ +<?php + +namespace Wikibase\Lib; + +use InvalidArgumentException; +use Wikibase\DataModel\Entity\EntityId; +use Wikibase\DataModel\Entity\ItemId; +use Wikibase\DataModel\Entity\PropertyId; + +/** + * Constructs EntityId objects from entity type identifiers and unique entity ID serialization + * fragments. A fragment is typically the unique, numeric part of an entity ID, excluding the + * prefix. Items and properties are always supported for legacy reasons. + * + * Meant to be the counterpart for @see Int32EntityId::getNumericId, as well as an extensible + * replacement for @see LegacyIdInterpreter::newIdFromTypeAndNumber. + * + * @todo Move to DataModel Services. + * + * @since 0.5 + * + * @license GPL-2.0+ + * @author Thiemo Mättig + */ +class FragmentedEntityIdBuilder { + + /** + * @var callable[] + */ + private $builders; + + /** + * @param callable[] $builders Array mapping entity type identifiers to callables accepting a + * single mixed value, representing the unique fragment of an entity ID serialization, and + * returning an EntityId object. + * + * @throws InvalidArgumentException + */ + public function __construct( array $builders ) { + foreach ( $builders as $entityType => $builder ) { + if ( !is_string( $entityType ) || $entityType === '' || !is_callable( $builder ) ) { + throw new InvalidArgumentException( '$builders must map non-empty strings to callables' ); + } + } + + $this->builders = $builders; + } + + /** + * @param string $entityType + * @param mixed $fragment + * + * @throws InvalidArgumentException + * @return EntityId + */ + public function build( $entityType, $fragment ) { + if ( isset( $this->builders[$entityType] ) ) { + $id = $this->builders[$entityType]( $fragment ); + } elseif ( $entityType === 'item' ) { + $id = ItemId::newFromNumber( $fragment ); + } elseif ( $entityType === 'property' ) { + $id = PropertyId::newFromNumber( $fragment ); + } else { + throw new InvalidArgumentException( 'Unknown entity type ' . $entityType ); + } + + if ( !( $id instanceof EntityId ) ) { + throw new InvalidArgumentException( 'Builder for ' . $entityType . ' is invalid' ); + } + + return $id; + } + +} diff --git a/lib/tests/phpunit/EntityTypeDefinitionsTest.php b/lib/tests/phpunit/EntityTypeDefinitionsTest.php index e6c18ce..807b6e2 100644 --- a/lib/tests/phpunit/EntityTypeDefinitionsTest.php +++ b/lib/tests/phpunit/EntityTypeDefinitionsTest.php @@ -10,6 +10,7 @@ * * @licence GNU GPL v2+ * @author Bene* < [email protected] > + * @author Thiemo Mättig */ class EntityTypeDefinitionsTest extends PHPUnit_Framework_TestCase { @@ -22,6 +23,12 @@ 'content-model-id' => 'foo-model', 'content-handler-factory-callback' => 'foo-handler', 'entity-factory-callback' => 'new-foo', + 'entity-differ-strategy-builder' => 'foo-differ', + 'entity-patcher-strategy-builder' => 'foo-patcher', + 'js-deserializer-factory-function' => 'foo-js-deserializer', + 'entity-id-pattern' => 'foo-id-pattern', + 'entity-id-builder' => 'new-foo-id', + 'entity-id-fragment-builder' => 'new-fragmented-foo-id', ), 'bar' => array( 'serializer-factory-callback' => 'bar-serializer', @@ -107,4 +114,44 @@ ); } + public function testGetEntityDifferStrategyBuilders() { + $definitions = new EntityTypeDefinitions( $this->getDefinitions() ); + + $this->assertSame( [ + 'foo' => 'foo-differ', + ], $definitions->getEntityDifferStrategyBuilders() ); + } + + public function testGetEntityPatcherStrategyBuilders() { + $definitions = new EntityTypeDefinitions( $this->getDefinitions() ); + + $this->assertSame( [ + 'foo' => 'foo-patcher', + ], $definitions->getEntityPatcherStrategyBuilders() ); + } + + public function testGetJsDeserializerFactoryFunctions() { + $definitions = new EntityTypeDefinitions( $this->getDefinitions() ); + + $this->assertSame( [ + 'foo' => 'foo-js-deserializer', + ], $definitions->getJsDeserializerFactoryFunctions() ); + } + + public function testGetEntityIdBuilders() { + $definitions = new EntityTypeDefinitions( $this->getDefinitions() ); + + $this->assertSame( [ + 'foo-id-pattern' => 'new-foo-id', + ], $definitions->getEntityIdBuilders() ); + } + + public function testGetEntityIdFragmentBuilders() { + $definitions = new EntityTypeDefinitions( $this->getDefinitions() ); + + $this->assertSame( [ + 'foo' => 'new-fragmented-foo-id', + ], $definitions->getEntityIdFragmentBuilders() ); + } + } diff --git a/lib/tests/phpunit/FragmentedEntityIdBuilderTest.php b/lib/tests/phpunit/FragmentedEntityIdBuilderTest.php new file mode 100644 index 0000000..a568709 --- /dev/null +++ b/lib/tests/phpunit/FragmentedEntityIdBuilderTest.php @@ -0,0 +1,97 @@ +<?php + +namespace Wikibase\Lib\Tests; + +use InvalidArgumentException; +use PHPUnit_Framework_TestCase; +use Wikibase\DataModel\Entity\EntityId; +use Wikibase\DataModel\Entity\ItemId; +use Wikibase\DataModel\Entity\PropertyId; +use Wikibase\Lib\FragmentedEntityIdBuilder; + +/** + * @covers Wikibase\Lib\FragmentedEntityIdBuilder + * + * @licence GNU GPL v2+ + * @author Thiemo Mättig + */ +class FragmentedEntityIdBuilderTest extends PHPUnit_Framework_TestCase { + + private function getBuilder() { + return new FragmentedEntityIdBuilder( [ + 'numeric-item' => function( $numericId ) { + return new ItemId( 'Q' . $numericId ); + }, + 'custom-item' => function( $fragment ) { + return new ItemId( 'Q100' . $fragment ); + }, + ] ); + } + + public function invalidConstructorArgumentProvider() { + return [ + [ [ 0 => function() {} ] ], + [ [ '' => function() {} ] ], + [ [ 'string' => null ] ], + [ [ 'string' => 'not a callable' ] ], + ]; + } + + /** + * @dataProvider invalidConstructorArgumentProvider + */ + public function testGivenInvalidBuilder_constructorFails( $builders ) { + $this->setExpectedException( InvalidArgumentException::class ); + new FragmentedEntityIdBuilder( $builders ); + } + + public function testGivenInvalidCallback_buildFails() { + $builder = new FragmentedEntityIdBuilder( [ + 'numeric-item' => function( $fragment ) { + return null; + }, + ] ); + $this->setExpectedException( InvalidArgumentException::class ); + $builder->build( 'numeric-item', 1 ); + } + + public function validEntityIdFragmentProvider() { + return [ + 'Items are always supported' => [ 'item', 1, new ItemId( 'Q1' ) ], + 'Properties are always supported' => [ 'property', 2, new PropertyId( 'P2' ) ], + + 'int' => [ 'numeric-item', 3, new ItemId( 'Q3' ) ], + 'float' => [ 'numeric-item', 4.0, new ItemId( 'Q4' ) ], + 'string' => [ 'numeric-item', '5', new ItemId( 'Q5' ) ], + + 'custom' => [ 'custom-item', 6, new ItemId( 'Q1006' ) ], + ]; + } + + /** + * @dataProvider validEntityIdFragmentProvider + */ + public function testGivenValidFragment_buildSucceeds( $entityType, $fragment, EntityId $expected ) { + $id = $this->getBuilder()->build( $entityType, $fragment ); + $this->assertEquals( $expected, $id ); + } + + public function invalidEntityIdFragmentProvider() { + return [ + [ null, 1 ], + [ 'unknown', 2 ], + [ 'item', null ], + [ 'item', new ItemId( 'Q4' ) ], + ]; + } + + /** + * @dataProvider invalidEntityIdFragmentProvider + */ + public function testGivenInvalidFragment_buildFails( $entityType, $fragment ) { + $builder = $this->getBuilder(); + $this->setExpectedException( InvalidArgumentException::class ); + $builder->build( $entityType, $fragment ); + } + +} -- To view, visit https://gerrit.wikimedia.org/r/290930 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I9b541240ccbfb22425ab48b7de9c635be14dfc6d Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Wikibase Gerrit-Branch: master Gerrit-Owner: Thiemo Mättig (WMDE) <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
