MissingSubjectTripleNodeSimplifier   C
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 205
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 21

Test Coverage

Coverage 99.13%

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 21
dl 0
loc 205
ccs 114
cts 115
cp 0.9913
rs 5.1017
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A isSimplifierFor() 0 3 2
A isTripleWithMissingSubject() 0 4 2
B isTripleWithMissingSubjectOperator() 0 13 5
A simplify() 0 11 2
A buildQueryForNode() 0 11 4
B buildQueryForUnion() 0 20 5
A buildQueryForIntersection() 0 14 3
D buildQueryForTriple() 0 36 9
A bagsPropertiesPerType() 0 13 2
A buildQueryForProperty() 0 6 1
A buildValueDescriptionsForObjects() 0 17 4
A formatQueryResult() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like MissingSubjectTripleNodeSimplifier often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MissingSubjectTripleNodeSimplifier, and based on these observations, apply Extract Interface, too.

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 AGPLv3+
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 22
	}
60
61
	/**
62
	 * @see AbstractNode::isSimplifierFor
63
	 */
64 7
	public function isSimplifierFor(AbstractNode $node) {
65 7
		return $this->isTripleWithMissingSubject($node) || $this->isTripleWithMissingSubjectOperator($node);
66
	}
67
68 7
	private function isTripleWithMissingSubject(AbstractNode $node) {
69 7
		return $node instanceof TripleNode &&
70 7
			$node->getSubject() instanceof MissingNode;
71
	}
72
73 5
	private function isTripleWithMissingSubjectOperator(AbstractNode $node) {
74 5
		if(!($node instanceof IntersectionNode || $node instanceof UnionNode)) {
75 4
			return false;
76
		}
77
78 2
		foreach($node->getOperands() as $operand) {
79 2
			if(!$this->isSimplifierFor($operand)) {
80 1
				return false;
81
			}
82 1
		}
83
84 1
		return true;
85
	}
86
87
	/**
88
	 * @see NodeSimplifier::doSimplification
89
	 */
90 14
	public function simplify(AbstractNode $node) {
91
		try {
92 14
			$query = $this->buildQueryForNode($node);
93 14
		} catch(EmptyQueryException $e) {
94 4
			return new ResourceListNode();
95
		}
96
97 6
		$entityIds = $this->entityStore->getItemIdForQueryLookup()->getItemIdsForQuery($query, new QueryOptions(self::QUERY_LIMIT, 0));
98
99 6
		return $this->formatQueryResult($entityIds);
100
	}
101
102 14
	private function buildQueryForNode(AbstractNode $node) {
103 14
		if($node instanceof UnionNode) {
104 5
			return $this->buildQueryForUnion($node);
105 13
		} else if($node instanceof IntersectionNode) {
106 3
			return $this->buildQueryForIntersection($node);
107 13
		} else if($node instanceof TripleNode) {
108 12
			return $this->buildQueryForTriple($node);
109
		} else {
110 1
			throw new InvalidArgumentException('Unsupported Node');
111
		}
112
	}
113
114 5
	private function buildQueryForUnion(UnionNode $unionNode) {
115 5
		$queries = array();
116
117 5
		foreach($unionNode->getOperands() as $operandNode) {
118
			try {
119 4
				$queries[] = $this->buildQueryForNode($operandNode);
120 4
			} catch(EmptyQueryException $e) {
121
				//May be ignored: we are in a union
122
			}
123 4
		}
124
125 4
		switch(count($queries)) {
126 4
			case 0:
127 2
				throw new EmptyQueryException();
128 2
			case 1:
129 1
				return reset($queries);
130 1
			default:
131 1
				return new Disjunction($queries);
132 1
		}
133
	}
134
135 3
	private function buildQueryForIntersection(IntersectionNode $intersectionNode) {
136 3
		$queries = array();
137
138 3
		foreach($intersectionNode->getOperands() as $operandNode) {
139 3
			$queries[] = $this->buildQueryForNode($operandNode);
140 2
		}
141
142 2
		switch(count($queries)) {
143 2
			case 1:
144 1
				return reset($queries);
145 1
			default:
146 1
				return new Conjunction($queries);
147 1
		}
148
	}
149
150 12
	private function buildQueryForTriple(TripleNode $triple) {
151 12
		if(!($triple->getSubject()->equals(new MissingNode()))) {
152 3
			throw new InvalidArgumentException('Triple whose subject is not missing given.');
153
		}
154
155 9
		$simplifier = $this->nodeSimplifierFactory->newNodeSimplifier();
156 9
		$predicate = $simplifier->simplify($triple->getPredicate());
157 9
		$object = $simplifier->simplify($triple->getObject());
158 9
		if(!($predicate instanceof ResourceListNode && $object instanceof ResourceListNode)) {
159
			throw new NodeSimplifierException('Invalid triple');
160
		}
161
162 9
		$propertyNodes = $this->resourceListNodeParser->parse($predicate, 'wikibase-property');
163 9
		$queryParameters = array();
164
165 9
		foreach($this->bagsPropertiesPerType($propertyNodes) as $objectType => $propertyNodes) {
166 7
			$objectNodes = $this->resourceListNodeParser->parse($object, $objectType);
167
168 7
			foreach($propertyNodes as $propertyNode) {
169
				try {
170 7
					$queryParameters[] = $this->buildQueryForProperty($propertyNode, $objectNodes);
171 7
				} catch(EmptyQueryException $e) {
172
					//May be ignored: we are in a union
173
				}
174 7
			}
175 9
		}
176
177 9
		switch(count($queryParameters)) {
178 9
			case 0:
179 3
				throw new EmptyQueryException();
180 6
			case 1:
181 4
				return reset($queryParameters);
182 2
			default:
183 2
				return new Disjunction($queryParameters);
184 2
		}
185
	}
186
187 9
	private function bagsPropertiesPerType($propertyNodes) {
188 9
		$propertyNodesPerType = array();
189
190
		/** @var WikibaseResourceNode $propertyNode */
191 9
		foreach($propertyNodes as $propertyNode) {
192 7
			$objectType = $this->entityStore->getPropertyLookup()->getPropertyForId(
193 7
				$propertyNode->getDataValue()->getEntityId()
194 7
			)->getDataTypeId();
195 7
			$propertyNodesPerType[$objectType][] = $propertyNode;
196 9
		}
197
198 9
		return $propertyNodesPerType;
199
	}
200
201 7
	private function buildQueryForProperty(WikibaseResourceNode $predicate, ResourceListNode $objectList) {
202 7
		return new SomeProperty(
203 7
			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 7
			$this->buildValueDescriptionsForObjects($objectList)
205 6
		);
206
	}
207
208 7
	private function buildValueDescriptionsForObjects(ResourceListNode $objectList) {
209 7
		$valueDescriptions = array();
210
211
		/** @var WikibaseResourceNode $object */
212 7
		foreach($objectList as $object) {
213 6
			$valueDescriptions[] = new ValueDescription($object->getDataValue());
214 7
		}
215
216 7
		switch(count($valueDescriptions)) {
217 7
			case 0:
218 1
				throw new EmptyQueryException();
219 6
			case 1:
220 5
				return reset($valueDescriptions);
221 1
			default:
222 1
				return new Disjunction($valueDescriptions);
223 1
		}
224
	}
225
226 6
	private function formatQueryResult(array $subjectIds) {
227 6
		$nodes = array();
228
229 6
		foreach($subjectIds as $subjectId) {
230 6
			$nodes[] = new WikibaseResourceNode('', new EntityIdValue($subjectId));
231 6
		}
232
233 6
		return new ResourceListNode($nodes);
234
	}
235
}
236