jenkins-bot has submitted this change and it was merged. (
https://gerrit.wikimedia.org/r/404711 )
Change subject: Add support for “instance or subclass of” relation
......................................................................
Add support for “instance or subclass of” relation
The “instance or subclass of” relation is the union of the “instance of”
and “subclass of” relations: the link from the item to the class can
either be via the “instance of” property or via the “subclass of”
property. After that, as usual, only “subclass of” is processed: the
relation does *not* mean that “instance of” and “subclass of” can be
mixed in the whole hierarchy – only on the first link!
TypeCheckerHelper::hasClassInRelation is changed to accept a list of
relation property IDs instead of a single one, and gains a new helper
function to extract statements of several property IDs from a
StatementList (since StatementList doesn’t have such a function, as far
as I can tell). The two checkers translate the new relation type to a
relation property ID list of both the “instance of” and the “subclass
of” property ID.
Bug: T169858
Change-Id: Iedfe3013a10de0af7f12ea0f6fde7164dbe4f062
---
M i18n/en.json
M i18n/qqq.json
M src/ConstraintCheck/Checker/TypeChecker.php
M src/ConstraintCheck/Checker/ValueTypeChecker.php
M src/ConstraintCheck/Helper/TypeCheckerHelper.php
M tests/phpunit/Checker/TypeChecker/TypeCheckerHelperTest.php
M tests/phpunit/Checker/TypeChecker/TypeCheckerTest.php
M tests/phpunit/Checker/TypeChecker/ValueTypeCheckerTest.php
8 files changed, 160 insertions(+), 19 deletions(-)
Approvals:
WMDE-leszek: Checked; Looks good to me, approved
jenkins-bot: Verified
diff --git a/i18n/en.json b/i18n/en.json
index 2327d3e..090bdac 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -136,8 +136,10 @@
"wbqc-violation-message-symmetric": "$1 should also have the symmetric
statement $2 $3.",
"wbqc-violation-message-type-instance": "Entities using the $1 property
should be instances of {{PLURAL:$3|1=$5|2=$5 or $6|one of the following
classes}} (or of {{PLURAL:$3|1=a subclass of it|2=a subclass of them|one of
their subclasses}}), but $2 currently {{PLURAL:$3|1=isn't.|2=isn't.|isn't:
$4}}",
"wbqc-violation-message-type-subclass": "Entities using the $1 property
should be subclasses of {{PLURAL:$3|1=$5|2=$5 or $6|one of the following
classes}} (or of {{PLURAL:$3|1=a subclass of it|2=a subclass of them|one of
their subclasses}}), but $2 currently {{PLURAL:$3|1=isn't.|2=isn't.|isn't:
$4}}",
+ "wbqc-violation-message-type-instanceOrSubclass": "Entities using the
$1 property should be instances or subclasses of {{PLURAL:$3|1=$5|2=$5 or
$6|one of the following classes}} (or of {{PLURAL:$3|1=a subclass of it|2=a
subclass of them|one of their subclasses}}), but $2 currently
{{PLURAL:$3|1=isn't.|2=isn't.|isn't: $4}}",
"wbqc-violation-message-valueType-instance": "Values of $1 statements
should be instances of {{PLURAL:$3|1=$5|2=$5 or $6|one of the following
classes}} (or of {{PLURAL:$3|1=a subclass of it|2=a subclass of them|one of
their subclasses}}), but $2 currently {{PLURAL:$3|1=isn't.|2=isn't.|isn't:
$4}}",
"wbqc-violation-message-valueType-subclass": "Values of $1 statements
should be subclasses of {{PLURAL:$3|1=$5|2=$5 or $6|one of the following
classes}} (or of {{PLURAL:$3|1=a subclass of it|2=a subclass of them|one of
their subclasses}}), but $2 currently {{PLURAL:$3|1=isn't.|2=isn't.|isn't:
$4}}",
+ "wbqc-violation-message-valueType-instanceOrSubclass": "Values of $1
statements should be instances or subclasses of {{PLURAL:$3|1=$5|2=$5 or $6|one
of the following classes}} (or of {{PLURAL:$3|1=a subclass of it|2=a subclass
of them|one of their subclasses}}), but $2 currently
{{PLURAL:$3|1=isn't.|2=isn't.|isn't: $4}}",
"wbqc-violation-message-target-required-claim": "$1 should have
{{PLURAL:$3|0=a statement $2.|1=a statement $2 $5.|a statement for $2 with one
of the following values:$4}}",
"wbqc-violation-message-unique-value": "This property's value must not
be present on any other item, but is also present on {{PLURAL:$1|1=$3.|2=$3 and
$4.|the following items: $2}}",
"wbqc-violation-message-valueOnly": "This property should only be used
for the main value of a statement, not for qualifiers or references.",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 0fa388a..283dc8d 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -134,8 +134,10 @@
"wbqc-violation-message-symmetric": "Message for a violation of the
“Symmetric” constraint, when the symmetric statement of a statement does not
exist. $1, $2 and $3 contain the expected subject entity, property, and target
entity of the missing symmetric statement.",
"wbqc-violation-message-type-instance": "Message for a violation of the
“Type” constraint, when the subject of a statement should be an instance of a
certain type but isn't. $1 is the property of the statement, $2 is the subject
of the statement, $3 is the number of classes, $4 is an HTML list of all
classes, and $5, $6 etc. are the individual
classes.\n{{Related|wbqc-violation-message-type-subclass}}",
"wbqc-violation-message-type-subclass": "Message for a violation of the
“Type” constraint, when the subject of a statement should be a subclass of a
certain type but isn't. $1 is the property of the statement, $2 is the subject
of the statement, $3 is the number of classes, $4 is an HTML list of all
classes, and $5, $6 etc. are the individual
classes.\n{{Related|wbqc-violation-message-type-instance}}",
+ "wbqc-violation-message-type-instanceOrSubclass": "Message for a
violation of the “Type” constraint, when the subject of a statement should be
an instance or a subclass of a certain type but isn't. $1 is the property of
the statement, $2 is the subject of the statement, $3 is the number of classes,
$4 is an HTML list of all classes, and $5, $6 etc. are the individual
classes.\n{{Related|wbqc-violation-message-type-instance}}",
"wbqc-violation-message-valueType-instance": "Message for a violation
of the “Value type” constraint, when the value of a statement should be an
instance of a certain type but isn't. $1 is the property of the statement, $2
is the value of the statement, $3 is the number of classes, $4 is an HTML list
of all classes, and $5, $6 etc. are the individual
classes.\n{{Related|wbqc-violation-message-valueType-subclass}}",
"wbqc-violation-message-valueType-subclass": "Message for a violation
of the “Value type” constraint, when the value of a statement should be a
subclass of a certain type but isn't. $1 is the property of the statement, $2
is the value of the statement, $3 is the number of classes, $4 is an HTML list
of all classes, and $5, $6 etc. are the individual
classes.\n{{Related|wbqc-violation-message-valueType-instance}}",
+ "wbqc-violation-message-valueType-instanceOrSubclass": "Message for a
violation of the “Value type” constraint, when the value of a statement should
be an instance or a subclass of a certain type but isn't. $1 is the property of
the statement, $2 is the value of the statement, $3 is the number of classes,
$4 is an HTML list of all classes, and $5, $6 etc. are the individual
classes.\n{{Related|wbqc-violation-message-valueType-instance}}",
"wbqc-violation-message-target-required-claim": "Message for a
violation of the “Target required claim” constraint, when the target entity of
a statement is missing an expected statement. Parameters:\n* $1 is the subject
entity of the missing statement, i. e. the target entity of the statement that
has the constraint.\n* $2 is the property of the missing statement.\n* $3 is
the number of values permitted for the missing statement (or 0, in which case
the constraint only specifies that there should be a statement but not the
values it should have).\n* $4 is an HTML list of all values permitted for the
missing statement.\n* $5, $6 etc. are the individual values permitted for the
missing statement.\n{{Related|wbqc-violation-message-item}}",
"wbqc-violation-message-unique-value": "Message for violation of the
Unique Value constraint, when other items are found. Parameters:\n* $1 is the
number of other items with the same value.\n* $2 is an HTML list of all other
items found with the same value.\n* $3, $4 etc. are the individual other items
with the same value.",
"wbqc-violation-message-valueOnly": "Message for a violation of the
“used for values only” constraint, when a property intended for the main value
only was used in a qualifier or reference.",
diff --git a/src/ConstraintCheck/Checker/TypeChecker.php
b/src/ConstraintCheck/Checker/TypeChecker.php
index 3ce5359..d405edf 100644
--- a/src/ConstraintCheck/Checker/TypeChecker.php
+++ b/src/ConstraintCheck/Checker/TypeChecker.php
@@ -109,16 +109,18 @@
);
$relation =
$this->constraintParameterParser->parseRelationParameter(
$constraintParameters, $constraint->getConstraintTypeItemId() );
- if ( $relation === 'instance' ) {
- $relationId = $this->config->get(
'WBQualityConstraintsInstanceOfId' );
- } elseif ( $relation === 'subclass' ) {
- $relationId = $this->config->get(
'WBQualityConstraintsSubclassOfId' );
+ $relationIds = [];
+ if ( $relation === 'instance' || $relation ===
'instanceOrSubclass' ) {
+ $relationIds[] = $this->config->get(
'WBQualityConstraintsInstanceOfId' );
+ }
+ if ( $relation === 'subclass' || $relation ===
'instanceOrSubclass' ) {
+ $relationIds[] = $this->config->get(
'WBQualityConstraintsSubclassOfId' );
}
$parameters['relation'] = [ $relation ];
$result = $this->typeCheckerHelper->hasClassInRelation(
$context->getEntity()->getStatements(),
- $relationId,
+ $relationIds,
$classes
);
diff --git a/src/ConstraintCheck/Checker/ValueTypeChecker.php
b/src/ConstraintCheck/Checker/ValueTypeChecker.php
index 91a34c0..5f0ad93 100644
--- a/src/ConstraintCheck/Checker/ValueTypeChecker.php
+++ b/src/ConstraintCheck/Checker/ValueTypeChecker.php
@@ -121,10 +121,12 @@
);
$relation =
$this->constraintParameterParser->parseRelationParameter(
$constraintParameters, $constraint->getConstraintTypeItemId() );
- if ( $relation === 'instance' ) {
- $relationId = $this->config->get(
'WBQualityConstraintsInstanceOfId' );
- } elseif ( $relation === 'subclass' ) {
- $relationId = $this->config->get(
'WBQualityConstraintsSubclassOfId' );
+ $relationIds = [];
+ if ( $relation === 'instance' || $relation ===
'instanceOrSubclass' ) {
+ $relationIds[] = $this->config->get(
'WBQualityConstraintsInstanceOfId' );
+ }
+ if ( $relation === 'subclass' || $relation ===
'instanceOrSubclass' ) {
+ $relationIds[] = $this->config->get(
'WBQualityConstraintsSubclassOfId' );
}
$parameters['relation'] = [ $relation ];
@@ -163,7 +165,7 @@
$result = $this->typeCheckerHelper->hasClassInRelation(
$statements,
- $relationId,
+ $relationIds,
$classes
);
diff --git a/src/ConstraintCheck/Helper/TypeCheckerHelper.php
b/src/ConstraintCheck/Helper/TypeCheckerHelper.php
index 2c2487b..7225bb1 100644
--- a/src/ConstraintCheck/Helper/TypeCheckerHelper.php
+++ b/src/ConstraintCheck/Helper/TypeCheckerHelper.php
@@ -162,21 +162,21 @@
/**
* Checks, if one of the itemId serializations in $classesToCheck
* is contained in the list of $statements
- * via property $relationId or if it is a subclass of
- * one of the items referenced in $statements via $relationId
+ * via properties $relationIds or if it is a subclass of
+ * one of the items referenced in $statements via $relationIds
*
* @param StatementList $statements
- * @param string $relationId
+ * @param string[] $relationIds
* @param string[] $classesToCheck
*
* @return CachedBool
* @throws SparqlHelperException if SPARQL is used and the query times
out or some other error occurs
*/
- public function hasClassInRelation( StatementList $statements,
$relationId, array $classesToCheck ) {
+ public function hasClassInRelation( StatementList $statements, array
$relationIds, array $classesToCheck ) {
$metadatas = [];
/** @var Statement $statement */
- foreach ( $statements->getByPropertyId( new PropertyId(
$relationId ) ) as $statement ) {
+ foreach ( $this->getStatementsByPropertyIds( $statements,
$relationIds ) as $statement ) {
$mainSnak = $statement->getMainSnak();
if ( !$this->hasCorrectType( $mainSnak ) ) {
@@ -215,11 +215,30 @@
}
/**
+ * @param StatementList $statements
+ * @param string[] $propertyIdSerializations
+ * @return StatementList
+ */
+ private function getStatementsByPropertyIds(
+ StatementList $statements,
+ array $propertyIdSerializations
+ ) {
+ $statementArrays = [];
+ foreach ( $propertyIdSerializations as $propertyIdSerialization
) {
+ $propertyId = new PropertyId( $propertyIdSerialization
);
+ $statementArrays[] = $statements->getByPropertyId(
$propertyId )->toArray();
+ }
+ return new StatementList(
+ call_user_func_array( 'array_merge', $statementArrays )
+ );
+ }
+
+ /**
* @param PropertyId $propertyId ID of the property that introduced the
constraint
* @param EntityId $entityId ID of the entity that does not have the
required type
* @param string[] $classes item ID serializations of the classes that
$entityId should have
* @param string $checker "type" or "valueType" (for message key)
- * @param string $relation "instance" or "subclass" (for message key)
+ * @param string $relation "instance", "subclass", or
"instanceOrSubclass" (for message key)
*
* @return string Localized HTML message
*/
@@ -227,8 +246,10 @@
// Possible messages:
// wbqc-violation-message-type-instance
// wbqc-violation-message-type-subclass
+ // wbqc-violation-message-type-instanceOrSubclass
// wbqc-violation-message-valueType-instance
// wbqc-violation-message-valueType-subclass
+ // wbqc-violation-message-valueType-instanceOrSubclass
$message = wfMessage( 'wbqc-violation-message-' . $checker .
'-' . $relation );
$message->rawParams(
diff --git a/tests/phpunit/Checker/TypeChecker/TypeCheckerHelperTest.php
b/tests/phpunit/Checker/TypeChecker/TypeCheckerHelperTest.php
index 79df966..23360f2 100644
--- a/tests/phpunit/Checker/TypeChecker/TypeCheckerHelperTest.php
+++ b/tests/phpunit/Checker/TypeChecker/TypeCheckerHelperTest.php
@@ -97,21 +97,85 @@
$statement1 = new Statement( new PropertyValueSnak( new
PropertyId( 'P1' ), new EntityIdValue( new ItemId( 'Q42' ) ) ) );
$statement2 = new Statement( new PropertyValueSnak( new
PropertyId( 'P31' ), new EntityIdValue( new ItemId( 'Q1' ) ) ) );
$statements = new StatementList( [ $statement1, $statement2 ] );
- $this->assertTrue( $this->getHelper()->hasClassInRelation(
$statements, 'P31', [ 'Q1' ] )->getBool() );
+ $this->assertTrue( $this->getHelper()->hasClassInRelation(
$statements, [ 'P31' ], [ 'Q1' ] )->getBool() );
}
public function testHasClassInRelation_Invalid() {
$statement1 = new Statement( new PropertyValueSnak( new
PropertyId( 'P1' ), new EntityIdValue( new ItemId( 'Q42' ) ) ) );
$statement2 = new Statement( new PropertyValueSnak( new
PropertyId( 'P31' ), new EntityIdValue( new ItemId( 'Q100' ) ) ) );
$statements = new StatementList( [ $statement1, $statement2 ] );
- $this->assertFalse( $this->getHelper()->hasClassInRelation(
$statements, 'P31', [ 'Q1' ] )->getBool() );
+ $this->assertFalse( $this->getHelper()->hasClassInRelation(
$statements, [ 'P31' ], [ 'Q1' ] )->getBool() );
}
public function testHasClassInRelation_ValidWithIndirection() {
$statement1 = new Statement( new PropertyValueSnak( new
PropertyId( 'P1' ), new EntityIdValue( new ItemId( 'Q42' ) ) ) );
$statement2 = new Statement( new PropertyValueSnak( new
PropertyId( 'P31' ), new EntityIdValue( new ItemId( 'Q5' ) ) ) );
$statements = new StatementList( [ $statement1, $statement2 ] );
- $this->assertTrue( $this->getHelper()->hasClassInRelation(
$statements, 'P31', [ 'Q4' ] )->getBool() );
+ $this->assertTrue( $this->getHelper()->hasClassInRelation(
$statements, [ 'P31' ], [ 'Q4' ] )->getBool() );
+ }
+
+ /**
+ * Test the “instance or subclass of” relation.
+ * The statement list being tested links to Q1 with $firstRelation.
+ * If $secondRelation is not null, then Q1 links to Q2 with
$secondRelation.
+ *
+ * @param string $firstRelation 'instance' or 'subclass'
+ * @param string|null $secondRelation 'instance' or 'subclass'
+ * @param string $class item ID serialization
+ * @param bool $expected
+ * @dataProvider getInstanceOrSubclassOfProvider
+ */
+ public function testHasClassInRelation_InstanceOrSubclassOf(
+ $firstRelation,
+ $secondRelation,
+ $class,
+ $expected
+ ) {
+ $instanceOfId = $this->getDefaultConfig()->get(
'WBQualityConstraintsInstanceOfId' );
+ $subclassOfId = $this->getDefaultConfig()->get(
'WBQualityConstraintsSubclassOfId' );
+ $relationIds = [ $instanceOfId, $subclassOfId ];
+
+ $statements = new StatementList(
+ NewStatement::forProperty(
+ $firstRelation === 'instance' ?
+ $instanceOfId :
+ $subclassOfId
+ )
+ ->withValue( new ItemId( 'Q1' ) )
+ ->build()
+ );
+ $lookup = new InMemoryEntityLookup();
+
+ if ( $secondRelation !== null ) {
+ $q1 = NewItem::withId( 'Q1' )
+ ->andStatement(
+ NewStatement::forProperty(
+ $secondRelation === 'instance' ?
+ $instanceOfId :
+ $subclassOfId
+ )
+ ->withValue( new ItemId( 'Q2' )
)
+ )
+ ->build();
+ $lookup->addEntity( $q1 );
+ }
+ $helper = $this->getHelper( $lookup );
+
+ $result = $helper->hasClassInRelation( $statements,
$relationIds, [ $class ] );
+
+ $this->assertSame( $expected, $result->getBool() );
+ }
+
+ public function getInstanceOrSubclassOfProvider() {
+ return [
+ 'direct instance' => [ 'instance', null, 'Q1', true ],
+ 'direct subclass' => [ 'subclass', null, 'Q1', true ],
+ 'direct instance of unrelated' => [ 'instance', null,
'Q100', false ],
+ 'instance of subclass' => [ 'instance', 'subclass',
'Q2', true ],
+ 'subclass of subclass' => [ 'subclass', 'subclass',
'Q2', true ],
+ 'subclass of instance' => [ 'subclass', 'instance',
'Q2', false ],
+ 'instance of subclass of unrelated' => [ 'instance',
'subclass', 'Q200', false ],
+ ];
}
public function testIsSubclassOf_ValidWithIndirection() {
diff --git a/tests/phpunit/Checker/TypeChecker/TypeCheckerTest.php
b/tests/phpunit/Checker/TypeChecker/TypeCheckerTest.php
index 3e269fc..093fd77 100644
--- a/tests/phpunit/Checker/TypeChecker/TypeCheckerTest.php
+++ b/tests/phpunit/Checker/TypeChecker/TypeCheckerTest.php
@@ -139,6 +139,30 @@
$this->assertCompliance( $checkResult );
}
+ public function testTypeConstraintInstanceOrSubclassValidViaInstance() {
+ $entity = $this->lookup->getEntity( new ItemId( 'Q1' ) );
+ $constraintParameters = array_merge(
+ $this->relationParameter( 'instanceOrSubclass' ),
+ $this->classParameter( [ 'Q100', 'Q101' ] )
+ );
+ $constraint = $this->getConstraintMock( $constraintParameters );
+
+ $checkResult = $this->checker->checkConstraint( new
FakeSnakContext( $this->typeSnak, $entity ), $constraint );
+ $this->assertCompliance( $checkResult );
+ }
+
+ public function testTypeConstraintInstanceOrSubclassValidViaSubclass() {
+ $entity = $this->lookup->getEntity( new ItemId( 'Q4' ) );
+ $constraintParameters = array_merge(
+ $this->relationParameter( 'instanceOrSubclass' ),
+ $this->classParameter( [ 'Q100', 'Q101' ] )
+ );
+ $constraint = $this->getConstraintMock( $constraintParameters );
+
+ $checkResult = $this->checker->checkConstraint( new
FakeSnakContext( $this->typeSnak, $entity ), $constraint );
+ $this->assertCompliance( $checkResult );
+ }
+
public function testTypeConstraintInstanceInvalid() {
$entity = $this->lookup->getEntity( new ItemId( 'Q1' ) );
$constraintParameters = array_merge(
diff --git a/tests/phpunit/Checker/TypeChecker/ValueTypeCheckerTest.php
b/tests/phpunit/Checker/TypeChecker/ValueTypeCheckerTest.php
index 01a6067..fa846b3 100644
--- a/tests/phpunit/Checker/TypeChecker/ValueTypeCheckerTest.php
+++ b/tests/phpunit/Checker/TypeChecker/ValueTypeCheckerTest.php
@@ -141,6 +141,30 @@
$this->assertCompliance( $checkResult );
}
+ public function
testValueTypeConstraintInstanceOrSubclassValidViaInstance() {
+ $snak = new PropertyValueSnak( $this->valueTypePropertyId, new
EntityIdValue( new ItemId( 'Q1' ) ) );
+ $constraintParameters = array_merge(
+ $this->relationParameter( 'instanceOrSubclass' ),
+ $this->classParameter( [ 'Q100', 'Q101' ] )
+ );
+ $constraint = $this->getConstraintMock( $constraintParameters );
+
+ $checkResult = $this->checker->checkConstraint( new
FakeSnakContext( $snak ), $constraint );
+ $this->assertCompliance( $checkResult );
+ }
+
+ public function
testValueTypeConstraintInstanceOrSubclassValidViaSubclass() {
+ $snak = new PropertyValueSnak( $this->valueTypePropertyId, new
EntityIdValue( new ItemId( 'Q4' ) ) );
+ $constraintParameters = array_merge(
+ $this->relationParameter( 'instanceOrSubclass' ),
+ $this->classParameter( [ 'Q100', 'Q101' ] )
+ );
+ $constraint = $this->getConstraintMock( $constraintParameters );
+
+ $checkResult = $this->checker->checkConstraint( new
FakeSnakContext( $snak ), $constraint );
+ $this->assertCompliance( $checkResult );
+ }
+
public function testValueTypeConstraintInstanceInvalid() {
$snak = new PropertyValueSnak( $this->valueTypePropertyId, new
EntityIdValue( new ItemId( 'Q1' ) ) );
$constraintParameters = array_merge(
--
To view, visit https://gerrit.wikimedia.org/r/404711
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Iedfe3013a10de0af7f12ea0f6fde7164dbe4f062
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/WikibaseQualityConstraints
Gerrit-Branch: master
Gerrit-Owner: Lucas Werkmeister (WMDE) <[email protected]>
Gerrit-Reviewer: Lucas Werkmeister (WMDE) <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: WMDE-leszek <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits