Completed
Push — master ( 7b235c...d3db5d )
by Henri
02:20
created

GenericSparql::querySuperProperties()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 19
Code Lines 11

Duplication

Lines 19
Ratio 100 %

Importance

Changes 0
Metric Value
dl 19
loc 19
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 6
nop 1
1
<?php
2
3
/**
4
 * Generates SPARQL queries and provides access to the SPARQL endpoint.
5
 */
6
class GenericSparql {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
7
    /**
8
     * A SPARQL Client eg. an EasyRDF instance.
9
     * @property EasyRdf\Sparql\Client $client
10
     */
11
    protected $client;
12
    /**
13
     * Graph uri.
14
     * @property string $graph
15
     */
16
    protected $graph;
17
    /**
18
     * A SPARQL query graph part template.
19
     * @property string $graph
20
     */
21
    protected $graphClause;
22
    /**
23
     * Model instance.
24
     * @property Model $model
25
     */
26
    protected $model;
27
28
    /**
29
     * Cache used to avoid expensive shorten() calls
30
     * @property array $qnamecache
31
     */
32
    private $qnamecache = array();
33
34
    /**
35
     * Requires the following three parameters.
36
     * @param string $endpoint SPARQL endpoint address.
37
     * @param object $graph an EasyRDF SPARQL graph instance.
38
     * @param object $model a Model instance.
39
     */
40
    public function __construct($endpoint, $graph, $model) {
41
        $this->graph = $graph;
42
        $this->model = $model;
43
44
        // create the EasyRDF SPARQL client instance to use
45
        $this->initializeHttpClient();
46
        $this->client = new EasyRdf\Sparql\Client($endpoint);
47
48
        // set graphClause so that it can be used by all queries
49
        if ($this->isDefaultEndpoint()) // default endpoint; query any graph (and catch it in a variable)
50
        {
51
            $this->graphClause = "GRAPH $graph";
52
        } elseif ($graph) // query a specific graph
53
        {
54
            $this->graphClause = "GRAPH <$graph>";
55
        } else // query the default graph
56
        {
57
            $this->graphClause = "";
58
        }
59
60
    }
61
    
62
    /**
63
     * Execute the SPARQL query using the SPARQL client, logging it as well.
64
     * @param string $query SPARQL query to perform
65
     * @return object query result
66
     */
67
    protected function query($query) {
68
        $queryId = sprintf("%05d", rand(0, 99999));
69
        $logger = $this->model->getLogger();
70
        $logger->info("[qid $queryId] SPARQL query:\n$query\n");
71
        $starttime = microtime(true);
72
        $result = $this->client->query($query);
73
        $elapsed = intval(round((microtime(true) - $starttime) * 1000));
74
        if(method_exists($result, 'numRows')) {
75
            $numRows = $result->numRows();
0 ignored issues
show
Bug introduced by
The method numRows does only exist in EasyRdf\Sparql\Result, but not in EasyRdf\Graph and EasyRdf\Http\Response.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
76
            $logger->info("[qid $queryId] result: $numRows rows returned in $elapsed ms");
77
        } else { // graph result
78
            $numTriples = $result->countTriples();
0 ignored issues
show
Bug introduced by
The method countTriples does only exist in EasyRdf\Graph, but not in EasyRdf\Http\Response and EasyRdf\Sparql\Result.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
79
            $logger->info("[qid $queryId] result: $numTriples triples returned in $elapsed ms");
80
        }
81
        return $result;
82
    }
83
    
84
    
85
    /**
86
     * Generates FROM clauses for the queries 
87
     * @param Vocabulary[]|null $vocabs
88
     * @return string
89
     */
90
    protected function generateFromClause($vocabs=null) {
91
        $graphs = array();
92
        $clause = '';
93
        if (!$vocabs) {
94
            return $this->graph !== '?graph' && $this->graph !== NULL ? "FROM <$this->graph>" : '';
95
        }
96
        foreach($vocabs as $vocab) {
97
            $graph = $vocab->getGraph();
98
            if (!in_array($graph, $graphs)) {
99
                array_push($graphs, $graph);
100
            }
101
        }
102
        foreach ($graphs as $graph) {
103
            if($graph !== NULL)
104
                $clause .= "FROM NAMED <$graph> "; 
105
        }
106
        return $clause;
107
    }
108
109
    protected function initializeHttpClient() {
110
        // configure the HTTP client used by EasyRdf\Sparql\Client
111
        $httpclient = EasyRdf\Http::getDefaultHttpClient();
112
        $httpclient->setConfig(array('timeout' => $this->model->getConfig()->getSparqlTimeout()));
113
114
        // if special cache control (typically no-cache) was requested by the
115
        // client, set the same type of cache control headers also in subsequent
116
        // in the SPARQL requests (this is useful for performance testing)
117
        // @codeCoverageIgnoreStart
118
        $cacheControl = filter_input(INPUT_SERVER, 'HTTP_CACHE_CONTROL', FILTER_SANITIZE_STRING);
119
        $pragma = filter_input(INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_STRING);
120
        if ($cacheControl !== null || $pragma !== null) {
121
            $val = $pragma !== null ? $pragma : $cacheControl;
122
            $httpclient->setHeaders('Cache-Control', $val);
123
        }
124
        // @codeCoverageIgnoreEnd
125
126
        EasyRdf\Http::setDefaultHttpClient($httpclient); // actually redundant..
127
    }
128
129
    /**
130
     * Return true if this is the default SPARQL endpoint, used as the facade to query
131
     * all vocabularies.
132
     */
133
134
    protected function isDefaultEndpoint() {
135
        return $this->graph[0] == '?';
136
    }
137
138
    /**
139
     * Returns the graph instance
140
     * @return object EasyRDF graph instance.
141
     */
142
    public function getGraph() {
143
        return $this->graph;
144
    }
145
    
146
    /**
147
     * Shorten a URI
148
     * @param string $uri URI to shorten
149
     * @return string shortened URI, or original URI if it cannot be shortened
150
     */
151
    private function shortenUri($uri) {
152
        if (!array_key_exists($uri, $this->qnamecache)) {
153
            $res = new EasyRdf\Resource($uri);
154
            $qname = $res->shorten(); // returns null on failure
155
            $this->qnamecache[$uri] = ($qname !== null) ? $qname : $uri;
156
        }
157
        return $this->qnamecache[$uri];
158
    }
159
160
161
    /**
162
     * Generates the sparql query for retrieving concept and collection counts in a vocabulary.
163
     * @return string sparql query
164
     */
165
    private function generateCountConceptsQuery($array, $group) {
166
        $fcl = $this->generateFromClause();
167
        $optional = $array ? "UNION { ?type rdfs:subClassOf* <$array> }" : '';
168
        $optional .= $group ? "UNION { ?type rdfs:subClassOf* <$group> }" : '';
169
        $query = <<<EOQ
170
      SELECT (COUNT(?conc) as ?c) ?type ?typelabel $fcl WHERE {
171
        { ?conc a ?type .
172
        { ?type rdfs:subClassOf* skos:Concept . } UNION { ?type rdfs:subClassOf* skos:Collection . } $optional }
173
        OPTIONAL { ?type rdfs:label ?typelabel . }
174
      }
175
GROUP BY ?type ?typelabel
176
EOQ;
177
        return $query;
178
    }
179
180
    /**
181
     * Used for transforming the concept count query results.
182
     * @param EasyRdf\Sparql\Result $result query results to be transformed
183
     * @param string $lang language of labels
184
     * @return Array containing the label counts
185
     */
186
    private function transformCountConceptsResults($result, $lang) {
187
        $ret = array();
188
        foreach ($result as $row) {
189
            if (!isset($row->type)) {
190
                continue;
191
            }
192
            $ret[$row->type->getUri()]['type'] = $row->type->getUri();
193
            $ret[$row->type->getUri()]['count'] = $row->c->getValue();
194
            if (isset($row->typelabel) && $row->typelabel->getLang() === $lang) {
195
                $ret[$row->type->getUri()]['label'] = $row->typelabel->getValue();
196
            }
197
198
        }
199
        return $ret;
200
    }
201
202
    /**
203
     * Used for counting number of concepts and collections in a vocabulary.
204
     * @param string $lang language of labels
205
     * @return int number of concepts in this vocabulary
206
     */
207
    public function countConcepts($lang = null, $array = null, $group = null) {
208
        $query = $this->generateCountConceptsQuery($array, $group);
209
        $result = $this->query($query);
210
        return $this->transformCountConceptsResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 209 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformCountConceptsResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
211
    }
212
213
    /**
214
     * @param array $langs Languages to query for
215
     * @param string[] $props property names
216
     * @return string sparql query
217
     */
218
    private function generateCountLangConceptsQuery($langs, $classes, $props) {
219
        $gcl = $this->graphClause;
220
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
221
222
        $values = $this->formatValues('?type', $classes, 'uri');
223
        $valuesLang = $this->formatValues('?lang', $langs, 'literal');
224
        $valuesProp = $this->formatValues('?prop', $props, null);
225
226
        $query = <<<EOQ
227
SELECT ?lang ?prop
228
  (COUNT(?label) as ?count)
229
WHERE {
230
  $gcl {
231
    ?conc a ?type .
232
    ?conc ?prop ?label .
233
    FILTER (langMatches(lang(?label), ?lang))
234
    $valuesLang
235
    $valuesProp
236
  }
237
  $values
238
}
239
GROUP BY ?lang ?prop ?type
240
EOQ;
241
        return $query;
242
    }
243
244
    /**
245
     * Transforms the CountLangConcepts results into an array of label counts.
246
     * @param EasyRdf\Sparql\Result $result query results to be transformed
247
     * @param array $langs Languages to query for
248
     * @param string[] $props property names
249
     */
250
    private function transformCountLangConceptsResults($result, $langs, $props) {
251
        $ret = array();
252
        // set default count to zero; overridden below if query found labels
253
        foreach ($langs as $lang) {
254
            foreach ($props as $prop) {
255
                $ret[$lang][$prop] = 0;
256
            }
257
        }
258
        foreach ($result as $row) {
259
            if (isset($row->lang) && isset($row->prop) && isset($row->count)) {
260
                $ret[$row->lang->getValue()][$row->prop->shorten()] += 
261
                $row->count->getValue();
262
            }
263
264
        }
265
        ksort($ret);
266
        return $ret;
267
    }
268
269
    /**
270
     * Counts the number of concepts in a easyRDF graph with a specific language.
271
     * @param array $langs Languages to query for
272
     * @return Array containing count of concepts for each language and property.
273
     */
274
    public function countLangConcepts($langs, $classes = null) {
275
        $props = array('skos:prefLabel', 'skos:altLabel', 'skos:hiddenLabel');
276
        $query = $this->generateCountLangConceptsQuery($langs, $classes, $props);
277
        // Count the number of terms in each language
278
        $result = $this->query($query);
279
        return $this->transformCountLangConceptsResults($result, $langs, $props);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 278 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformCountLangConceptsResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
280
    }
281
282
    /**
283
     * Formats a VALUES clause (SPARQL 1.1) which states that the variable should be bound to one
284
     * of the constants given.
285
     * @param string $varname variable name, e.g. "?uri"
286
     * @param array $values the values
287
     * @param string $type type of values: "uri", "literal" or null (determines quoting style)
288
     */
289
    protected function formatValues($varname, $values, $type = null) {
290
        $constants = array();
291
        foreach ($values as $val) {
292
            if ($type == 'uri') {
293
                $val = "<$val>";
294
            }
295
296
            if ($type == 'literal') {
297
                $val = "'$val'";
298
            }
299
300
            $constants[] = "($val)";
301
        }
302
        $values = implode(" ", $constants);
303
304
        return "VALUES ($varname) { $values }";
305
    }
306
307
    /**
308
     * Filters multiple instances of the same vocabulary from the input array.
309
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
310
     * @return \Vocabulary[]
311
     */
312
    private function filterDuplicateVocabs($vocabs) {
313
        // filtering duplicates
314
        $uniqueVocabs = array();
315
        if ($vocabs !== null && sizeof($vocabs) > 0) {
316
            foreach ($vocabs as $voc) {
317
                $uniqueVocabs[$voc->getId()] = $voc;
318
            }
319
        }
320
321
        return $uniqueVocabs;
322
    }
323
324
    /**
325
     * Generates a sparql query for one or more concept URIs
326
     * @param mixed $uris concept URI (string) or array of URIs
327
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
328
     * @param \Vocabulary[]|null $vocabs array of Vocabulary objects
329
     * @return string sparql query
330
     */
331
    private function generateConceptInfoQuery($uris, $arrayClass, $vocabs) {
332
        $gcl = $this->graphClause;
333
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
334
        $values = $this->formatValues('?uri', $uris, 'uri');
335
        $uniqueVocabs = $this->filterDuplicateVocabs($vocabs);
336
        $valuesGraph = empty($vocabs) ? $this->formatValuesGraph($uniqueVocabs) : '';
337
338
        if ($arrayClass === null) {
339
            $construct = $optional = "";
340
        } else {
341
            // add information that can be used to format narrower concepts by
342
            // the array they belong to ("milk by source animal" use case)
343
            $construct = "\n ?x skos:member ?o . ?x skos:prefLabel ?xl . ?x a <$arrayClass> .";
344
            $optional = "\n OPTIONAL {
345
                      ?x skos:member ?o .
346
                      ?x a <$arrayClass> .
347
                      ?x skos:prefLabel ?xl .
348
                      FILTER NOT EXISTS {
349
                        ?x skos:member ?other .
350
                        FILTER NOT EXISTS { ?other skos:broader ?uri }
351
                      }
352
                    }";
353
        }
354
        $query = <<<EOQ
355
CONSTRUCT {
356
 ?s ?p ?uri .
357
 ?sp ?uri ?op .
358
 ?uri ?p ?o .
359
 ?p rdfs:label ?proplabel .
360
 ?p rdfs:subPropertyOf ?pp .
361
 ?pp rdfs:label ?plabel .
362
 ?o a ?ot .
363
 ?o skos:prefLabel ?opl .
364
 ?o rdfs:label ?ol .
365
 ?o rdf:value ?ov .
366
 ?o skos:notation ?on .
367
 ?o ?oprop ?oval .
368
 ?o ?xlprop ?xlval .
369
 ?directgroup skos:member ?uri .
370
 ?parent skos:member ?group .
371
 ?group skos:prefLabel ?grouplabel .
372
 ?b1 rdf:first ?item .
373
 ?b1 rdf:rest ?b2 .
374
 ?item a ?it .
375
 ?item skos:prefLabel ?il .
376
 ?group a ?grouptype . $construct
377
} $fcl WHERE {
378
 $gcl {
379
  {
380
    ?s ?p ?uri .
381
    FILTER(!isBlank(?s))
382
    FILTER(?p != skos:inScheme)
383
  }
384
  UNION
385
  { ?sp ?uri ?op . }
386
  UNION
387
  {
388
    ?directgroup skos:member ?uri .
389
    ?group skos:member+ ?uri .
390
    ?group skos:prefLabel ?grouplabel .
391
    ?group a ?grouptype .
392
    OPTIONAL { ?parent skos:member ?group }
393
  }
394
  UNION
395
  {
396
   ?uri ?p ?o .
397
   OPTIONAL {
398
     ?o rdf:rest* ?b1 .
399
     ?b1 rdf:first ?item .
400
     ?b1 rdf:rest ?b2 .
401
     OPTIONAL { ?item a ?it . }
402
     OPTIONAL { ?item skos:prefLabel ?il . }
403
   }
404
   OPTIONAL {
405
     { ?p rdfs:label ?proplabel . }
406
     UNION
407
     { ?p rdfs:subPropertyOf ?pp . }
408
   }
409
   OPTIONAL {
410
     { ?o a ?ot . }
411
     UNION
412
     { ?o skos:prefLabel ?opl . }
413
     UNION
414
     { ?o rdfs:label ?ol . }
415
     UNION
416
     { ?o rdf:value ?ov . 
417
       OPTIONAL { ?o ?oprop ?oval . }
418
     }
419
     UNION
420
     { ?o skos:notation ?on . }
421
     UNION
422
     { ?o a skosxl:Label .
423
       ?o ?xlprop ?xlval }
424
   } $optional
425
  }
426
 }
427
 $values
428
}
429
$valuesGraph
430
EOQ;
431
        return $query;
432
    }
433
434
    /**
435
     * Transforms ConceptInfo query results into an array of Concept objects
436
     * @param EasyRdf\Graph $result query results to be transformed
437
     * @param array $uris concept URIs
438
     * @param \Vocabulary[] $vocabs array of Vocabulary object
439
     * @param string|null $clang content language
440
     * @return mixed query result graph (EasyRdf\Graph), or array of Concept objects
441
     */
442
    private function transformConceptInfoResults($result, $uris, $vocabs, $clang) {
443
        $conceptArray = array();
444
        foreach ($uris as $index => $uri) {
445
            $conc = $result->resource($uri);
446
            $vocab = sizeof($vocabs) == 1 ? $vocabs[0] : $vocabs[$index];
447
            $conceptArray[] = new Concept($this->model, $vocab, $conc, $result, $clang);
448
        }
449
        return $conceptArray;
450
    }
451
452
    /**
453
     * Returns information (as a graph) for one or more concept URIs
454
     * @param mixed $uris concept URI (string) or array of URIs
455
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
456
     * @param \Vocabulary[]|null $vocabs vocabularies to target
457
     * @return \Concept[]
458
     */
459
    public function queryConceptInfoGraph($uris, $arrayClass = null, $vocabs = array()) {
460
        // if just a single URI is given, put it in an array regardless
461
        if (!is_array($uris)) {
462
            $uris = array($uris);
463
        }
464
465
        $query = $this->generateConceptInfoQuery($uris, $arrayClass, $vocabs);
466
        $result = $this->query($query);
467
        return $result;
468
    }
469
470
    /**
471
     * Returns information (as an array of Concept objects) for one or more concept URIs
472
     * @param mixed $uris concept URI (string) or array of URIs
473
     * @param string|null $arrayClass the URI for thesaurus array class, or null if not used
474
     * @param \Vocabulary[] $vocabs vocabularies to target
475
     * @param string|null $clang content language
476
     * @return EasyRdf\Graph
477
     */
478
    public function queryConceptInfo($uris, $arrayClass = null, $vocabs = array(), $clang = null) {
479
        // if just a single URI is given, put it in an array regardless
480
        if (!is_array($uris)) {
481
            $uris = array($uris);
482
        }
483
        $result = $this->queryConceptInfoGraph($uris, $arrayClass, $vocabs);
484
        if ($result->isEmpty()) {
0 ignored issues
show
Bug introduced by
The method isEmpty does only exist in EasyRdf\Graph, but not in EasyRdf\Http\Response and EasyRdf\Sparql\Result.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
485
            return;
486
        }
487
        return $this->transformConceptInfoResults($result, $uris, $vocabs, $clang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->queryConceptInfoG..., $arrayClass, $vocabs) on line 483 can also be of type object<EasyRdf\Http\Response> or object<EasyRdf\Sparql\Result>; however, GenericSparql::transformConceptInfoResults() does only seem to accept object<EasyRdf\Graph>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
488
    }
489
490
    /**
491
     * Generates the sparql query for queryTypes
492
     * @param string $lang
493
     * @return string sparql query
494
     */
495
    private function generateQueryTypesQuery($lang) {
496
        $fcl = $this->generateFromClause();
497
        $query = <<<EOQ
498
SELECT DISTINCT ?type ?label ?superclass $fcl
499
WHERE {
500
  {
501
    { BIND( skos:Concept as ?type ) }
502
    UNION
503
    { BIND( skos:Collection as ?type ) }
504
    UNION
505
    { BIND( isothes:ConceptGroup as ?type ) }
506
    UNION
507
    { BIND( isothes:ThesaurusArray as ?type ) }
508
    UNION
509
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Concept . }
510
    UNION
511
    { ?type rdfs:subClassOf/rdfs:subClassOf* skos:Collection . }
512
  }
513
  OPTIONAL {
514
    ?type rdfs:label ?label .
515
    FILTER(langMatches(lang(?label), '$lang'))
516
  }
517
  OPTIONAL {
518
    ?type rdfs:subClassOf ?superclass .
519
  }
520
  FILTER EXISTS {
521
    ?s a ?type .
522
    ?s skos:prefLabel ?prefLabel .
523
  }
524
}
525
EOQ;
526
        return $query;
527
    }
528
529
    /**
530
     * Transforms the results into an array format.
531
     * @param EasyRdf\Sparql\Result $result
532
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
533
     */
534 View Code Duplication
    private function transformQueryTypesResults($result) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
535
        $ret = array();
536
        foreach ($result as $row) {
537
            $type = array();
538
            if (isset($row->label)) {
539
                $type['label'] = $row->label->getValue();
540
            }
541
542
            if (isset($row->superclass)) {
543
                $type['superclass'] = $row->superclass->getUri();
544
            }
545
546
            $ret[$row->type->getURI()] = $type;
547
        }
548
        return $ret;
549
    }
550
551
    /**
552
     * Retrieve information about types from the endpoint
553
     * @param string $lang
554
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
555
     */
556
    public function queryTypes($lang) {
557
        $query = $this->generateQueryTypesQuery($lang);
558
        $result = $this->query($query);
559
        return $this->transformQueryTypesResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 558 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformQueryTypesResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
560
    }
561
562
    /**
563
     * Generates the concept scheme query.
564
     * @param string $conceptscheme concept scheme URI
565
     * @return string sparql query
566
     */
567
    private function generateQueryConceptSchemeQuery($conceptscheme) {
568
        $fcl = $this->generateFromClause();
569
        $query = <<<EOQ
570
CONSTRUCT {
571
  <$conceptscheme> ?property ?value .
572
} $fcl WHERE {
573
  <$conceptscheme> ?property ?value .
574
  FILTER (?property != skos:hasTopConcept)
575
}
576
EOQ;
577
        return $query;
578
    }
579
580
    /**
581
     * Retrieves conceptScheme information from the endpoint.
582
     * @param string $conceptscheme concept scheme URI
583
     * @return EasyRDF_Graph query result graph
584
     */
585
    public function queryConceptScheme($conceptscheme) {
586
        $query = $this->generateQueryConceptSchemeQuery($conceptscheme);
587
        return $this->query($query);
588
    }
589
590
    /**
591
     * Generates the queryConceptSchemes sparql query.
592
     * @param string $lang language of labels
593
     * @return string sparql query
594
     */
595
    private function generateQueryConceptSchemesQuery($lang) {
596
        $fcl = $this->generateFromClause();
597
        $query = <<<EOQ
598
SELECT ?cs ?label ?preflabel ?title ?domain ?domainLabel $fcl
599
WHERE {
600
 ?cs a skos:ConceptScheme .
601
 OPTIONAL{
602
    ?cs dcterms:subject ?domain.
603
    ?domain skos:prefLabel ?domainLabel.
604
    FILTER(langMatches(lang(?domainLabel), '$lang'))
605
}
606
 OPTIONAL {
607
   ?cs rdfs:label ?label .
608
   FILTER(langMatches(lang(?label), '$lang'))
609
 }
610
 OPTIONAL {
611
   ?cs skos:prefLabel ?preflabel .
612
   FILTER(langMatches(lang(?preflabel), '$lang'))
613
 }
614
 OPTIONAL {
615
   { ?cs dc11:title ?title }
616
   UNION
617
   { ?cs dc:title ?title }
618
   FILTER(langMatches(lang(?title), '$lang'))
619
 }
620
} 
621
ORDER BY ?cs
622
EOQ;
623
        return $query;
624
    }
625
626
    /**
627
     * Transforms the queryConceptScheme results into an array format.
628
     * @param EasyRdf\Sparql\Result $result
629
     * @return array
630
     */
631
    private function transformQueryConceptSchemesResults($result) {
632
        $ret = array();
633
        foreach ($result as $row) {
634
            $conceptscheme = array();
635
            if (isset($row->label)) {
636
                $conceptscheme['label'] = $row->label->getValue();
637
            }
638
639
            if (isset($row->preflabel)) {
640
                $conceptscheme['prefLabel'] = $row->preflabel->getValue();
641
            }
642
643
            if (isset($row->title)) {
644
                $conceptscheme['title'] = $row->title->getValue();
645
            }
646
            // add dct:subject and their labels in the result
647
            if(isset($row->domain) && isset($row->domainLabel)){
648
                $conceptscheme['subject']['uri']=$row->domain->getURI();
649
                $conceptscheme['subject']['prefLabel']=$row->domainLabel->getValue();
650
            }
651
652
            $ret[$row->cs->getURI()] = $conceptscheme;
653
        }
654
        return $ret;
655
    }
656
657
    /**
658
     * return a list of skos:ConceptScheme instances in the given graph
659
     * @param string $lang language of labels
660
     * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
661
     */
662
    public function queryConceptSchemes($lang) {
663
        $query = $this->generateQueryConceptSchemesQuery($lang);
664
        $result = $this->query($query);
665
        return $this->transformQueryConceptSchemesResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 664 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transform...ConceptSchemesResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
666
    }
667
668
    /**
669
     * Generate a VALUES clause for limiting the targeted graphs.
670
     * @param Vocabulary[]|null $vocabs the vocabularies to target 
671
     * @return string[] array of graph URIs
672
     */
673
    protected function getVocabGraphs($vocabs) {
674
        if ($vocabs === null || sizeof($vocabs) == 0) {
675
            // searching from all vocabularies - limit to known graphs
676
            $vocabs = $this->model->getVocabularies();
677
        }
678
        $graphs = array();
679
        foreach ($vocabs as $voc) {
680
            $graphs[] = $voc->getGraph();
681
        }
682
        return $graphs;
683
    }
684
685
    /**
686
     * Generate a VALUES clause for limiting the targeted graphs.
687
     * @param Vocabulary[]|null $vocabs array of Vocabulary objects to target
688
     * @return string VALUES clause, or "" if not necessary to limit
689
     */
690
    protected function formatValuesGraph($vocabs) {
691
        if (!$this->isDefaultEndpoint()) {
692
            return "";
693
        }
694
        $graphs = $this->getVocabGraphs($vocabs);
695
        return $this->formatValues('?graph', $graphs, 'uri');
696
    }
697
698
    /**
699
     * Generate a FILTER clause for limiting the targeted graphs.
700
     * @param array $vocabs array of Vocabulary objects to target
701
     * @return string FILTER clause, or "" if not necessary to limit
702
     */
703
    protected function formatFilterGraph($vocabs) {
704
        if (!$this->isDefaultEndpoint()) {
705
            return "";
706
        }
707
        $graphs = $this->getVocabGraphs($vocabs);
708
        $values = array();
709
        foreach ($graphs as $graph) {
710
          $values[] = "<$graph>";
711
        }
712
        return "FILTER (?graph IN (" . implode(',', $values) . "))";
713
    }
714
715
    /**
716
     * Formats combined limit and offset clauses for the sparql query
717
     * @param int $limit maximum number of hits to retrieve; 0 for unlimited
718
     * @param int $offset offset of results to retrieve; 0 for beginning of list
719
     * @return string sparql query clauses
720
     */
721
    protected function formatLimitAndOffset($limit, $offset) {
722
        $limit = ($limit) ? 'LIMIT ' . $limit : '';
723
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
724
        // eliminating whitespace and line changes when the conditions aren't needed.
725
        $limitandoffset = '';
726
        if ($limit && $offset) {
727
            $limitandoffset = "\n" . $limit . "\n" . $offset;
728
        } elseif ($limit) {
729
            $limitandoffset = "\n" . $limit;
730
        } elseif ($offset) {
731
            $limitandoffset = "\n" . $offset;
732
        }
733
734
        return $limitandoffset;
735
    }
736
737
    /**
738
     * Formats a sparql query clause for limiting the search to specific concept types.
739
     * @param array $types limit search to concepts of the given type(s)
740
     * @return string sparql query clause
741
     */
742
    protected function formatTypes($types) {
743
        $typePatterns = array();
744
        if (!empty($types)) {
745
            foreach ($types as $type) {
746
                $unprefixed = EasyRdf\RdfNamespace::expand($type);
747
                $typePatterns[] = "{ ?s a <$unprefixed> }";
748
            }
749
        }
750
751
        return implode(' UNION ', $typePatterns);;
752
    }
753
754
    /**
755
     * @param string $prop property to include in the result eg. 'broader' or 'narrower'
756
     * @return string sparql query clause
757
     */
758
    private function formatPropertyCsvClause($prop) {
759
        # This expression creates a CSV row containing pairs of (uri,prefLabel) values.
760
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
761
        $clause = <<<EOV
762
(GROUP_CONCAT(DISTINCT CONCAT(
763
 '"', IF(isIRI(?$prop),STR(?$prop),''), '"', ',',
764
 '"', REPLACE(IF(BOUND(?{$prop}lab),?{$prop}lab,''), '"', '""'), '"', ',',
765
 '"', REPLACE(IF(isLiteral(?{$prop}),?{$prop},''), '"', '""'), '"'
766
); separator='\\n') as ?{$prop}s)
767
EOV;
768
        return $clause;
769
    }
770
    
771
    /**
772
     * @return string sparql query clause
773
     */
774
    private function formatPrefLabelCsvClause() {
775
        # This expression creates a CSV row containing pairs of (prefLabel, lang) values.
776
        # The REPLACE is performed for quotes (" -> "") so they don't break the CSV format.
777
        $clause = <<<EOV
778
(GROUP_CONCAT(DISTINCT CONCAT(
779
 '"', STR(?pref), '"', ',', '"', lang(?pref), '"'
780
); separator='\\n') as ?preflabels)
781
EOV;
782
        return $clause;
783
    }
784
785
    /**
786
     * @param string $lang language code of the returned labels
787
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
788
     * @return string sparql query clause
789
     */
790
    protected function formatExtraFields($lang, $fields) {
791
        // extra variable expressions to request and extra fields to query for
792
        $ret = array('extravars' => '', 'extrafields' => '');
793
794
        if ($fields === null) {
795
            return $ret; 
796
        }
797
798
        if (in_array('prefLabel', $fields)) {
799
            $ret['extravars'] .= $this->formatPreflabelCsvClause();
800
            $ret['extrafields'] .= <<<EOF
801
OPTIONAL {
802
  ?s skos:prefLabel ?pref .
803
}
804
EOF;
805
            // removing the prefLabel from the fields since it has been handled separately
806
            $fields = array_diff($fields, array('prefLabel'));
807
        }
808
809
        foreach ($fields as $field) {
810
            $ret['extravars'] .= $this->formatPropertyCsvClause($field);
811
            $ret['extrafields'] .= <<<EOF
812
OPTIONAL {
813
  ?s skos:$field ?$field .
814
  FILTER(!isLiteral(?$field)||langMatches(lang(?{$field}), '$lang'))
815
  OPTIONAL { ?$field skos:prefLabel ?{$field}lab . FILTER(langMatches(lang(?{$field}lab), '$lang')) }
816
}
817
EOF;
818
        }
819
820
        return $ret;
821
    }
822
823
    /**
824
     * Generate condition for matching labels in SPARQL
825
     * @param string $term search term
826
     * @param string $searchLang language code used for matching labels (null means any language)
827
     * @return string sparql query snippet
828
     */
829
    protected function generateConceptSearchQueryCondition($term, $searchLang)
830
    {
831
        # use appropriate matching function depending on query type: =, strstarts, strends or full regex
832
        if (preg_match('/^[^\*]+$/', $term)) { // exact query
833
            $term = str_replace('\\', '\\\\', $term); // quote slashes
834
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
835
            $filtercond = "LCASE(STR(?match)) = '$term'";
836
        } elseif (preg_match('/^[^\*]+\*$/', $term)) { // prefix query
837
            $term = substr($term, 0, -1); // remove the final asterisk
838
            $term = str_replace('\\', '\\\\', $term); // quote slashes
839
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
840
            $filtercond = "STRSTARTS(LCASE(STR(?match)), '$term')";
841
        } elseif (preg_match('/^\*[^\*]+$/', $term)) { // suffix query
842
            $term = substr($term, 1); // remove the preceding asterisk
843
            $term = str_replace('\\', '\\\\', $term); // quote slashes
844
            $term = str_replace('\'', '\\\'', mb_strtolower($term, 'UTF-8')); // make lowercase and escape single quotes
845
            $filtercond = "STRENDS(LCASE(STR(?match)), '$term')";
846
        } else { // too complicated - have to use a regex
847
            # make sure regex metacharacters are not passed through
848
            $term = str_replace('\\', '\\\\', preg_quote($term));
849
            $term = str_replace('\\\\*', '.*', $term); // convert asterisk to regex syntax
850
            $term = str_replace('\'', '\\\'', $term); // ensure single quotes are quoted
851
            $filtercond = "REGEX(STR(?match), '^$term$', 'i')";
852
        }
853
854
        $labelcondMatch = ($searchLang) ? "&& LANGMATCHES(lang(?match), '$searchLang')" : "";
855
        
856
        return "?s ?prop ?match . FILTER ($filtercond $labelcondMatch)";
857
    }
858
859
860
    /**
861
     * Inner query for concepts using a search term.
862
     * @param string $term search term
863
     * @param string $lang language code of the returned labels
864
     * @param string $searchLang language code used for matching labels (null means any language)
865
     * @param string[] $props properties to target e.g. array('skos:prefLabel','skos:altLabel')
866
     * @param boolean $unique restrict results to unique concepts (default: false)
867
     * @return string sparql query
868
     */
869
    protected function generateConceptSearchQueryInner($term, $lang, $searchLang, $props, $unique, $filterGraph)
870
    {
871
        $valuesProp = $this->formatValues('?prop', $props);
872
        $textcond = $this->generateConceptSearchQueryCondition($term, $searchLang);
873
        $rawterm = str_replace('\\', '\\\\', str_replace('*', '', $term));
874
        
875
        // graph clause, if necessary
876
        $graphClause = $filterGraph != '' ? 'GRAPH ?graph' : '';
877
878
        // extra conditions for label language, if specified
879
        $labelcondLabel = ($lang) ? "LANGMATCHES(lang(?label), '$lang')" : "lang(?match) = '' || LANGMATCHES(lang(?label), lang(?match))";
880
        // if search language and UI/display language differ, must also consider case where there is no prefLabel in
881
        // the display language; in that case, should use the label with the same language as the matched label
882
        $labelcondFallback = ($searchLang != $lang) ?
883
          "OPTIONAL { # in case previous OPTIONAL block gives no labels\n" .
884
          "?s skos:prefLabel ?label . FILTER (LANGMATCHES(LANG(?label), LANG(?match))) }" : "";
885
          
886
        //  Including the labels if there is no query term given.
887
        if ($rawterm === '') {
888
          $labelClause = "?s skos:prefLabel ?label .";
889
          $labelClause = ($lang) ? $labelClause . " FILTER (LANGMATCHES(LANG(?label), '$lang'))" : $labelClause . "";
890
          return $labelClause . " BIND(?label AS ?match)";
891
        }
892
893
        /*
894
         * This query does some tricks to obtain a list of unique concepts.
895
         * From each match generated by the text index, a string such as
896
         * "1en@example" is generated, where the first character is a number
897
         * encoding the property and priority, then comes the language tag and
898
         * finally the original literal after an @ sign. Of these, the MIN
899
         * function is used to pick the best match for each concept. Finally,
900
         * the structure is unpacked to get back the original string. Phew!
901
         */
902
        $hitvar = $unique ? '(MIN(?matchstr) AS ?hit)' : '(?matchstr AS ?hit)';
903
        $hitgroup = $unique ? 'GROUP BY ?s ?label ?notation' : '';
904
         
905
        $query = <<<EOQ
906
   SELECT DISTINCT ?s ?label ?notation $hitvar
907
   WHERE {
908
    $graphClause {
909
     { 
910
     $valuesProp
911
     VALUES (?prop ?pri) { (skos:prefLabel 1) (skos:altLabel 3) (skos:hiddenLabel 5)}
912
     $textcond
913
     ?s ?prop ?match }
914
     UNION
915
     { ?s skos:notation "$rawterm" }
916
     OPTIONAL {
917
      ?s skos:prefLabel ?label .
918
      FILTER ($labelcondLabel)
919
     } $labelcondFallback
920
     BIND(IF(langMatches(LANG(?match),'$lang'), ?pri, ?pri+1) AS ?npri)
921
     BIND(CONCAT(STR(?npri), LANG(?match), '@', STR(?match)) AS ?matchstr)
922
     OPTIONAL { ?s skos:notation ?notation }
923
    }
924
    $filterGraph
925
   }
926
   $hitgroup
927
EOQ;
928
929
        return $query;
930
    }
931
932
    /**
933
     * Query for concepts using a search term.
934
     * @param array|null $fields extra fields to include in the result (array of strings). (default: null = none)
935
     * @param boolean $unique restrict results to unique concepts (default: false)
936
     * @param boolean $showDeprecated whether to include deprecated concepts in search results (default: false)
937
     * @param ConceptSearchParameters $params 
938
     * @return string sparql query
939
     */
940
    protected function generateConceptSearchQuery($fields, $unique, $params, $showDeprecated = false) {
941
        $vocabs = $params->getVocabs();
942
        $gcl = $this->graphClause;
943
        $fcl = empty($vocabs) ? '' : $this->generateFromClause($vocabs);
944
        $formattedtype = $this->formatTypes($params->getTypeLimit());
945
        $formattedfields = $this->formatExtraFields($params->getLang(), $fields);
946
        $extravars = $formattedfields['extravars'];
947
        $extrafields = $formattedfields['extrafields'];
948
        $schemes = $params->getSchemeLimit();
949
950
        // limit the search to only requested concept schemes
951
        $schemecond = '';
952
        if (!empty($schemes)) {
953
            $conditions = array();
954
            foreach($schemes as $scheme) {
955
                $conditions[] = "{?s skos:inScheme <$scheme>}";
956
            }
957
            $schemecond = '{'.implode(" UNION ",$conditions).'}';
958
        }
959
        $filterDeprecated="";
960
        //show or hide deprecated concepts
961
        if(!$showDeprecated){
962
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
963
        }
964
        // extra conditions for parent and group, if specified
965
        $parentcond = ($params->getParentLimit()) ? "?s skos:broader+ <" . $params->getParentLimit() . "> ." : "";
966
        $groupcond = ($params->getGroupLimit()) ? "<" . $params->getGroupLimit() . "> skos:member ?s ." : "";
967
        $pgcond = $parentcond . $groupcond;
968
969
        $orderextra = $this->isDefaultEndpoint() ? $this->graph : '';
970
971
        # make VALUES clauses
972
        $props = array('skos:prefLabel', 'skos:altLabel');
973
        if ($params->getHidden()) {
974
            $props[] = 'skos:hiddenLabel';
975
        }
976
977
        $filterGraph = empty($vocabs) ? $this->formatFilterGraph($vocabs) : '';
978
979
        // remove futile asterisks from the search term
980
        $term = $params->getSearchTerm();
981
        while (strpos($term, '**') !== false) {
982
            $term = str_replace('**', '*', $term);
983
        }
984
985
        $labelpriority = <<<EOQ
986
  FILTER(BOUND(?s))
987
  BIND(STR(SUBSTR(?hit,1,1)) AS ?pri)
988
  BIND(IF((SUBSTR(STRBEFORE(?hit, '@'),1) != ?pri), STRLANG(STRAFTER(?hit, '@'), SUBSTR(STRBEFORE(?hit, '@'),2)), STRAFTER(?hit, '@')) AS ?match)
989
  BIND(IF((?pri = "1" || ?pri = "2") && ?match != ?label, ?match, ?unbound) as ?plabel)
990
  BIND(IF((?pri = "3" || ?pri = "4"), ?match, ?unbound) as ?alabel)
991
  BIND(IF((?pri = "5" || ?pri = "6"), ?match, ?unbound) as ?hlabel)
992
EOQ;
993
        $innerquery = $this->generateConceptSearchQueryInner($params->getSearchTerm(), $params->getLang(), $params->getSearchLang(), $props, $unique, $filterGraph);
994
        if ($params->getSearchTerm() === '*' || $params->getSearchTerm() === '') { 
995
          $labelpriority = ''; 
996
        }
997
        $query = <<<EOQ
998
SELECT DISTINCT ?s ?label ?plabel ?alabel ?hlabel ?graph ?notation (GROUP_CONCAT(DISTINCT STR(?type);separator=' ') as ?types) $extravars 
999
$fcl
1000
WHERE {
1001
 $gcl {
1002
  {
1003
  $innerquery
1004
  }
1005
  $labelpriority
1006
  $formattedtype
1007
  { $pgcond 
1008
   ?s a ?type .
1009
   $extrafields $schemecond
1010
  }
1011
  $filterDeprecated
1012
 }
1013
 $filterGraph
1014
}
1015
GROUP BY ?s ?match ?label ?plabel ?alabel ?hlabel ?notation ?graph
1016
ORDER BY LCASE(STR(?match)) LANG(?match) $orderextra
1017
EOQ;
1018
        return $query;
1019
    }
1020
1021
    /**
1022
     * Transform a single concept search query results into the skosmos desired return format.
1023
     * @param $row SPARQL query result row
1024
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1025
     * @return array query result object
1026
     */
1027
    private function transformConceptSearchResult($row, $vocabs, $fields)
1028
    {
1029
        $hit = array();
1030
        $hit['uri'] = $row->s->getUri();
1031
1032
        if (isset($row->graph)) {
1033
            $hit['graph'] = $row->graph->getUri();
1034
        }
1035
1036
        foreach (explode(" ", $row->types->getValue()) as $typeuri) {
1037
            $hit['type'][] = $this->shortenUri($typeuri);
1038
        }
1039
1040
        if(!empty($fields)) {
1041
            foreach ($fields as $prop) {
1042
                $propname = $prop . 's';
1043
                if (isset($row->$propname)) {
1044
                    foreach (explode("\n", $row->$propname->getValue()) as $line) {
1045
                        $rdata = str_getcsv($line, ',', '"', '"');
1046
                        $propvals = array();
1047
                        if ($rdata[0] != '') {
1048
                            $propvals['uri'] = $rdata[0];
1049
                        }
1050
                        if ($rdata[1] != '') {
1051
                            $propvals['prefLabel'] = $rdata[1];
1052
                        }
1053
                        if ($rdata[2] != '') {
1054
                            $propvals = $rdata[2];
1055
                        }
1056
1057
                        $hit['skos:' . $prop][] = $propvals;
1058
                    }
1059
                }
1060
            }
1061
        }
1062
1063
        
1064
        if (isset($row->preflabels)) {
1065
            foreach (explode("\n", $row->preflabels->getValue()) as $line) {
1066
                $pref = str_getcsv($line, ',', '"', '"');
1067
                $hit['prefLabels'][$pref[1]] = $pref[0];
1068
            }
1069
        }
1070
1071
        foreach ($vocabs as $vocab) { // looping the vocabulary objects and asking these for a localname for the concept.
1072
            $localname = $vocab->getLocalName($hit['uri']);
1073
            if ($localname !== $hit['uri']) { // only passing the result forward if the uri didn't boomerang right back.
1074
                $hit['localname'] = $localname;
1075
                break; // stopping the search when we find one that returns something valid.
1076
            }
1077
        }
1078
1079
        if (isset($row->label)) {
1080
            $hit['prefLabel'] = $row->label->getValue();
1081
        }
1082
1083
        if (isset($row->label)) {
1084
            $hit['lang'] = $row->label->getLang();
1085
        }
1086
1087
        if (isset($row->notation)) {
1088
            $hit['notation'] = $row->notation->getValue();
1089
        }
1090
1091
        if (isset($row->plabel)) {
1092
            $hit['matchedPrefLabel'] = $row->plabel->getValue();
1093
            $hit['lang'] = $row->plabel->getLang();
1094 View Code Duplication
        } elseif (isset($row->alabel)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1095
            $hit['altLabel'] = $row->alabel->getValue();
1096
            $hit['lang'] = $row->alabel->getLang();
1097
        } elseif (isset($row->hlabel)) {
1098
            $hit['hiddenLabel'] = $row->hlabel->getValue();
1099
            $hit['lang'] = $row->hlabel->getLang();
1100
        }
1101
        return $hit;
1102
    }
1103
1104
    /**
1105
     * Transform the concept search query results into the skosmos desired return format.
1106
     * @param EasyRdf\Sparql\Result $results
1107
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1108
     * @return array query result object
1109
     */
1110
    private function transformConceptSearchResults($results, $vocabs, $fields) {
1111
        $ret = array();
1112
1113
        foreach ($results as $row) {
1114
            if (!isset($row->s)) {
1115
                // don't break if query returns a single dummy result
1116
                continue;
1117
            }
1118
            $ret[] = $this->transformConceptSearchResult($row, $vocabs, $fields);
1119
        }
1120
        return $ret;
1121
    }
1122
1123
    /**
1124
     * Query for concepts using a search term.
1125
     * @param array $vocabs array of Vocabulary objects to search; empty for global search
1126
     * @param array $fields extra fields to include in the result (array of strings). (default: null = none)
1127
     * @param boolean $unique restrict results to unique concepts (default: false)
1128
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1129
     * @param ConceptSearchParameters $params 
1130
     * @return array query result object
1131
     */
1132
    public function queryConcepts($vocabs, $fields = null, $unique = false, $params, $showDeprecated = false) {
1133
        $query = $this->generateConceptSearchQuery($fields, $unique, $params,$showDeprecated);
1134
        $results = $this->query($query);
1135
        return $this->transformConceptSearchResults($results, $vocabs, $fields);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $this->query($query) on line 1134 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformConceptSearchResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1136
    }
1137
1138
    /**
1139
     * Generates sparql query clauses used for creating the alphabetical index.
1140
     * @param string $letter the letter (or special class) to search for
1141
     * @return array of sparql query clause strings
1142
     */
1143
    private function formatFilterConditions($letter, $lang) {
1144
        $useRegex = false;
1145
1146
        if ($letter == '*') {
1147
            $letter = '.*';
1148
            $useRegex = true;
1149
        } elseif ($letter == '0-9') {
1150
            $letter = '[0-9].*';
1151
            $useRegex = true;
1152
        } elseif ($letter == '!*') {
1153
            $letter = '[^\\\\p{L}\\\\p{N}].*';
1154
            $useRegex = true;
1155
        }
1156
1157
        # make text query clause
1158
        $lcletter = mb_strtolower($letter, 'UTF-8'); // convert to lower case, UTF-8 safe
1159
        if ($useRegex) {
1160
            $filtercondLabel = $lang ? "regex(str(?label), '^$letter$', 'i') && langMatches(lang(?label), '$lang')" : "regex(str(?label), '^$letter$', 'i')";
1161
            $filtercondALabel = $lang ? "regex(str(?alabel), '^$letter$', 'i') && langMatches(lang(?alabel), '$lang')" : "regex(str(?alabel), '^$letter$', 'i')";
1162
        } else {
1163
            $filtercondLabel = $lang ? "strstarts(lcase(str(?label)), '$lcletter') && langMatches(lang(?label), '$lang')" : "strstarts(lcase(str(?label)), '$lcletter')";
1164
            $filtercondALabel = $lang ? "strstarts(lcase(str(?alabel)), '$lcletter') && langMatches(lang(?alabel), '$lang')" : "strstarts(lcase(str(?alabel)), '$lcletter')";
1165
        }
1166
        return array('filterpref' => $filtercondLabel, 'filteralt' => $filtercondALabel);
1167
    }
1168
1169
    /**
1170
     * Generates the sparql query used for rendering the alphabetical index.
1171
     * @param string $letter the letter (or special class) to search for
1172
     * @param string $lang language of labels
1173
     * @param integer $limit limits the amount of results
1174
     * @param integer $offset offsets the result set
1175
     * @param array|null $classes
1176
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1177
     * @return string sparql query
1178
     */
1179
    protected function generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes, $showDeprecated = false) {
1180
        $fcl = $this->generateFromClause();
1181
        $classes = ($classes) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1182
        $values = $this->formatValues('?type', $classes, 'uri');
1183
        $limitandoffset = $this->formatLimitAndOffset($limit, $offset);
1184
        $conditions = $this->formatFilterConditions($letter, $lang);
1185
        $filtercondLabel = $conditions['filterpref'];
1186
        $filtercondALabel = $conditions['filteralt'];
1187
        $filterDeprecated="";
1188
        if(!$showDeprecated){
1189
            $filterDeprecated="FILTER NOT EXISTS { ?s owl:deprecated true }";
1190
        }
1191
        $query = <<<EOQ
1192
SELECT DISTINCT ?s ?label ?alabel $fcl
1193
WHERE {
1194
  {
1195
    ?s skos:prefLabel ?label .
1196
    FILTER (
1197
      $filtercondLabel
1198
    )
1199
  }
1200
  UNION
1201
  {
1202
    {
1203
      ?s skos:altLabel ?alabel .
1204
      FILTER (
1205
        $filtercondALabel
1206
      )
1207
    }
1208
    {
1209
      ?s skos:prefLabel ?label .
1210
      FILTER (langMatches(lang(?label), '$lang'))
1211
    }
1212
  }
1213
  ?s a ?type .
1214
  $filterDeprecated
1215
  $values
1216
}
1217
ORDER BY STR(LCASE(COALESCE(?alabel, ?label))) $limitandoffset
1218
EOQ;
1219
        return $query;
1220
    }
1221
1222
    /**
1223
     * Transforms the alphabetical list query results into an array format.
1224
     * @param EasyRdf\Sparql\Result $results
1225
     * @return array
1226
     */
1227
    private function transformAlphabeticalListResults($results) {
1228
        $ret = array();
1229
1230
        foreach ($results as $row) {
1231
            if (!isset($row->s)) {
1232
                continue;
1233
            }
1234
            // don't break if query returns a single dummy result
1235
1236
            $hit = array();
1237
            $hit['uri'] = $row->s->getUri();
1238
1239
            $hit['localname'] = $row->s->localName();
1240
1241
            $hit['prefLabel'] = $row->label->getValue();
1242
            $hit['lang'] = $row->label->getLang();
1243
1244 View Code Duplication
            if (isset($row->alabel)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1245
                $hit['altLabel'] = $row->alabel->getValue();
1246
                $hit['lang'] = $row->alabel->getLang();
1247
            }
1248
1249
            $ret[] = $hit;
1250
        }
1251
1252
        return $ret;
1253
    }
1254
1255
    /**
1256
     * Query for concepts with a term starting with the given letter. Also special classes '0-9' (digits),
1257
     * '*!' (special characters) and '*' (everything) are accepted.
1258
     * @param string $letter the letter (or special class) to search for
1259
     * @param string $lang language of labels
1260
     * @param integer $limit limits the amount of results
1261
     * @param integer $offset offsets the result set
1262
     * @param array $classes
1263
     * @param boolean $showDeprecated whether to include deprecated concepts in the result (default: false)
1264
     */
1265
    public function queryConceptsAlphabetical($letter, $lang, $limit = null, $offset = null, $classes = null,$showDeprecated = false) {
1266
        $query = $this->generateAlphabeticalListQuery($letter, $lang, $limit, $offset, $classes,$showDeprecated);
1267
        $results = $this->query($query);
1268
        return $this->transformAlphabeticalListResults($results);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $this->query($query) on line 1267 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformAlphabeticalListResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1269
    }
1270
1271
    /**
1272
     * Creates the query used for finding out which letters should be displayed in the alphabetical index.
1273
     * Note that we force the datatype of the result variable otherwise Virtuoso does not properly interpret the DISTINCT and we have duplicated results
1274
     * @param string $lang language
1275
     * @return string sparql query
1276
     */
1277
    private function generateFirstCharactersQuery($lang, $classes) {
1278
        $fcl = $this->generateFromClause();
1279
        $classes = (sizeof($classes) > 0) ? $classes : array('http://www.w3.org/2004/02/skos/core#Concept');
1280
        $values = $this->formatValues('?type', $classes, 'uri');
1281
        $query = <<<EOQ
1282
SELECT DISTINCT (ucase(str(substr(?label, 1, 1))) as ?l) $fcl WHERE {
1283
  ?c skos:prefLabel ?label .
1284
  ?c a ?type
1285
  FILTER(langMatches(lang(?label), '$lang'))
1286
  $values
1287
}
1288
EOQ;
1289
        return $query;
1290
    }
1291
1292
    /**
1293
     * Transforms the first characters query results into an array format.
1294
     * @param EasyRdf\Sparql\Result $result
1295
     * @return array
1296
     */
1297
    private function transformFirstCharactersResults($result) {
1298
        $ret = array();
1299
        foreach ($result as $row) {
1300
            $ret[] = $row->l->getValue();
1301
        }
1302
        return $ret;
1303
    }
1304
1305
    /**
1306
     * Query for the first characters (letter or otherwise) of the labels in the particular language.
1307
     * @param string $lang language
1308
     * @return array array of characters
1309
     */
1310
    public function queryFirstCharacters($lang, $classes = null) {
1311
        $query = $this->generateFirstCharactersQuery($lang, $classes);
1312
        $result = $this->query($query);
1313
        return $this->transformFirstCharactersResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1312 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformFirstCharactersResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1314
    }
1315
1316
    /**
1317
     * @param string $uri
1318
     * @param string $lang
1319
     * @return string sparql query string
1320
     */
1321
    private function generateLabelQuery($uri, $lang) {
1322
        $fcl = $this->generateFromClause();
1323
        $labelcondLabel = ($lang) ? "FILTER( langMatches(lang(?label), '$lang') )" : "";
1324
        $query = <<<EOQ
1325
SELECT ?label $fcl
1326
WHERE {
1327
  <$uri> a ?type .
1328
  OPTIONAL {
1329
    <$uri> skos:prefLabel ?label .
1330
    $labelcondLabel
1331
  }
1332
  OPTIONAL {
1333
    <$uri> rdfs:label ?label .
1334
    $labelcondLabel
1335
  }
1336
  OPTIONAL {
1337
    <$uri> dc:title ?label .
1338
    $labelcondLabel
1339
  }
1340
  OPTIONAL {
1341
    <$uri> dc11:title ?label .
1342
    $labelcondLabel
1343
  }
1344
}
1345
EOQ;
1346
        return $query;
1347
    }
1348
1349
    /**
1350
     * Query for a label (skos:prefLabel, rdfs:label, dc:title, dc11:title) of a resource.
1351
     * @param string $uri
1352
     * @param string $lang
1353
     * @return array array of labels (key: lang, val: label), or null if resource doesn't exist
1354
     */
1355 View Code Duplication
    public function queryLabel($uri, $lang) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1356
        $query = $this->generateLabelQuery($uri, $lang);
1357
        $result = $this->query($query);
1358
        $ret = array();
1359
        foreach ($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type object<EasyRdf\Http\Resp...>|object<EasyRdf\Graph> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1360
            if (!isset($row->label)) {
1361
                // existing concept but no labels
1362
                return array();
1363
            }
1364
            $ret[$row->label->getLang()] = $row->label;
1365
        }
1366
1367
        if (sizeof($ret) > 0) {
1368
            // existing concept, with label(s)
1369
            return $ret;
1370
        } else {
1371
            // nonexistent concept
1372
            return null;
1373
        }
1374
    }
1375
    
1376
    /**
1377
     * Generates a SPARQL query to retrieve the super properties of a given property URI.
1378
     * Note this must be executed in the graph where this information is available.
1379
     * @param string $uri
1380
     * @return string sparql query string
1381
     */
1382
    private function generateSubPropertyOfQuery($uri) {
1383
        $fcl = $this->generateFromClause();
1384
        $query = <<<EOQ
1385
SELECT ?superProperty $fcl
1386
WHERE {
1387
  <$uri> rdfs:subPropertyOf ?superProperty
1388
}
1389
EOQ;
1390
        return $query;
1391
    }
1392
    
1393
    /**
1394
     * Query the super properties of a provided property URI.
1395
     * @param string $uri URI of a propertyes
1396
     * @return array array super properties, or null if none exist
1397
     */
1398 View Code Duplication
    public function querySuperProperties($uri) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1399
        $query = $this->generateSubPropertyOfQuery($uri);
1400
        $result = $this->query($query);
1401
        $ret = array();
1402
        foreach ($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type object<EasyRdf\Http\Resp...>|object<EasyRdf\Graph> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1403
            if (isset($row->superProperty)) {
1404
                $ret[] = $row->superProperty->getUri();
1405
            }
1406
            
1407
        }
1408
        
1409
        if (sizeof($ret) > 0) {
1410
            // return result
1411
            return $ret;
1412
        } else {
1413
            // no result, return null
1414
            return null;
1415
        }
1416
    }
1417
1418
1419
    /**
1420
     * Generates a sparql query for queryNotation.
1421
     * @param string $uri
1422
     * @return string sparql query
1423
     */
1424
    private function generateNotationQuery($uri) {
1425
        $fcl = $this->generateFromClause();
1426
1427
        $query = <<<EOQ
1428
SELECT * $fcl
1429
WHERE {
1430
  <$uri> skos:notation ?notation .
1431
}
1432
EOQ;
1433
        return $query;
1434
    }
1435
1436
    /**
1437
     * Query for the notation of the concept (skos:notation) of a resource.
1438
     * @param string $uri
1439
     * @return string notation or null if it doesn't exist
1440
     */
1441
    public function queryNotation($uri) {
1442
        $query = $this->generateNotationQuery($uri);
1443
        $result = $this->query($query);
1444
        foreach ($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type object<EasyRdf\Http\Resp...>|object<EasyRdf\Graph> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1445
            if (isset($row->notation)) {
1446
                return $row->notation->getValue();
1447
            }
1448
        }
1449
        return null;
1450
    }
1451
1452
    /**
1453
     * Generates a sparql query for queryProperty.
1454
     * @param string $uri
1455
     * @param string $prop the name of the property eg. 'skos:broader'.
1456
     * @param string $lang
1457
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1458
     * @return string sparql query
1459
     */
1460 View Code Duplication
    private function generatePropertyQuery($uri, $prop, $lang, $anylang) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1461
        $fcl = $this->generateFromClause();
1462
        $anylang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1463
1464
        $query = <<<EOQ
1465
SELECT * $fcl
1466
WHERE {
1467
  <$uri> a skos:Concept .
1468
  OPTIONAL {
1469
    <$uri> $prop ?object .
1470
    OPTIONAL {
1471
      ?object skos:prefLabel ?label .
1472
      FILTER (langMatches(lang(?label), "$lang"))
1473
    }
1474
    OPTIONAL {
1475
      ?object skos:prefLabel ?label .
1476
      FILTER (lang(?label) = "")
1477
    }
1478
    $anylang
1479
  }
1480
}
1481
EOQ;
1482
        return $query;
1483
    }
1484
1485
    /**
1486
     * Transforms the sparql query result into an array or null if the concept doesn't exist.
1487
     * @param EasyRdf\Sparql\Result $result
1488
     * @param string $lang
1489
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1490
     */
1491
    private function transformPropertyQueryResults($result, $lang) {
1492
        $ret = array();
1493
        foreach ($result as $row) {
1494
            if (!isset($row->object)) {
1495
                return array();
1496
            }
1497
            // existing concept but no properties
1498
            if (isset($row->label)) {
1499
                if ($row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1500
                    $ret[$row->object->getUri()]['label'] = $row->label->getValue();
1501
                }
1502
1503
            } else {
1504
                $ret[$row->object->getUri()]['label'] = null;
1505
            }
1506
        }
1507
        if (sizeof($ret) > 0) {
1508
            return $ret;
1509
        }
1510
        // existing concept, with properties
1511
        else {
1512
            return null;
1513
        }
1514
        // nonexistent concept
1515
    }
1516
1517
    /**
1518
     * Query a single property of a concept.
1519
     * @param string $uri
1520
     * @param string $prop the name of the property eg. 'skos:broader'.
1521
     * @param string $lang
1522
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1523
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1524
     */
1525
    public function queryProperty($uri, $prop, $lang, $anylang = false) {
1526
        $uri = is_array($uri) ? $uri[0] : $uri;
1527
        $query = $this->generatePropertyQuery($uri, $prop, $lang, $anylang);
1528
        $result = $this->query($query);
1529
        return $this->transformPropertyQueryResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1528 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformPropertyQueryResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1530
    }
1531
1532
    /**
1533
     * Query a single transitive property of a concept.
1534
     * @param string $uri
1535
     * @param array $props the name of the property eg. 'skos:broader'.
1536
     * @param string $lang
1537
     * @param integer $limit
1538
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1539
     * @return string sparql query
1540
     */
1541
    private function generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang) {
1542
        $uri = is_array($uri) ? $uri[0] : $uri;
1543
        $fcl = $this->generateFromClause();
1544
        $propertyClause = implode('|', $props);
1545
        $otherlang = $anylang ? "OPTIONAL { ?object skos:prefLabel ?label }" : "";
1546
        // need to do a SPARQL subquery because LIMIT needs to be applied /after/
1547
        // the direct relationships have been collapsed into one string
1548
        $query = <<<EOQ
1549
SELECT * $fcl
1550
WHERE {
1551
  SELECT ?object ?label (GROUP_CONCAT(STR(?dir);separator=' ') as ?direct)
1552
  WHERE {
1553
    <$uri> a skos:Concept .
1554
    OPTIONAL {
1555
      <$uri> $propertyClause* ?object .
1556
      OPTIONAL {
1557
        ?object $propertyClause ?dir .
1558
      }
1559
    }
1560
    OPTIONAL {
1561
      ?object skos:prefLabel ?label .
1562
      FILTER (langMatches(lang(?label), "$lang"))
1563
    }
1564
    $otherlang
1565
  }
1566
  GROUP BY ?object ?label
1567
}
1568
LIMIT $limit
1569
EOQ;
1570
        return $query;
1571
    }
1572
1573
    /**
1574
     * Transforms the sparql query result object into an array.
1575
     * @param EasyRdf\Sparql\Result $result
1576
     * @param string $lang
1577
     * @param string $fallbacklang language to use if label is not available in the preferred language
1578
     * @return array of property values (key: URI, val: label), or null if concept doesn't exist
1579
     */
1580
    private function transformTransitivePropertyResults($result, $lang, $fallbacklang) {
1581
        $ret = array();
1582
        foreach ($result as $row) {
1583
            if (!isset($row->object)) {
1584
                return array();
1585
            }
1586
            // existing concept but no properties
1587
            if (isset($row->label)) {
1588
                $val = array('label' => $row->label->getValue());
1589
            } else {
1590
                $val = array('label' => null);
1591
            }
1592 View Code Duplication
            if (isset($row->direct) && $row->direct->getValue() != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1593
                $val['direct'] = explode(' ', $row->direct->getValue());
1594
            }
1595
            // Preventing labels in a non preferred language overriding the preferred language.
1596
            if (isset($row->label) && $row->label->getLang() === $lang || array_key_exists($row->object->getUri(), $ret) === false) {
1597
                if (!isset($row->label) || $row->label->getLang() === $lang) {
1598
                    $ret[$row->object->getUri()] = $val;
1599
                } elseif ($row->label->getLang() === $fallbacklang) {
1600
                    $val['label'] .= ' (' . $row->label->getLang() . ')';
1601
                    $ret[$row->object->getUri()] = $val;
1602
                }
1603
            }
1604
        }
1605
1606
        // second iteration of results to find labels for the ones that didn't have one in the preferred languages
1607
        foreach ($result as $row) {
1608
            if (isset($row->object) && array_key_exists($row->object->getUri(), $ret) === false) {
1609
                $val = array('label' => $row->label->getValue());
1610 View Code Duplication
                if (isset($row->direct) && $row->direct->getValue() != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1611
                    $val['direct'] = explode(' ', $row->direct->getValue());
1612
                }
1613
                $ret[$row->object->getUri()] = $val;
1614
            }
1615
        }
1616
1617
        if (sizeof($ret) > 0) {
1618
            return $ret;
1619
        }
1620
        // existing concept, with properties
1621
        else {
1622
            return null;
1623
        }
1624
        // nonexistent concept
1625
    }
1626
1627
    /**
1628
     * Query a single transitive property of a concept.
1629
     * @param string $uri
1630
     * @param array $props the property/properties.
1631
     * @param string $lang
1632
     * @param string $fallbacklang language to use if label is not available in the preferred language
1633
     * @param integer $limit
1634
     * @param boolean $anylang if you want a label even when it isn't available in the language you requested.
1635
     * @return array array of property values (key: URI, val: label), or null if concept doesn't exist
1636
     */
1637
    public function queryTransitiveProperty($uri, $props, $lang, $limit, $anylang = false, $fallbacklang = '') {
1638
        $query = $this->generateTransitivePropertyQuery($uri, $props, $lang, $limit, $anylang);
1639
        $result = $this->query($query);
1640
        return $this->transformTransitivePropertyResults($result, $lang, $fallbacklang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1639 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transform...sitivePropertyResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1641
    }
1642
1643
    /**
1644
     * Generates the query for a concepts skos:narrowers.
1645
     * @param string $uri
1646
     * @param string $lang
1647
     * @param string $fallback
1648
     * @return string sparql query
1649
     */
1650
    private function generateChildQuery($uri, $lang, $fallback, $props) {
1651
        $uri = is_array($uri) ? $uri[0] : $uri;
1652
        $fcl = $this->generateFromClause();
1653
        $propertyClause = implode('|', $props);
1654
        $query = <<<EOQ
1655
SELECT ?child ?label ?child ?grandchildren ?notation $fcl WHERE {
1656
  <$uri> a skos:Concept .
1657
  OPTIONAL {
1658
    ?child $propertyClause <$uri> .
1659
    OPTIONAL {
1660
      ?child skos:prefLabel ?label .
1661
      FILTER (langMatches(lang(?label), "$lang"))
1662
    }
1663
    OPTIONAL {
1664
      ?child skos:prefLabel ?label .
1665
      FILTER (langMatches(lang(?label), "$fallback"))
1666
    }
1667
    OPTIONAL { # other language case
1668
      ?child skos:prefLabel ?label .
1669
    }
1670
    OPTIONAL {
1671
      ?child skos:notation ?notation .
1672
    }
1673
    BIND ( EXISTS { ?a $propertyClause ?child . } AS ?grandchildren )
1674
  }
1675
}
1676
EOQ;
1677
        return $query;
1678
    }
1679
1680
    /**
1681
     * Transforms the sparql result object into an array.
1682
     * @param EasyRdf\Sparql\Result $result
1683
     * @param string $lang
1684
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1685
     */
1686
    private function transformNarrowerResults($result, $lang) {
1687
        $ret = array();
1688
        foreach ($result as $row) {
1689
            if (!isset($row->child)) {
1690
                return array();
1691
            }
1692
            // existing concept but no children
1693
1694
            $label = null;
1695
            if (isset($row->label)) {
1696
                if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
1697
                    $label = $row->label->getValue();
1698
                } else {
1699
                    $label = $row->label->getValue() . " (" . $row->label->getLang() . ")";
1700
                }
1701
1702
            }
1703
            $childArray = array(
1704
                'uri' => $row->child->getUri(),
1705
                'prefLabel' => $label,
1706
                'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1707
            );
1708
            if (isset($row->notation)) {
1709
                $childArray['notation'] = $row->notation->getValue();
1710
            }
1711
1712
            $ret[] = $childArray;
1713
        }
1714
        if (sizeof($ret) > 0) {
1715
            return $ret;
1716
        }
1717
        // existing concept, with children
1718
        else {
1719
            return null;
1720
        }
1721
        // nonexistent concept
1722
    }
1723
1724
    /**
1725
     * Query the narrower concepts of a concept.
1726
     * @param string $uri
1727
     * @param string $lang
1728
     * @param string $fallback
1729
     * @return array array of arrays describing each child concept, or null if concept doesn't exist
1730
     */
1731
    public function queryChildren($uri, $lang, $fallback, $props) {
1732
        $query = $this->generateChildQuery($uri, $lang, $fallback, $props);
1733
        $result = $this->query($query);
1734
        return $this->transformNarrowerResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 1733 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformNarrowerResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1735
    }
1736
1737
    /**
1738
     * Query the top concepts of a vocabulary.
1739
     * @param string $conceptSchemes concept schemes whose top concepts to query for
1740
     * @param string $lang language of labels
1741
     * @param string $fallback language to use if label is not available in the preferred language
1742
     */
1743
    public function queryTopConcepts($conceptSchemes, $lang, $fallback) {
1744
        if (!is_array($conceptSchemes)) {
1745
            $conceptSchemes = array($conceptSchemes);
1746
        }
1747
1748
        $values = $this->formatValues('?topuri', $conceptSchemes, 'uri');
1749
1750
        $fcl = $this->generateFromClause();
1751
        $query = <<<EOQ
1752
SELECT DISTINCT ?top ?topuri ?label ?notation ?children $fcl WHERE {
1753
  ?top skos:topConceptOf ?topuri .
1754
  OPTIONAL {
1755
    ?top skos:prefLabel ?label .
1756
    FILTER (langMatches(lang(?label), "$lang"))
1757
  }
1758
  OPTIONAL {
1759
    ?top skos:prefLabel ?label .
1760
    FILTER (langMatches(lang(?label), "$fallback"))
1761
  }
1762
  OPTIONAL { # fallback - other language case
1763
    ?top skos:prefLabel ?label .
1764
  }
1765
  OPTIONAL { ?top skos:notation ?notation . }
1766
  BIND ( EXISTS { ?top skos:narrower ?a . } AS ?children )
1767
  $values
1768
}
1769
EOQ;
1770
        $result = $this->query($query);
1771
        $ret = array();
1772
        foreach ($result as $row) {
0 ignored issues
show
Bug introduced by
The expression $result of type object<EasyRdf\Http\Resp...>|object<EasyRdf\Graph> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1773
            if (isset($row->top) && isset($row->label)) {
1774
                $label = $row->label->getValue();
1775 View Code Duplication
                if ($row->label->getLang() && $row->label->getLang() !== $lang && strpos($row->label->getLang(), $lang . "-") !== 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1776
                    $label .= ' (' . $row->label->getLang() . ')';
1777
                }
1778
                $top = array('uri' => $row->top->getUri(), 'topConceptOf' => $row->topuri->getUri(), 'label' => $label, 'hasChildren' => filter_var($row->children->getValue(), FILTER_VALIDATE_BOOLEAN));
1779
                if (isset($row->notation)) {
1780
                    $top['notation'] = $row->notation->getValue();
1781
                }
1782
1783
                $ret[] = $top;
1784
            }
1785
        }
1786
1787
        return $ret;
1788
    }
1789
1790
    /**
1791
     * Generates a sparql query for finding the hierarchy for a concept.
1792
	 * A concept may be a top concept in multiple schemes, returned as a single whitespace-separated literal.
1793
     * @param string $uri concept uri.
1794
     * @param string $lang
1795
     * @param string $fallback language to use if label is not available in the preferred language
1796
     * @return string sparql query
1797
     */
1798
    private function generateParentListQuery($uri, $lang, $fallback, $props) {
1799
        $fcl = $this->generateFromClause();
1800
        $propertyClause = implode('|', $props);
1801
        $query = <<<EOQ
1802
SELECT ?broad ?parent ?member ?children ?grandchildren
1803
(SAMPLE(?lab) as ?label) (SAMPLE(?childlab) as ?childlabel) (GROUP_CONCAT(?topcs; separator=" ") as ?tops) 
1804
(SAMPLE(?nota) as ?notation) (SAMPLE(?childnota) as ?childnotation) $fcl
1805
WHERE {
1806
  <$uri> a skos:Concept .
1807
  OPTIONAL {
1808
    <$uri> $propertyClause* ?broad .
1809
    OPTIONAL {
1810
      ?broad skos:prefLabel ?lab .
1811
      FILTER (langMatches(lang(?lab), "$lang"))
1812
    }
1813
    OPTIONAL {
1814
      ?broad skos:prefLabel ?lab .
1815
      FILTER (langMatches(lang(?lab), "$fallback"))
1816
    }
1817
    OPTIONAL { # fallback - other language case
1818
      ?broad skos:prefLabel ?lab .
1819
    }
1820
    OPTIONAL { ?broad skos:notation ?nota . }
1821
    OPTIONAL { ?broad $propertyClause ?parent . }
1822
    OPTIONAL { ?broad skos:narrower ?children .
1823
      OPTIONAL {
1824
        ?children skos:prefLabel ?childlab .
1825
        FILTER (langMatches(lang(?childlab), "$lang"))
1826
      }
1827
      OPTIONAL {
1828
        ?children skos:prefLabel ?childlab .
1829
        FILTER (langMatches(lang(?childlab), "$fallback"))
1830
      }
1831
      OPTIONAL { # fallback - other language case
1832
        ?children skos:prefLabel ?childlab .
1833
      }
1834
      OPTIONAL {
1835
        ?children skos:notation ?childnota .
1836
      }
1837
    }
1838
    BIND ( EXISTS { ?children skos:narrower ?a . } AS ?grandchildren )
1839
    OPTIONAL { ?broad skos:topConceptOf ?topcs . }
1840
  }
1841
}
1842
GROUP BY ?broad ?parent ?member ?children ?grandchildren
1843
EOQ;
1844
        return $query;
1845
    }
1846
1847
    /**
1848
     * Transforms the result into an array.
1849
     * @param EasyRdf\Sparql\Result
1850
     * @param string $lang
1851
     * @return an array for the REST controller to encode.
1852
     */
1853
    private function transformParentListResults($result, $lang)
1854
    {
1855
        $ret = array();
1856
        foreach ($result as $row) {
1857
            if (!isset($row->broad)) {
1858
                // existing concept but no broaders
1859
                return array();
1860
            }
1861
            $uri = $row->broad->getUri();
1862
            if (!isset($ret[$uri])) {
1863
                $ret[$uri] = array('uri' => $uri);
1864
            }
1865
            if (isset($row->exact)) {
1866
                $ret[$uri]['exact'] = $row->exact->getUri();
1867
            }
1868
            if (isset($row->tops)) {
1869
               $topConceptsList=array();
0 ignored issues
show
Unused Code introduced by
$topConceptsList is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1870
               $topConceptsList=explode(" ", $row->tops->getValue());
1871
               // sort to garantee an alphabetical ordering of the URI
1872
               sort($topConceptsList);
1873
               $ret[$uri]['tops'] = $topConceptsList;
1874
            }
1875
            if (isset($row->children)) {
1876
                if (!isset($ret[$uri]['narrower'])) {
1877
                    $ret[$uri]['narrower'] = array();
1878
                }
1879
1880
                $label = null;
1881
                if (isset($row->childlabel)) {
1882
                    $label = $row->childlabel->getValue();
1883
                    if ($row->childlabel->getLang() !== $lang && strpos($row->childlabel->getLang(), $lang . "-") !== 0) {
1884
                        $label .= " (" . $row->childlabel->getLang() . ")";
1885
                    }
1886
1887
                }
1888
1889
                $childArr = array(
1890
                    'uri' => $row->children->getUri(),
1891
                    'label' => $label,
1892
                    'hasChildren' => filter_var($row->grandchildren->getValue(), FILTER_VALIDATE_BOOLEAN),
1893
                );
1894
                if (isset($row->childnotation)) {
1895
                    $childArr['notation'] = $row->childnotation->getValue();
1896
                }
1897
1898
                if (!in_array($childArr, $ret[$uri]['narrower'])) {
1899
                    $ret[$uri]['narrower'][] = $childArr;
1900
                }
1901
1902
            }
1903
            if (isset($row->label)) {
1904
                $preflabel = $row->label->getValue();
1905 View Code Duplication
                if ($row->label->getLang() && $row->label->getLang() !== $lang && strpos($row->label->getLang(), $lang . "-") !== 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1906
                    $preflabel .= ' (' . $row->label->getLang() . ')';
1907
                }
1908
1909
                $ret[$uri]['prefLabel'] = $preflabel;
1910
            }
1911
            if (isset($row->notation)) {
1912
                $ret[$uri]['notation'] = $row->notation->getValue();
1913
            }
1914
1915
            if (isset($row->parent) && (isset($ret[$uri]['broader']) && !in_array($row->parent->getUri(), $ret[$uri]['broader']))) {
1916
                $ret[$uri]['broader'][] = $row->parent->getUri();
1917
            } elseif (isset($row->parent) && !isset($ret[$uri]['broader'])) {
1918
                $ret[$uri]['broader'][] = $row->parent->getUri();
1919
            }
1920
        }
1921
        if (sizeof($ret) > 0) {
1922
            // existing concept, with children
1923
            return $ret;
1924
        }
1925
        else {
1926
            // nonexistent concept
1927
            return null;
1928
        }
1929
    }
1930
1931
    /**
1932
     * Query for finding the hierarchy for a concept.
1933
     * @param string $uri concept uri.
1934
     * @param string $lang
1935
     * @param string $fallback language to use if label is not available in the preferred language
1936
     * @param array $props the hierarchy property/properties to use
1937
     * @return an array for the REST controller to encode.
1938
     */
1939
    public function queryParentList($uri, $lang, $fallback, $props) {
1940
        $query = $this->generateParentListQuery($uri, $lang, $fallback, $props);
1941
        $result = $this->query($query);
1942
        return $this->transformParentListResults($result, $lang);
1943
    }
1944
1945
    /**
1946
     * return a list of concept group instances, sorted by label
1947
     * @param string $groupClass URI of concept group class
1948
     * @param string $lang language of labels to return
1949
     * @return string sparql query
1950
     */
1951
    private function generateConceptGroupsQuery($groupClass, $lang) {
1952
        $fcl = $this->generateFromClause();
1953
        $query = <<<EOQ
1954
SELECT ?group (GROUP_CONCAT(DISTINCT STR(?child);separator=' ') as ?children) ?label ?members ?notation $fcl
1955
WHERE {
1956
  ?group a <$groupClass> .
1957
  OPTIONAL { ?group skos:member|isothes:subGroup ?child .
1958
             ?child a <$groupClass> }
1959
  BIND(EXISTS{?group skos:member ?submembers} as ?members)
1960
  OPTIONAL { ?group skos:prefLabel ?label }
1961
  OPTIONAL { ?group rdfs:label ?label }
1962
  FILTER (langMatches(lang(?label), '$lang'))
1963
  OPTIONAL { ?group skos:notation ?notation }
1964
}
1965
GROUP BY ?group ?label ?members ?notation
1966
ORDER BY lcase(?label)
1967
EOQ;
1968
        return $query;
1969
    }
1970
1971
    /**
1972
     * Transforms the sparql query result into an array.
1973
     * @param EasyRdf\Sparql\Result $result
1974
     * @return array
1975
     */
1976
    private function transformConceptGroupsResults($result) {
1977
        $ret = array();
1978
        foreach ($result as $row) {
1979
            if (!isset($row->group)) {
1980
                # no groups found, see issue #357
1981
                continue;
1982
            }
1983
            $group = array('uri' => $row->group->getURI());
1984
            if (isset($row->label)) {
1985
                $group['prefLabel'] = $row->label->getValue();
1986
            }
1987
1988
            if (isset($row->children)) {
1989
                $group['childGroups'] = explode(' ', $row->children->getValue());
1990
            }
1991
1992
            if (isset($row->members)) {
1993
                $group['hasMembers'] = $row->members->getValue();
1994
            }
1995
1996
            if (isset($row->notation)) {
1997
                $group['notation'] = $row->notation->getValue();
1998
            }
1999
2000
            $ret[] = $group;
2001
        }
2002
        return $ret;
2003
    }
2004
2005
    /**
2006
     * return a list of concept group instances, sorted by label
2007
     * @param string $groupClass URI of concept group class
2008
     * @param string $lang language of labels to return
2009
     * @return array Result array with group URI as key and group label as value
2010
     */
2011
    public function listConceptGroups($groupClass, $lang) {
2012
        $query = $this->generateConceptGroupsQuery($groupClass, $lang);
2013
        $result = $this->query($query);
2014
        return $this->transformConceptGroupsResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2013 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformConceptGroupsResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2015
    }
2016
2017
    /**
2018
     * Generates the sparql query for listConceptGroupContents
2019
     * @param string $groupClass URI of concept group class
2020
     * @param string $group URI of the concept group instance
2021
     * @param string $lang language of labels to return
2022
     * @param boolean $showDeprecated whether to include deprecated in the result
2023
     * @return string sparql query
2024
     */
2025
    private function generateConceptGroupContentsQuery($groupClass, $group, $lang, $showDeprecated = false) {
2026
        $fcl = $this->generateFromClause();
2027
        $filterDeprecated="";
2028
        if(!$showDeprecated){
2029
            $filterDeprecated="  FILTER NOT EXISTS { ?conc owl:deprecated true }";
2030
        }
2031
        $query = <<<EOQ
2032
SELECT ?conc ?super ?label ?members ?type ?notation $fcl
2033
WHERE {
2034
 <$group> a <$groupClass> .
2035
 { <$group> skos:member ?conc . } UNION { ?conc isothes:superGroup <$group> }
2036
$filterDeprecated
2037
 ?conc a ?type .
2038
 OPTIONAL { ?conc skos:prefLabel ?label .
2039
  FILTER (langMatches(lang(?label), '$lang'))
2040
 }
2041
 OPTIONAL { ?conc skos:prefLabel ?label . }
2042
 OPTIONAL { ?conc skos:notation ?notation }
2043
 BIND(EXISTS{?submembers isothes:superGroup ?conc} as ?super)
2044
 BIND(EXISTS{?conc skos:member ?submembers} as ?members)
2045
} ORDER BY lcase(?label)
2046
EOQ;
2047
        return $query;
2048
    }
2049
2050
    /**
2051
     * Transforms the sparql query result into an array.
2052
     * @param EasyRdf\Sparql\Result $result
2053
     * @param string $lang language of labels to return
2054
     * @return array
2055
     */
2056
    private function transformConceptGroupContentsResults($result, $lang) {
2057
        $ret = array();
2058
        $values = array();
2059
        foreach ($result as $row) {
2060
            if (!array_key_exists($row->conc->getURI(), $values)) {
2061
                $values[$row->conc->getURI()] = array(
2062
                    'uri' => $row->conc->getURI(),
2063
                    'isSuper' => $row->super->getValue(),
2064
                    'hasMembers' => $row->members->getValue(),
2065
                    'type' => array($row->type->shorten()),
2066
                );
2067
                if (isset($row->label)) {
2068
                    if ($row->label->getLang() == $lang || strpos($row->label->getLang(), $lang . "-") == 0) {
2069
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue();
2070
                    } else {
2071
                        $values[$row->conc->getURI()]['prefLabel'] = $row->label->getValue() . " (" . $row->label->getLang() . ")";
2072
                    }
2073
2074
                }
2075
                if (isset($row->notation)) {
2076
                    $values[$row->conc->getURI()]['notation'] = $row->notation->getValue();
2077
                }
2078
2079
            } else {
2080
                $values[$row->conc->getURI()]['type'][] = $row->type->shorten();
2081
            }
2082
        }
2083
2084
        foreach ($values as $val) {
2085
            $ret[] = $val;
2086
        }
2087
2088
        return $ret;
2089
    }
2090
2091
    /**
2092
     * return a list of concepts in a concept group
2093
     * @param string $groupClass URI of concept group class
2094
     * @param string $group URI of the concept group instance
2095
     * @param string $lang language of labels to return
2096
     * @param boolean $showDeprecated whether to include deprecated concepts in search results
2097
     * @return array Result array with concept URI as key and concept label as value
2098
     */
2099
    public function listConceptGroupContents($groupClass, $group, $lang,$showDeprecated = false) {
2100
        $query = $this->generateConceptGroupContentsQuery($groupClass, $group, $lang,$showDeprecated);
2101
        $result = $this->query($query);
2102
        return $this->transformConceptGroupContentsResults($result, $lang);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2101 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transform...tGroupContentsResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2103
    }
2104
2105
    /**
2106
     * Generates the sparql query for queryChangeList.
2107
     * @param string $lang language of labels to return.
2108
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2109
     * @return string sparql query
2110
     */
2111 View Code Duplication
    private function generateChangeListQuery($lang, $offset, $prop) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2112
        $fcl = $this->generateFromClause();
2113
        $offset = ($offset) ? 'OFFSET ' . $offset : '';
2114
2115
        $query = <<<EOQ
2116
SELECT DISTINCT ?concept ?date ?label $fcl
2117
WHERE {
2118
  ?concept a skos:Concept .
2119
  ?concept $prop ?date .
2120
  ?concept skos:prefLabel ?label .
2121
  FILTER (langMatches(lang(?label), '$lang'))
2122
}
2123
ORDER BY DESC(YEAR(?date)) DESC(MONTH(?date)) LCASE(?label)
2124
LIMIT 200 $offset
2125
EOQ;
2126
        return $query;
2127
    }
2128
2129
    /**
2130
     * Transforms the sparql query result into an array.
2131
     * @param EasyRdf\Sparql\Result $result
2132
     * @return array
2133
     */
2134 View Code Duplication
    private function transformChangeListResults($result) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2135
        $ret = array();
2136
        foreach ($result as $row) {
2137
            $concept = array('uri' => $row->concept->getURI());
2138
            if (isset($row->label)) {
2139
                $concept['prefLabel'] = $row->label->getValue();
2140
            }
2141
2142
            if (isset($row->date)) {
2143
                $concept['date'] = $row->date->getValue();
2144
            }
2145
2146
            $ret[] = $concept;
2147
        }
2148
        return $ret;
2149
    }
2150
2151
    /**
2152
     * return a list of recently changed or entirely new concepts
2153
     * @param string $lang language of labels to return
2154
     * @param int $offset offset of results to retrieve; 0 for beginning of list
2155
     * @return array Result array
2156
     */
2157
    public function queryChangeList($lang, $offset, $prop) {
2158
        $query = $this->generateChangeListQuery($lang, $offset, $prop);
2159
        $result = $this->query($query);
2160
        return $this->transformChangeListResults($result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->query($query) on line 2159 can also be of type object<EasyRdf\Graph> or object<EasyRdf\Http\Response>; however, GenericSparql::transformChangeListResults() does only seem to accept object<EasyRdf\Sparql\Result>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2161
    }
2162
}
2163