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

Reply via email to