Completed
Push — master ( 7880aa...3f359c )
by Thomas
05:14
created

MissingSubjectTripleNodeSimplifier::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1.008

Importance

Changes 8
Bugs 0 Features 1
Metric Value
c 8
b 0
f 1
dl 0
loc 5
ccs 4
cts 5
cp 0.8
rs 9.4285
cc 1
eloc 4
nc 1
nop 3
crap 1.008
1
<?php
2
3
namespace PPP\Wikidata\TreeSimplifier;
4
5
use Ask\Language\Description\Conjunction;
6
use Ask\Language\Description\Disjunction;
7
use Ask\Language\Description\SomeProperty;
8
use Ask\Language\Description\ValueDescription;
9
use Ask\Language\Option\QueryOptions;
10
use InvalidArgumentException;
11
use PPP\DataModel\AbstractNode;
12
use PPP\DataModel\IntersectionNode;
13
use PPP\DataModel\MissingNode;
14
use PPP\DataModel\ResourceListNode;
15
use PPP\DataModel\TripleNode;
16
use PPP\DataModel\UnionNode;
17
use PPP\Module\TreeSimplifier\NodeSimplifier;
18
use PPP\Module\TreeSimplifier\NodeSimplifierException;
19
use PPP\Module\TreeSimplifier\NodeSimplifierFactory;
20
use PPP\Wikidata\ValueParsers\ResourceListNodeParser;
21
use PPP\Wikidata\WikibaseResourceNode;
22
use Wikibase\DataModel\Entity\EntityIdValue;
23
use Wikibase\EntityStore\EntityStore;
24
25
/**
26
 * Simplifies a triple node when the subject is missing.
27
 *
28
 * @licence GPLv2+
29
 * @author Thomas Pellissier Tanon
30
 */
31
class MissingSubjectTripleNodeSimplifier implements NodeSimplifier {
32
33
	const QUERY_LIMIT = 50;
34
35
	/**
36
	 * @var NodeSimplifierFactory
37
	 */
38
	private $nodeSimplifierFactory;
39
40
	/**
41
	 * @var EntityStore
42
	 */
43
	private $entityStore;
44
45
	/**
46
	 * @var ResourceListNodeParser
47
	 */
48
	private $resourceListNodeParser;
49
50
	/**
51
	 * @param NodeSimplifierFactory $nodeSimplifierFactory
52
	 * @param EntityStore $entityStore
53
	 * @param ResourceListNodeParser $resourceListNodeParser
54
	 */
55 22
	public function __construct(NodeSimplifierFactory $nodeSimplifierFactory, EntityStore $entityStore, ResourceListNodeParser $resourceListNodeParser) {
56 22
		$this->nodeSimplifierFactory = $nodeSimplifierFactory;
57 22
		$this->entityStore = $entityStore;
58 22
		$this->resourceListNodeParser = $resourceListNodeParser;
59
	}
60
61
	/**
62
	 * @see AbstractNode::isSimplifierFor
63
	 */
64 5
	public function isSimplifierFor(AbstractNode $node) {
65
		return $this->isTripleWithMissingSubject($node) || $this->isTripleWithMissingSubjectOperator($node);
66 5
	}
67
68 5
	private function isTripleWithMissingSubject(AbstractNode $node) {
69 5
		return $node instanceof TripleNode &&
70 1
			$node->getSubject() instanceof MissingNode;
71 5
	}
72
73 4
	private function isTripleWithMissingSubjectOperator(AbstractNode $node) {
74 3
		if(!($node instanceof IntersectionNode || $node instanceof UnionNode)) {
75 4
			return false;
76
		}
77
78
		foreach($node->getOperands() as $operand) {
79
			if(!$this->isSimplifierFor($operand)) {
80
				return false;
81
			}
82
		}
83
84
		return true;
85 3
	}
86
87
	/**
88
	 * @see NodeSimplifier::doSimplification
89
	 */
90 14
	public function simplify(AbstractNode $node) {
91
		try {
92 8
			$query = $this->buildQueryForNode($node);
93
		} catch(EmptyQueryException $e) {
94
			return new ResourceListNode();
95 10
		}
96
97
		$entityIds = $this->entityStore->getItemIdForQueryLookup()->getItemIdsForQuery($query, new QueryOptions(self::QUERY_LIMIT, 0));
98
99
		return $this->formatQueryResult($entityIds);
100 14
	}
101
102 12
	private function buildQueryForNode(AbstractNode $node) {
103 9
		if($node instanceof UnionNode) {
104 3
			return $this->buildQueryForUnion($node);
105 10
		} else if($node instanceof IntersectionNode) {
106 1
			return $this->buildQueryForIntersection($node);
107 12
		} else if($node instanceof TripleNode) {
108 6
			return $this->buildQueryForTriple($node);
109
		} else {
110
			throw new InvalidArgumentException('Unsupported Node');
111
		}
112 9
	}
113
114 5
	private function buildQueryForUnion(UnionNode $unionNode) {
115 5
		$queries = array();
116
117 5
		foreach($unionNode->getOperands() as $operandNode) {
118
			try {
119 2
				$queries[] = $this->buildQueryForNode($operandNode);
120
			} catch(EmptyQueryException $e) {
121
				//May be ignored: we are in a union
122 2
			}
123 2
		}
124
125 4
		switch(count($queries)) {
126
			case 0:
127
				throw new EmptyQueryException();
128
			case 1:
129
				return reset($queries);
130
			default:
131
				return new Disjunction($queries);
132
		}
133 4
	}
134
135 3
	private function buildQueryForIntersection(IntersectionNode $intersectionNode) {
136 3
		$queries = array();
137
138 3
		foreach($intersectionNode->getOperands() as $operandNode) {
139 1
			$queries[] = $this->buildQueryForNode($operandNode);
140 1
		}
141
142 2
		switch(count($queries)) {
143
			case 1:
144
				return reset($queries);
145
			default:
146
				return new Conjunction($queries);
147
		}
148 2
	}
149
150 11
	private function buildQueryForTriple(TripleNode $triple) {
151
		if(!($triple->getSubject()->equals(new MissingNode()))) {
152
			throw new InvalidArgumentException('Triple whose subject is not missing given.');
153
		}
154
155
		$simplifier = $this->nodeSimplifierFactory->newNodeSimplifier();
156
		$predicate = $simplifier->simplify($triple->getPredicate());
157
		$object = $simplifier->simplify($triple->getObject());
158 8
		if(!($predicate instanceof ResourceListNode && $object instanceof ResourceListNode)) {
159
			throw new NodeSimplifierException('Invalid triple');
160
		}
161
162
		$propertyNodes = $this->resourceListNodeParser->parse($predicate, 'wikibase-property');
163 8
		$queryParameters = array();
164
165 8
		foreach($this->bagsPropertiesPerType($propertyNodes) as $objectType => $propertyNodes) {
166
			$objectNodes = $this->resourceListNodeParser->parse($object, $objectType);
167
168
			foreach($propertyNodes as $propertyNode) {
169
				try {
170 1
					$queryParameters[] = $this->buildQueryForProperty($propertyNode, $objectNodes);
171
				} catch(EmptyQueryException $e) {
172
					//May be ignored: we are in a union
173 4
				}
174
			}
175 2
		}
176
177 8
		switch(count($queryParameters)) {
178
			case 0:
179
				throw new EmptyQueryException();
180
			case 1:
181
				return reset($queryParameters);
182
			default:
183
				return new Disjunction($queryParameters);
184
		}
185 11
	}
186
187 8
	private function bagsPropertiesPerType($propertyNodes) {
188 8
		$propertyNodesPerType = array();
189
190
		/** @var WikibaseResourceNode $propertyNode */
191
		foreach($propertyNodes as $propertyNode) {
192 4
			$objectType = $this->entityStore->getPropertyLookup()->getPropertyForId(
193
				$propertyNode->getDataValue()->getEntityId()
194
			)->getDataTypeId();
195 4
			$propertyNodesPerType[$objectType][] = $propertyNode;
196 2
		}
197
198 8
		return $propertyNodesPerType;
199
	}
200
201 4
	private function buildQueryForProperty(WikibaseResourceNode $predicate, ResourceListNode $objectList) {
202
		return new SomeProperty(
203
			new EntityIdValue($predicate->getDataValue()->getEntityId()),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface DataValues\DataValue as the method getEntityId() does only exist in the following implementations of said interface: Wikibase\DataModel\Entity\EntityIdValue.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
204 4
			$this->buildValueDescriptionsForObjects($objectList)
205
		);
206 4
	}
207
208 4
	private function buildValueDescriptionsForObjects(ResourceListNode $objectList) {
209 4
		$valueDescriptions = array();
210
211
		/** @var WikibaseResourceNode $object */
212
		foreach($objectList as $object) {
213
			$valueDescriptions[] = new ValueDescription($object->getDataValue());
214 1
		}
215
216 4
		switch(count($valueDescriptions)) {
217
			case 0:
218
				throw new EmptyQueryException();
219
			case 1:
220
				return reset($valueDescriptions);
221
			default:
222
				return new Disjunction($valueDescriptions);
223
		}
224 4
	}
225
226 6
	private function formatQueryResult(array $subjectIds) {
227 6
		$nodes = array();
228
229
		foreach($subjectIds as $subjectId) {
230
			$nodes[] = new WikibaseResourceNode('', new EntityIdValue($subjectId));
231
		}
232
233
		return new ResourceListNode($nodes);
234 6
	}
235
}
236