These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Wikibase\DataModel\Services\Lookup; |
||
4 | |||
5 | use Wikibase\DataModel\Entity\EntityId; |
||
6 | use Wikibase\DataModel\Entity\EntityIdValue; |
||
7 | use Wikibase\DataModel\Entity\PropertyId; |
||
8 | use Wikibase\DataModel\Services\Entity\EntityPrefetcher; |
||
9 | use Wikibase\DataModel\Snak\PropertyValueSnak; |
||
10 | use Wikibase\DataModel\Snak\Snak; |
||
11 | use Wikibase\DataModel\Statement\StatementListProvider; |
||
12 | |||
13 | /** |
||
14 | * Service for getting the closest entity (out of a specified set), |
||
15 | * from a given starting entity. The starting entity, and the target entities |
||
16 | * are (potentially indirectly, via intermediate entities) linked by statements |
||
17 | * with a given property ID, pointing from the starting entity to one of the |
||
18 | * target entities. |
||
19 | * |
||
20 | * @since 3.10 |
||
21 | * |
||
22 | * @license GPL-2.0-or-later |
||
23 | * @author Marius Hoch |
||
24 | */ |
||
25 | class EntityRetrievingClosestReferencedEntityIdLookup implements ReferencedEntityIdLookup { |
||
26 | |||
27 | /** |
||
28 | * @var EntityLookup |
||
29 | */ |
||
30 | private $entityLookup; |
||
31 | |||
32 | /** |
||
33 | * @var EntityPrefetcher |
||
34 | */ |
||
35 | private $entityPrefetcher; |
||
36 | |||
37 | /** |
||
38 | * @var int Maximum search depth: Maximum number of intermediate entities to search through. |
||
39 | * For example 0 means that only the entities immediately referenced will be found. |
||
40 | */ |
||
41 | private $maxDepth; |
||
42 | |||
43 | /** |
||
44 | * @var int Maximum number of entities to retrieve. |
||
45 | */ |
||
46 | private $maxEntityVisits; |
||
47 | |||
48 | /** |
||
49 | * Map (entity id => true) of already visited entities. |
||
50 | * |
||
51 | * @var bool[] |
||
52 | */ |
||
53 | private $alreadyVisited = []; |
||
54 | |||
55 | /** |
||
56 | * @param EntityLookup $entityLookup |
||
57 | * @param EntityPrefetcher $entityPrefetcher |
||
58 | * @param int $maxDepth Maximum search depth: Maximum number of intermediate entities to search through. |
||
59 | * For example if 0 is given, only the entities immediately referenced will be found. |
||
60 | * If this limit gets exhausted, a MaxReferenceDepthExhaustedException is thrown. |
||
61 | * @param int $maxEntityVisits Maximum number of entities to retrieve during a lookup. |
||
62 | * If this limit gets exhausted, a MaxReferencedEntityVisitsExhaustedException is thrown. |
||
63 | */ |
||
64 | public function __construct( |
||
65 | EntityLookup $entityLookup, |
||
66 | EntityPrefetcher $entityPrefetcher, |
||
67 | $maxDepth, |
||
68 | $maxEntityVisits |
||
69 | ) { |
||
70 | $this->entityLookup = $entityLookup; |
||
71 | $this->entityPrefetcher = $entityPrefetcher; |
||
72 | $this->maxDepth = $maxDepth; |
||
73 | $this->maxEntityVisits = $maxEntityVisits; |
||
74 | } |
||
75 | |||
76 | /** |
||
77 | * Get the closest entity (out of $toIds), from a given entity. The starting entity, and |
||
78 | * the target entities are (potentially indirectly, via intermediate entities) linked by |
||
79 | * statements with the given property ID, pointing from the starting entity to one of the |
||
80 | * target entities. |
||
81 | * |
||
82 | * @since 3.10 |
||
83 | * |
||
84 | * @param EntityId $fromId |
||
85 | * @param PropertyId $propertyId |
||
86 | * @param EntityId[] $toIds |
||
87 | * |
||
88 | * @return EntityId|null Returns null in case none of the target entities are referenced. |
||
89 | * @throws ReferencedEntityIdLookupException |
||
90 | */ |
||
91 | public function getReferencedEntityId( EntityId $fromId, PropertyId $propertyId, array $toIds ) { |
||
92 | if ( !$toIds ) { |
||
0 ignored issues
–
show
|
|||
93 | return null; |
||
94 | } |
||
95 | |||
96 | $this->alreadyVisited = []; |
||
97 | |||
98 | $steps = $this->maxDepth + 1; // Add one as checking $fromId already is a step |
||
99 | $toVisit = [ $fromId ]; |
||
100 | |||
101 | while ( $steps-- ) { |
||
102 | $this->entityPrefetcher->prefetch( $toVisit ); |
||
103 | $toVisitNext = []; |
||
104 | |||
105 | foreach ( $toVisit as $curId ) { |
||
106 | $result = $this->processEntityById( $curId, $fromId, $propertyId, $toIds, $toVisitNext ); |
||
0 ignored issues
–
show
Are you sure the assignment to
$result is correct as $this->processEntityById..., $toIds, $toVisitNext) (which targets Wikibase\DataModel\Servi...up::processEntityById() ) seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.
Loading history...
|
|||
107 | if ( $result ) { |
||
108 | return $result; |
||
109 | } |
||
110 | } |
||
111 | // Remove already visited entities |
||
112 | $toVisit = array_unique( |
||
113 | array_diff( $toVisitNext, array_keys( $this->alreadyVisited ) ) |
||
114 | ); |
||
115 | |||
116 | if ( !$toVisit ) { |
||
0 ignored issues
–
show
The expression
$toVisit of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
117 | return null; |
||
118 | } |
||
119 | } |
||
120 | |||
121 | // Exhausted the max. depth without finding anything. |
||
122 | throw new MaxReferenceDepthExhaustedException( |
||
123 | $fromId, |
||
124 | $propertyId, |
||
125 | $toIds, |
||
126 | $this->maxDepth |
||
127 | ); |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * Find out whether an entity (directly) references one of the target ids. |
||
132 | * |
||
133 | * @param EntityId $id Id of the entity to process |
||
134 | * @param EntityId $fromId Id this lookup started from |
||
135 | * @param PropertyId $propertyId |
||
136 | * @param EntityId[] $toIds |
||
137 | * @param EntityId[] &$toVisit List of entities that still need to be checked |
||
138 | * @return EntityId|null Target id the entity refers to, null if none. |
||
139 | */ |
||
140 | private function processEntityById( |
||
141 | EntityId $id, |
||
142 | EntityId $fromId, |
||
143 | PropertyId $propertyId, |
||
144 | array $toIds, |
||
145 | array &$toVisit |
||
146 | ) { |
||
147 | $entity = $this->getEntity( $id, $fromId, $propertyId, $toIds ); |
||
148 | if ( !$entity ) { |
||
149 | return null; |
||
150 | } |
||
151 | |||
152 | $mainSnaks = $this->getMainSnaks( $entity, $propertyId ); |
||
153 | |||
154 | foreach ( $mainSnaks as $mainSnak ) { |
||
155 | $result = $this->processSnak( $mainSnak, $toVisit, $toIds ); |
||
0 ignored issues
–
show
Are you sure the assignment to
$result is correct as $this->processSnak($mainSnak, $toVisit, $toIds) (which targets Wikibase\DataModel\Servi...IdLookup::processSnak() ) seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.
Loading history...
|
|||
156 | if ( $result ) { |
||
157 | return $result; |
||
158 | } |
||
159 | } |
||
160 | |||
161 | return null; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * @param EntityId $id Id of the entity to get |
||
166 | * @param EntityId $fromId Id this lookup started from |
||
167 | * @param PropertyId $propertyId |
||
168 | * @param EntityId[] $toIds |
||
169 | * |
||
170 | * @return StatementListProvider|null Null if not applicable. |
||
171 | */ |
||
172 | private function getEntity( EntityId $id, EntityId $fromId, PropertyId $propertyId, array $toIds ) { |
||
173 | if ( isset( $this->alreadyVisited[$id->getSerialization()] ) ) { |
||
174 | trigger_error( |
||
175 | 'Entity ' . $id->getSerialization() . ' already visited.', |
||
176 | E_USER_WARNING |
||
177 | ); |
||
178 | |||
179 | return null; |
||
180 | } |
||
181 | |||
182 | $this->alreadyVisited[$id->getSerialization()] = true; |
||
183 | |||
184 | if ( count( $this->alreadyVisited ) > $this->maxEntityVisits ) { |
||
185 | throw new MaxReferencedEntityVisitsExhaustedException( |
||
186 | $fromId, |
||
187 | $propertyId, |
||
188 | $toIds, |
||
189 | $this->maxEntityVisits |
||
190 | ); |
||
191 | } |
||
192 | |||
193 | try { |
||
194 | $entity = $this->entityLookup->getEntity( $id ); |
||
195 | } catch ( EntityLookupException $ex ) { |
||
196 | throw new ReferencedEntityIdLookupException( $fromId, $propertyId, $toIds, null, $ex ); |
||
197 | } |
||
198 | |||
199 | if ( !( $entity instanceof StatementListProvider ) ) { |
||
200 | return null; |
||
201 | } |
||
202 | |||
203 | return $entity; |
||
204 | } |
||
205 | |||
206 | /** |
||
207 | * Decide whether a single Snak is pointing to one of the target ids. |
||
208 | * |
||
209 | * @param Snak $snak |
||
210 | * @param EntityId[] &$toVisit List of entities that still need to be checked |
||
211 | * @param EntityId[] $toIds |
||
212 | * @return EntityId|null Target id the Snak refers to, null if none. |
||
213 | */ |
||
214 | private function processSnak( Snak $snak, array &$toVisit, array $toIds ) { |
||
215 | if ( ! ( $snak instanceof PropertyValueSnak ) ) { |
||
216 | return null; |
||
217 | } |
||
218 | $dataValue = $snak->getDataValue(); |
||
219 | if ( ! ( $dataValue instanceof EntityIdValue ) ) { |
||
220 | return null; |
||
221 | } |
||
222 | |||
223 | $entityId = $dataValue->getEntityId(); |
||
224 | if ( in_array( $entityId, $toIds, false ) ) { |
||
225 | return $entityId; |
||
226 | } |
||
227 | |||
228 | $toVisit[] = $entityId; |
||
229 | |||
230 | return null; |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * @param StatementListProvider $statementListProvider |
||
235 | * @param PropertyId $propertyId |
||
236 | * @return Snak[] |
||
237 | */ |
||
238 | private function getMainSnaks( |
||
239 | StatementListProvider $statementListProvider, |
||
240 | PropertyId $propertyId |
||
241 | ) { |
||
242 | return $statementListProvider |
||
243 | ->getStatements() |
||
244 | ->getByPropertyId( $propertyId ) |
||
245 | ->getBestStatements() |
||
246 | ->getMainSnaks(); |
||
247 | } |
||
248 | |||
249 | } |
||
250 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.