This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
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\Repo\Rdf; |
||
4 | |||
5 | use PageProps; |
||
6 | use SplQueue; |
||
7 | use Wikibase\DataModel\Entity\EntityDocument; |
||
8 | use Wikibase\DataModel\Entity\EntityId; |
||
9 | use Wikibase\DataModel\Entity\PropertyId; |
||
10 | use Wikibase\DataModel\Services\Lookup\EntityLookup; |
||
11 | use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup; |
||
12 | use Wikibase\Lib\Store\EntityTitleLookup; |
||
13 | use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException; |
||
14 | use Wikimedia\Purtle\RdfWriter; |
||
15 | |||
16 | /** |
||
17 | * RDF mapping for wikibase data model. |
||
18 | * |
||
19 | * @license GPL-2.0-or-later |
||
20 | */ |
||
21 | class RdfBuilder implements EntityRdfBuilder, EntityMentionListener { |
||
22 | |||
23 | /** |
||
24 | * A list of entities mentioned/touched to or by this builder. |
||
25 | * The prefixed entity IDs are used as keys in the array, the value 'true' |
||
26 | * is used to indicate that the entity has been resolved. If the value |
||
27 | * is an EntityId, this indicates that the entity has not yet been resolved |
||
28 | * (defined). |
||
29 | * |
||
30 | * @var (bool|EntityId)[] |
||
31 | */ |
||
32 | private $entitiesResolved = []; |
||
33 | |||
34 | /** |
||
35 | * A queue of entities to output by this builder. |
||
36 | * |
||
37 | * @var SplQueue<EntityDocument> |
||
38 | */ |
||
39 | private $entitiesToOutput; |
||
40 | |||
41 | /** |
||
42 | * What the serializer would produce? |
||
43 | * |
||
44 | * @var int |
||
45 | */ |
||
46 | private $produceWhat; |
||
47 | |||
48 | /** |
||
49 | * @var RdfWriter |
||
50 | */ |
||
51 | private $writer; |
||
52 | |||
53 | /** |
||
54 | * @var DedupeBag |
||
55 | */ |
||
56 | private $dedupeBag; |
||
57 | |||
58 | /** |
||
59 | * RDF builders to apply when building RDF for an entity. |
||
60 | * @var EntityRdfBuilder[] |
||
61 | */ |
||
62 | private $builders = []; |
||
63 | |||
64 | /** |
||
65 | * @var RdfVocabulary |
||
66 | */ |
||
67 | private $vocabulary; |
||
68 | |||
69 | /** |
||
70 | * @var PropertyDataTypeLookup |
||
71 | */ |
||
72 | private $propertyLookup; |
||
73 | |||
74 | /** |
||
75 | * @var ValueSnakRdfBuilderFactory |
||
76 | */ |
||
77 | private $valueSnakRdfBuilderFactory; |
||
78 | |||
79 | /** |
||
80 | * @var EntityTitleLookup |
||
81 | */ |
||
82 | private $titleLookup; |
||
83 | |||
84 | /** |
||
85 | * Page properties handler, can be null if we don't need them. |
||
86 | * @var PageProps|null |
||
87 | */ |
||
88 | private $pageProps; |
||
89 | |||
90 | /** |
||
91 | * Entity-specific RDF builders to apply when building RDF for an entity. |
||
92 | * @var EntityRdfBuilder[] |
||
93 | */ |
||
94 | private $entityRdfBuilders; |
||
95 | |||
96 | /** |
||
97 | * @param RdfVocabulary $vocabulary |
||
98 | * @param ValueSnakRdfBuilderFactory $valueSnakRdfBuilderFactory |
||
99 | * @param PropertyDataTypeLookup $propertyLookup |
||
100 | * @param EntityRdfBuilderFactory $entityRdfBuilderFactory |
||
101 | * @param int $flavor |
||
102 | * @param RdfWriter $writer |
||
103 | * @param DedupeBag $dedupeBag |
||
104 | * @param EntityTitleLookup $titleLookup |
||
105 | */ |
||
106 | public function __construct( |
||
107 | RdfVocabulary $vocabulary, |
||
108 | ValueSnakRdfBuilderFactory $valueSnakRdfBuilderFactory, |
||
109 | PropertyDataTypeLookup $propertyLookup, |
||
110 | EntityRdfBuilderFactory $entityRdfBuilderFactory, |
||
111 | $flavor, |
||
112 | RdfWriter $writer, |
||
113 | DedupeBag $dedupeBag, |
||
114 | EntityTitleLookup $titleLookup |
||
115 | ) { |
||
116 | $this->entitiesToOutput = new SplQueue(); |
||
117 | $this->vocabulary = $vocabulary; |
||
118 | $this->propertyLookup = $propertyLookup; |
||
119 | $this->valueSnakRdfBuilderFactory = $valueSnakRdfBuilderFactory; |
||
120 | $this->writer = $writer; |
||
121 | $this->produceWhat = $flavor; |
||
122 | $this->dedupeBag = $dedupeBag; |
||
123 | $this->titleLookup = $titleLookup; |
||
124 | |||
125 | // XXX: move construction of sub-builders to a factory class. |
||
126 | $this->builders[] = $entityRdfBuilderFactory->getTermRdfBuilder( $vocabulary, $writer ); |
||
127 | |||
128 | if ( $this->shouldProduce( RdfProducer::PRODUCE_TRUTHY_STATEMENTS ) ) { |
||
129 | $this->builders[] = $this->newTruthyStatementRdfBuilder(); |
||
130 | } |
||
131 | |||
132 | if ( $this->shouldProduce( RdfProducer::PRODUCE_ALL_STATEMENTS ) ) { |
||
133 | $this->builders[] = $this->newFullStatementRdfBuilder(); |
||
134 | } |
||
135 | |||
136 | $this->entityRdfBuilders = $entityRdfBuilderFactory->getEntityRdfBuilders( |
||
137 | $flavor, |
||
138 | $vocabulary, |
||
139 | $writer, |
||
140 | $this, |
||
141 | $dedupeBag |
||
142 | ); |
||
143 | } |
||
144 | |||
145 | /** |
||
146 | * @param int $flavorFlags Flavor flags to use for this builder |
||
147 | * @return SnakRdfBuilder |
||
148 | */ |
||
149 | private function newSnakBuilder( $flavorFlags ) { |
||
150 | $statementValueBuilder = $this->valueSnakRdfBuilderFactory->getValueSnakRdfBuilder( |
||
151 | $flavorFlags, |
||
152 | $this->vocabulary, |
||
153 | $this->writer, |
||
154 | $this, |
||
155 | $this->dedupeBag |
||
156 | ); |
||
157 | $snakBuilder = new SnakRdfBuilder( $this->vocabulary, $statementValueBuilder, $this->propertyLookup ); |
||
158 | $snakBuilder->setEntityMentionListener( $this ); |
||
159 | |||
160 | return $snakBuilder; |
||
161 | } |
||
162 | |||
163 | /** |
||
164 | * @return EntityRdfBuilder |
||
165 | */ |
||
166 | private function newTruthyStatementRdfBuilder() { |
||
167 | //NOTE: currently, the only simple values are supported in truthy mode! |
||
168 | $simpleSnakBuilder = $this->newSnakBuilder( $this->produceWhat & ~RdfProducer::PRODUCE_FULL_VALUES ); |
||
169 | $statementBuilder = new TruthyStatementRdfBuilder( $this->vocabulary, $this->writer, $simpleSnakBuilder ); |
||
170 | |||
171 | return $statementBuilder; |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * @return EntityRdfBuilder |
||
176 | */ |
||
177 | private function newFullStatementRdfBuilder() { |
||
178 | $snakBuilder = $this->newSnakBuilder( $this->produceWhat ); |
||
179 | |||
180 | $builder = new FullStatementRdfBuilder( $this->vocabulary, $this->writer, $snakBuilder ); |
||
181 | $builder->setDedupeBag( $this->dedupeBag ); |
||
182 | $builder->setProduceQualifiers( $this->shouldProduce( RdfProducer::PRODUCE_QUALIFIERS ) ); |
||
183 | $builder->setProduceReferences( $this->shouldProduce( RdfProducer::PRODUCE_REFERENCES ) ); |
||
184 | |||
185 | return $builder; |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * Start writing RDF document |
||
190 | * Note that this builder does not have to finish it, it may be finished later. |
||
191 | */ |
||
192 | public function startDocument() { |
||
193 | foreach ( $this->getNamespaces() as $gname => $uri ) { |
||
194 | $this->writer->prefix( $gname, $uri ); |
||
195 | } |
||
196 | |||
197 | $this->writer->start(); |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Finish writing the document |
||
202 | * After that, nothing should ever be written into the document. |
||
203 | */ |
||
204 | public function finishDocument() { |
||
205 | $this->writer->finish(); |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * Returns the RDF generated by the builder |
||
210 | * |
||
211 | * @return string RDF |
||
212 | */ |
||
213 | public function getRDF() { |
||
214 | return $this->writer->drain(); |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * Returns a map of namespace names to URIs |
||
219 | * |
||
220 | * @return array |
||
221 | */ |
||
222 | public function getNamespaces() { |
||
223 | return $this->vocabulary->getNamespaces(); |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * Get map of page properties used by this builder |
||
228 | * |
||
229 | * @return string[][] |
||
230 | */ |
||
231 | public function getPageProperties() { |
||
232 | return $this->vocabulary->getPageProperties(); |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Should we produce this aspect? |
||
237 | * |
||
238 | * @param int $what |
||
239 | * |
||
240 | * @return bool |
||
241 | */ |
||
242 | private function shouldProduce( $what ) { |
||
243 | return ( $this->produceWhat & $what ) !== 0; |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * @see EntityMentionListener::entityReferenceMentioned |
||
248 | * |
||
249 | * @param EntityId $id |
||
250 | */ |
||
251 | public function entityReferenceMentioned( EntityId $id ) { |
||
252 | if ( $this->shouldProduce( RdfProducer::PRODUCE_RESOLVED_ENTITIES ) ) { |
||
253 | $this->entityToResolve( $id ); |
||
254 | } |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * @see EntityMentionListener::propertyMentioned |
||
259 | * |
||
260 | * @param PropertyId $id |
||
261 | */ |
||
262 | public function propertyMentioned( PropertyId $id ) { |
||
263 | if ( $this->shouldProduce( RdfProducer::PRODUCE_PROPERTIES ) ) { |
||
264 | $this->entityToResolve( $id ); |
||
265 | } |
||
266 | } |
||
267 | |||
268 | /** |
||
269 | * @see EntityMentionListener::subEntityMentioned |
||
270 | * |
||
271 | * @param EntityDocument $entity |
||
272 | */ |
||
273 | public function subEntityMentioned( EntityDocument $entity ) { |
||
274 | $this->entitiesToOutput->enqueue( $entity ); |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * Registers an entity as mentioned. |
||
279 | * Will be recorded as unresolved |
||
280 | * if it wasn't already marked as resolved. |
||
281 | * |
||
282 | * @param EntityId $entityId |
||
283 | */ |
||
284 | private function entityToResolve( EntityId $entityId ) { |
||
285 | $prefixedId = $entityId->getSerialization(); |
||
286 | |||
287 | if ( !isset( $this->entitiesResolved[$prefixedId] ) ) { |
||
288 | $this->entitiesResolved[$prefixedId] = $entityId; |
||
289 | } |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * Registers an entity as resolved. |
||
294 | * |
||
295 | * @param EntityId $entityId |
||
296 | */ |
||
297 | private function entityResolved( EntityId $entityId ) { |
||
298 | $prefixedId = $entityId->getSerialization(); |
||
299 | $this->entitiesResolved[$prefixedId] = true; |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * Adds revision information about an entity's revision to the RDF graph. |
||
304 | * |
||
305 | * @todo extract into MetaDataRdfBuilder |
||
306 | * |
||
307 | * @param EntityId $entityId |
||
308 | * @param int $revision |
||
309 | * @param string $timestamp in TS_MW format |
||
310 | */ |
||
311 | public function addEntityRevisionInfo( EntityId $entityId, $revision, $timestamp ) { |
||
312 | $timestamp = wfTimestamp( TS_ISO_8601, $timestamp ); |
||
313 | $entityLName = $this->vocabulary->getEntityLName( $entityId ); |
||
314 | $entityRepositoryName = $this->vocabulary->getEntityRepositoryName( $entityId ); |
||
315 | |||
316 | $this->writer->about( $this->vocabulary->dataNamespaceNames[$entityRepositoryName], $entityLName ) |
||
317 | ->a( RdfVocabulary::NS_SCHEMA_ORG, "Dataset" ) |
||
318 | ->say( RdfVocabulary::NS_SCHEMA_ORG, 'about' ) |
||
319 | ->is( $this->vocabulary->entityNamespaceNames[$entityRepositoryName], $entityLName ); |
||
320 | |||
321 | if ( $this->shouldProduce( RdfProducer::PRODUCE_VERSION_INFO ) ) { |
||
322 | // Dumps don't need version/license info for each entity, since it is included in the dump header |
||
323 | $this->writer |
||
324 | ->say( RdfVocabulary::NS_CC, 'license' )->is( $this->vocabulary->getLicenseUrl() ) |
||
325 | ->say( RdfVocabulary::NS_SCHEMA_ORG, 'softwareVersion' )->value( RdfVocabulary::FORMAT_VERSION ); |
||
326 | } |
||
327 | |||
328 | $this->writer->say( RdfVocabulary::NS_SCHEMA_ORG, 'version' )->value( $revision, 'xsd', 'integer' ) |
||
329 | ->say( RdfVocabulary::NS_SCHEMA_ORG, 'dateModified' )->value( $timestamp, 'xsd', 'dateTime' ); |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * Set page props handler |
||
334 | * @param PageProps $pageProps |
||
335 | * @return self |
||
336 | */ |
||
337 | public function setPageProps( PageProps $pageProps ) { |
||
338 | $this->pageProps = $pageProps; |
||
339 | return $this; |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * Add page props information |
||
344 | * @param EntityId $entityId |
||
345 | */ |
||
346 | public function addEntityPageProps( EntityId $entityId ) { |
||
347 | if ( !$this->pageProps || !$this->shouldProduce( RdfProducer::PRODUCE_PAGE_PROPS ) ) { |
||
348 | return; |
||
349 | } |
||
350 | $title = $this->titleLookup->getTitleForId( $entityId ); |
||
351 | $props = $this->getPageProperties(); |
||
352 | if ( !$title || !$props ) { |
||
0 ignored issues
–
show
|
|||
353 | return; |
||
354 | } |
||
355 | $propValues = $this->pageProps->getProperties( $title, array_keys( $props ) ); |
||
356 | if ( !$propValues ) { |
||
357 | return; |
||
358 | } |
||
359 | $entityProps = reset( $propValues ); |
||
360 | if ( !$entityProps ) { |
||
361 | return; |
||
362 | } |
||
363 | |||
364 | $entityRepositoryName = $this->vocabulary->getEntityRepositoryName( $entityId ); |
||
365 | $entityLName = $this->vocabulary->getEntityLName( $entityId ); |
||
366 | |||
367 | foreach ( $entityProps as $name => $value ) { |
||
368 | if ( !isset( $props[$name]['name'] ) ) { |
||
369 | continue; |
||
370 | } |
||
371 | |||
372 | if ( isset( $props[$name]['type'] ) ) { |
||
373 | settype( $value, $props[$name]['type'] ); |
||
374 | } |
||
375 | |||
376 | $this->writer->about( $this->vocabulary->dataNamespaceNames[$entityRepositoryName], $entityLName ) |
||
377 | ->say( RdfVocabulary::NS_ONTOLOGY, $props[$name]['name'] ) |
||
378 | ->value( $value ); |
||
379 | } |
||
380 | } |
||
381 | |||
382 | /** |
||
383 | * Adds meta-information about an entity (such as the ID and type) to the RDF graph. |
||
384 | * |
||
385 | * @todo extract into MetaDataRdfBuilder |
||
386 | * |
||
387 | * @param EntityDocument $entity |
||
388 | */ |
||
389 | private function addEntityMetaData( EntityDocument $entity ) { |
||
390 | $entityId = $entity->getId(); |
||
391 | $entityLName = $this->vocabulary->getEntityLName( $entityId ); |
||
392 | $entityRepoName = $this->vocabulary->getEntityRepositoryName( $entityId ); |
||
393 | |||
394 | $this->writer->about( |
||
395 | $this->vocabulary->entityNamespaceNames[$entityRepoName], |
||
396 | $entityLName |
||
397 | ) |
||
398 | ->a( RdfVocabulary::NS_ONTOLOGY, $this->vocabulary->getEntityTypeName( $entity->getType() ) ); |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * Add an entity to the RDF graph, including all supported structural components |
||
403 | * of the entity and its sub entities. |
||
404 | * |
||
405 | * @param EntityDocument $entity the entity to output. |
||
406 | */ |
||
407 | public function addEntity( EntityDocument $entity ) { |
||
408 | $this->addSingleEntity( $entity ); |
||
409 | $this->addQueuedEntities(); |
||
410 | } |
||
411 | |||
412 | /** |
||
413 | * Add a single entity to the RDF graph, including all supported structural components |
||
414 | * of the entity. |
||
415 | * |
||
416 | * @param EntityDocument $entity the entity to output. |
||
417 | */ |
||
418 | private function addSingleEntity( EntityDocument $entity ) { |
||
419 | $this->addEntityMetaData( $entity ); |
||
420 | |||
421 | $type = $entity->getType(); |
||
422 | if ( !empty( $this->entityRdfBuilders[$type] ) ) { |
||
423 | $this->entityRdfBuilders[$type]->addEntity( $entity ); |
||
424 | } |
||
425 | |||
426 | foreach ( $this->builders as $builder ) { |
||
427 | $builder->addEntity( $entity ); |
||
428 | } |
||
429 | |||
430 | $this->entityResolved( $entity->getId() ); |
||
431 | } |
||
432 | |||
433 | /** |
||
434 | * Add the RDF serialization of all entities in the entitiesToOutput queue |
||
435 | */ |
||
436 | private function addQueuedEntities() { |
||
437 | while ( !$this->entitiesToOutput->isEmpty() ) { |
||
438 | $this->addSingleEntity( $this->entitiesToOutput->dequeue() ); |
||
439 | } |
||
440 | } |
||
441 | |||
442 | /** |
||
443 | * Add stubs for any entities that were previously mentioned (e.g. as properties |
||
444 | * or data values). |
||
445 | * |
||
446 | * @param EntityLookup $entityLookup |
||
447 | */ |
||
448 | public function resolveMentionedEntities( EntityLookup $entityLookup ) { |
||
449 | $hasRedirect = false; |
||
450 | |||
451 | foreach ( $this->entitiesResolved as $id ) { |
||
452 | // $value is true if the entity has already been resolved, |
||
453 | // or an EntityId to resolve. |
||
454 | if ( !( $id instanceof EntityId ) ) { |
||
455 | continue; |
||
456 | } |
||
457 | |||
458 | try { |
||
459 | $entity = $entityLookup->getEntity( $id ); |
||
460 | |||
461 | if ( !$entity ) { |
||
462 | continue; |
||
463 | } |
||
464 | |||
465 | $this->addEntityStub( $entity ); |
||
466 | } catch ( RevisionedUnresolvedRedirectException $ex ) { |
||
467 | // NOTE: this may add more entries to the end of entitiesResolved |
||
468 | $target = $ex->getRedirectTargetId(); |
||
469 | $this->addEntityRedirect( $id, $target ); |
||
470 | $hasRedirect = true; |
||
471 | } |
||
472 | } |
||
473 | |||
474 | // If we encountered redirects, the redirect targets may now need resolving. |
||
475 | // They actually got added to $this->entitiesResolved, but may not have been |
||
476 | // processed by the loop above, because they got added while the loop was in progress. |
||
477 | if ( $hasRedirect ) { |
||
478 | // Call resolveMentionedEntities() recursively to resolve any yet unresolved |
||
479 | // redirect targets. The regress will eventually terminate even for circular |
||
480 | // redirect chains, because the second time an entity ID is encountered, it |
||
481 | // will be marked as already resolved. |
||
482 | $this->resolveMentionedEntities( $entityLookup ); |
||
483 | } |
||
484 | } |
||
485 | |||
486 | /** |
||
487 | * Adds stub information for the given Entity to the RDF graph. |
||
488 | * Stub information means meta information and labels. |
||
489 | * |
||
490 | * @param EntityDocument $entity |
||
491 | */ |
||
492 | public function addEntityStub( EntityDocument $entity ) { |
||
493 | $this->addEntityMetaData( $entity ); |
||
494 | |||
495 | $type = $entity->getType(); |
||
496 | if ( !empty( $this->entityRdfBuilders[$type] ) ) { |
||
497 | $this->entityRdfBuilders[$type]->addEntityStub( $entity ); |
||
498 | } |
||
499 | |||
500 | foreach ( $this->builders as $builder ) { |
||
501 | $builder->addEntityStub( $entity ); |
||
502 | } |
||
503 | } |
||
504 | |||
505 | /** |
||
506 | * Declares $from to be an alias for $to, using the owl:sameAs relationship. |
||
507 | * |
||
508 | * @param EntityId $from |
||
509 | * @param EntityId $to |
||
510 | */ |
||
511 | public function addEntityRedirect( EntityId $from, EntityId $to ) { |
||
512 | $fromLName = $this->vocabulary->getEntityLName( $from ); |
||
513 | $fromRepoName = $this->vocabulary->getEntityRepositoryName( $from ); |
||
514 | $toLName = $this->vocabulary->getEntityLName( $to ); |
||
515 | $toRepoName = $this->vocabulary->getEntityRepositoryName( $to ); |
||
516 | |||
517 | $this->writer->about( $this->vocabulary->entityNamespaceNames[$fromRepoName], $fromLName ) |
||
518 | ->say( 'owl', 'sameAs' ) |
||
519 | ->is( $this->vocabulary->entityNamespaceNames[$toRepoName], $toLName ); |
||
520 | |||
521 | $this->entityResolved( $from ); |
||
522 | |||
523 | if ( $this->shouldProduce( RdfProducer::PRODUCE_RESOLVED_ENTITIES ) ) { |
||
524 | $this->entityToResolve( $to ); |
||
525 | } |
||
526 | } |
||
527 | |||
528 | /** |
||
529 | * Create header structure for the dump (this makes RdfProducer::PRODUCE_VERSION_INFO redundant) |
||
530 | * |
||
531 | * @param int $timestamp Timestamp (for testing) |
||
532 | */ |
||
533 | public function addDumpHeader( $timestamp = 0 ) { |
||
534 | // TODO: this should point to "this document" |
||
535 | $this->writer->about( RdfVocabulary::NS_ONTOLOGY, 'Dump' ) |
||
536 | ->a( RdfVocabulary::NS_SCHEMA_ORG, "Dataset" ) |
||
537 | ->a( 'owl', 'Ontology' ) |
||
538 | ->say( RdfVocabulary::NS_CC, 'license' )->is( $this->vocabulary->getLicenseUrl() ) |
||
539 | ->say( RdfVocabulary::NS_SCHEMA_ORG, 'softwareVersion' )->value( RdfVocabulary::FORMAT_VERSION ) |
||
540 | ->say( RdfVocabulary::NS_SCHEMA_ORG, 'dateModified' )->value( wfTimestamp( TS_ISO_8601, $timestamp ), 'xsd', 'dateTime' ) |
||
541 | ->say( 'owl', 'imports' )->is( RdfVocabulary::getOntologyURI() ); |
||
542 | } |
||
543 | |||
544 | } |
||
545 |
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.