doSimplification()   C
last analyzed

Complexity

Conditions 7
Paths 12

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 30
ccs 0
cts 20
cp 0
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 18
nc 12
nop 1
crap 56
1
<?php
2
3
namespace PPP\Wikidata\TreeSimplifier;
4
5
use InvalidArgumentException;
6
use PPP\DataModel\AbstractNode;
7
use PPP\DataModel\IntersectionNode;
8
use PPP\DataModel\MissingNode;
9
use PPP\DataModel\ResourceListNode;
10
use PPP\DataModel\TripleNode;
11
use PPP\Module\TreeSimplifier\NodeSimplifier;
12
use PPP\Module\TreeSimplifier\NodeSimplifierFactory;
13
use PPP\Wikidata\ValueParsers\ResourceListNodeParser;
14
use PPP\Wikidata\WikibaseResourceNode;
15
use Wikibase\DataModel\Entity\ItemId;
16
use Wikibase\DataModel\Snak\PropertyValueSnak;
17
use Wikibase\DataModel\Snak\SnakList;
18
use Wikibase\DataModel\Statement\Statement;
19
use Wikibase\EntityStore\EntityStore;
20
21
/**
22
 * Simplifies cases like [Q42, Q43] ∩ (?, instanceof, human) without requests to WikidataQuery: just check that the items
23
 * have PropertyValueSnak(instanceof, human).
24
 *
25
 * @licence AGPLv3+
26
 * @author Thomas Pellissier Tanon
27
 */
28
class IntersectionWithFilterNodeSimplifier implements NodeSimplifier {
29
30
	/**
31
	 * @var NodeSimplifierFactory
32
	 */
33
	private $nodeSimplifierFactory;
34
35
	/**
36
	 * @var EntityStore
37
	 */
38
	private $entityStore;
39
40
	/**
41
	 * @var ResourceListNodeParser
42
	 */
43
	private $resourceListNodeParser;
44
45
	/**
46
	 * @param NodeSimplifierFactory $nodeSimplifierFactory
47
	 * @param EntityStore $entityStore
48
	 * @param ResourceListNodeParser $resourceListNodeParser
49
	 */
50
	public function __construct(NodeSimplifierFactory $nodeSimplifierFactory, EntityStore $entityStore, ResourceListNodeParser $resourceListNodeParser) {
51
		$this->nodeSimplifierFactory = $nodeSimplifierFactory;
52
		$this->entityStore = $entityStore;
53
		$this->resourceListNodeParser = $resourceListNodeParser;
54
	}
55
56
	/**
57
	 * @see AbstractNode::isSimplifierFor
58
	 */
59
	public function isSimplifierFor(AbstractNode $node) {
60
		return $node instanceof IntersectionNode;
61
	}
62
63
	/**
64
	 * @see NodeSimplifier::doSimplification
65
	 */
66
	public function simplify(AbstractNode $node) {
67
		if(!$this->isSimplifierFor($node)) {
68
			throw new InvalidArgumentException('IntersectionWithFilterNodeSimplifier can only simplify IntersectionNode');
69
		}
70
71
		return $this->doSimplification($node);
0 ignored issues
show
Compatibility introduced by
$node of type object<PPP\DataModel\AbstractNode> is not a sub-type of object<PPP\DataModel\IntersectionNode>. It seems like you assume a child class of the class PPP\DataModel\AbstractNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
72
	}
73
74
	private function doSimplification(IntersectionNode $node) {
75
		$triplesWithMissingSubjects = array();
76
		$otherOperands = array();
77
78
		foreach($node->getOperands() as $operand) {
79
			if($this->isTripleWithMissingSubject($operand)) {
80
				$triplesWithMissingSubjects[] = $operand;
81
			} else {
82
				$otherOperands[] = $operand;
83
			}
84
		}
85
86
		if(empty($otherOperands) || empty($triplesWithMissingSubjects)) {
87
			return $node; //case of the MissingSubjectNodeSimplifier
88
		}
89
90
		$baseList = $this->nodeSimplifierFactory->newNodeSimplifier()->simplify(new IntersectionNode($otherOperands));
91
92
		if(!($baseList instanceof ResourceListNode)) {
93
			$triplesWithMissingSubjects[] = $baseList;
94
			return new IntersectionNode($triplesWithMissingSubjects);
95
		}
96
		$baseList = $this->resourceListNodeParser->parse($baseList, 'wikibase-item');
97
98
		foreach($triplesWithMissingSubjects as $tripleWithMissingSubject) {
99
			$baseList = $this->applyTripleAsFilter($baseList, $tripleWithMissingSubject);
100
		}
101
102
		return $baseList;
103
	}
104
105
	private function isTripleWithMissingSubject(AbstractNode $node) {
106
		return $node instanceof TripleNode &&
107
			$node->getSubject() instanceof MissingNode &&
108
			$node->getPredicate() instanceof ResourceListNode &&
109
			$node->getObject() instanceof ResourceListNode;
110
	}
111
112
	private function applyTripleAsFilter(ResourceListNode $baseList, TripleNode $triple) {
113
		$possibleSnaks = $this->buildPossibleSnaks($triple);
114
		$filteredList = array();
115
116
		foreach($baseList as $resource) {
117
			if($this->isOneOfSnakInItem($resource->getDataValue()->getEntityId(), $possibleSnaks)) {
118
				$filteredList[] = $resource;
119
			}
120
		}
121
122
		return new ResourceListNode($filteredList);
123
	}
124
125
	private function isOneOfSnakInItem(ItemId $itemId, SnakList $snaks) {
126
		$item = $this->entityStore->getItemLookup()->getItemForId($itemId);
127
128
		/** @var Statement $statement */
129
		foreach($item->getStatements() as $statement) {
130
			if($snaks->hasSnak($statement->getMainSnak())) {
131
				return true;
132
			}
133
		}
134
135
		return false;
136
	}
137
138
	private function buildPossibleSnaks(TripleNode $triple) {
139
		$propertyNodes = $this->resourceListNodeParser->parse($triple->getPredicate(), 'wikibase-property');
0 ignored issues
show
Compatibility introduced by
$triple->getPredicate() of type object<PPP\DataModel\AbstractNode> is not a sub-type of object<PPP\DataModel\ResourceListNode>. It seems like you assume a child class of the class PPP\DataModel\AbstractNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
140
		$possibleSnaks = new SnakList();
141
142
		foreach($this->bagsPropertiesPerType($propertyNodes) as $objectType => $propertyNodes) {
143
			$objectNodes = $this->resourceListNodeParser->parse($triple->getObject(), $objectType);
0 ignored issues
show
Compatibility introduced by
$triple->getObject() of type object<PPP\DataModel\AbstractNode> is not a sub-type of object<PPP\DataModel\ResourceListNode>. It seems like you assume a child class of the class PPP\DataModel\AbstractNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
144
145
			foreach($propertyNodes as $property) {
146
				foreach($objectNodes as $object) {
147
					$possibleSnaks->addSnak(new PropertyValueSnak($property->getDataValue()->getEntityId(), $object->getDataValue()));
148
				}
149
			}
150
		}
151
152
		return $possibleSnaks;
153
	}
154
155
	private function bagsPropertiesPerType($propertyNodes) {
156
		$propertyNodesPerType = array();
157
158
		/** @var WikibaseResourceNode $propertyNode */
159
		foreach($propertyNodes as $propertyNode) {
160
			$objectType = $this->entityStore->getPropertyLookup()->getPropertyForId(
161
				$propertyNode->getDataValue()->getEntityId()
162
			)->getDataTypeId();
163
			$propertyNodesPerType[$objectType][] = $propertyNode;
164
		}
165
166
		return $propertyNodesPerType;
167
	}
168
}
169