Model   F
last analyzed

Complexity

Total Complexity 118

Size/Duplication

Total Lines 643
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 118
eloc 261
dl 0
loc 643
rs 2
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A getVocabularyByGraph() 0 18 5
A getDefaultSparql() 0 3 1
A getConfig() 0 3 1
C disambiguateVocabulary() 0 45 12
A getVersion() 0 8 2
A initializeLogging() 0 19 4
A getLocale() 0 3 1
A setLocale() 0 3 1
A getTranslator() 0 3 1
B getVocabularyList() 0 27 8
A getLanguages() 0 12 3
A __construct() 0 14 3
A getLogger() 0 3 1
C searchConceptsAndInfo() 0 49 12
A getVocabularyCategories() 0 8 2
A getClassificationLabel() 0 4 2
A getVocabularies() 0 20 6
A createDataObjects() 0 8 2
C searchConcepts() 0 56 17
A getResourceFromUri() 0 16 4
A getVocabulariesInCategory() 0 4 1
A getText() 0 3 1
A getVocabulary() 0 9 3
A getSparqlImplementation() 0 5 1
A getResourceLabel() 0 11 3
B getRDF() 0 30 6
B getTypes() 0 20 7
B guessVocabularyFromURI() 0 29 8

How to fix   Complexity   

Complex Class

Complex classes like Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

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

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

1
<?php
2
3
/**
4
 * Importing the dependencies.
5
 */
6
use Symfony\Component\Translation\Translator;
7
use Symfony\Component\Translation\Loader\JsonFileLoader;
8
9
/**
10
 * Model provides access to the data.
11
 * @property EasyRdf\Graph $graph
12
 * @property GlobalConfig $globalConfig
13
 */
14
class Model
15
{
16
    /** cache for Vocabulary objects */
17
    private $allVocabularies = null;
18
    /** cache for Vocabulary objects */
19
    private $vocabsByGraph = null;
20
    /** cache for Vocabulary objects */
21
    private $vocabsByUriSpace = null;
22
    /** how long to store retrieved URI information in APC cache */
23
    public const URI_FETCH_TTL = 86400; // 1 day
24
    private $globalConfig;
25
    private $logger;
26
    private $resolver;
27
    private $translator;
28
29
    /**
30
     * Initializes the Model object
31
     */
32
    public function __construct(string $config_filename = "../../config.ttl")
33
    {
34
        $this->resolver = new Resolver($this);
35
        $this->globalConfig = new GlobalConfig($this, $config_filename);
36
        $this->translator = null;
37
        foreach ($this->globalConfig->getLanguages() as $langcode => $locale) {
38
            if (is_null($this->translator)) {
39
                // use the first configured language as default language
40
                $this->translator = new Translator($langcode);
41
            }
42
            $this->translator->addLoader('json', new JsonFileLoader());
43
            $this->translator->addResource('json', __DIR__.'/../../resource/translations/messages.' . $langcode . '.json', $langcode);
44
        }
45
        $this->initializeLogging();
46
    }
47
48
    /**
49
     * Returns the GlobalConfig object given to the Model as a constructor parameter.
50
     * @return GlobalConfig
51
     */
52
    public function getConfig()
53
    {
54
        return $this->globalConfig;
55
    }
56
57
    /**
58
     * Configures the logging facility
59
     */
60
    private function initializeLogging()
61
    {
62
        $this->logger = new \Monolog\Logger('general');
63
        $formatter = new \Monolog\Formatter\LineFormatter("[%datetime%] %level_name% %message%\n");
64
        $formatter->allowInlineLineBreaks(true);
65
        if ($this->getConfig()->getLoggingBrowserConsole()) {
66
            $browserHandler = new \Monolog\Handler\BrowserConsoleHandler(\Monolog\Logger::INFO);
0 ignored issues
show
Deprecated Code introduced by
The constant Monolog\Logger::INFO has been deprecated: Use \Monolog\Level::Info ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

66
            $browserHandler = new \Monolog\Handler\BrowserConsoleHandler(/** @scrutinizer ignore-deprecated */ \Monolog\Logger::INFO);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
67
            $browserHandler->setFormatter($formatter);
68
            $this->logger->pushHandler($browserHandler);
69
        }
70
        if ($this->getConfig()->getLoggingFilename() !== null) {
0 ignored issues
show
introduced by
The condition $this->getConfig()->getLoggingFilename() !== null is always true.
Loading history...
71
            $streamHandler = new \Monolog\Handler\StreamHandler($this->getConfig()->getLoggingFilename(), \Monolog\Logger::INFO);
0 ignored issues
show
Deprecated Code introduced by
The constant Monolog\Logger::INFO has been deprecated: Use \Monolog\Level::Info ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

71
            $streamHandler = new \Monolog\Handler\StreamHandler($this->getConfig()->getLoggingFilename(), /** @scrutinizer ignore-deprecated */ \Monolog\Logger::INFO);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
72
            $streamHandler->setFormatter($formatter);
73
            $this->logger->pushHandler($streamHandler);
74
        }
75
        if (!$this->logger->getHandlers()) {
76
            // add a NullHandler to suppress the default Monolog logging to stderr
77
            $nullHandler = new \Monolog\Handler\NullHandler();
78
            $this->logger->pushHandler($nullHandler);
79
        }
80
    }
81
82
    /**
83
     * Return the logging facility
84
     * @return object logger
85
     */
86
    public function getLogger()
87
    {
88
        return $this->logger;
89
    }
90
91
    /**
92
     * Return the version of this Skosmos installation, or "unknown" if
93
     * it cannot be determined. The version information is based on Git tags.
94
     * @return string version
95
     */
96
    public function getVersion(): string
97
    {
98
        $ver = \Composer\InstalledVersions::getRootPackage()['pretty_version'];
99
        if ($ver === null) {
100
            return "unknown";
101
        }
102
103
        return $ver;
104
    }
105
106
    /**
107
     * Return all the vocabularies available.
108
     * @param boolean $categories whether you want everything included in a subarray of
109
     * a category.
110
     * @param boolean $shortname set to true if you want the vocabularies sorted by
111
     * their shortnames instead of their titles.
112
     */
113
    public function getVocabularyList($categories = true, $shortname = false)
114
    {
115
        $cats = $this->getVocabularyCategories();
116
        $ret = array();
117
        foreach ($cats as $cat) {
118
            $catlabel = $cat->getTitle();
119
120
            // find all the vocabs in this category
121
            $vocs = array();
122
            foreach ($cat->getVocabularies() as $voc) {
123
                $vocs[$shortname ? $voc->getConfig()->getShortname() : $voc->getConfig()->getTitle()] = $voc;
124
            }
125
            uksort($vocs, 'strcoll');
126
127
            if (sizeof($vocs) > 0 && $categories) {
128
                $ret[$catlabel] = $vocs;
129
            } elseif (sizeof($vocs) > 0) {
130
                $ret = array_merge($ret, $vocs);
131
            }
132
133
        }
134
135
        if (!$categories) {
136
            uksort($ret, 'strcoll');
137
        }
138
139
        return $ret;
140
    }
141
142
    /**
143
     * Return all types (RDFS/OWL classes) present in the specified vocabulary or all vocabularies.
144
     * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
145
     */
146
    public function getTypes($vocid = null, $lang = null)
147
    {
148
        $sparql = (isset($vocid)) ? $this->getVocabulary($vocid)->getSparql() : $this->getDefaultSparql();
149
        $result = $sparql->queryTypes($lang);
150
        foreach ($result as $uri => $values) {
151
            if (empty($values)) {
152
                $shorteneduri = EasyRdf\RdfNamespace::shorten($uri);
153
                if ($shorteneduri !== null) {
154
                    if (isset($lang)) {
155
                        $this->translator->setlocale($lang);
0 ignored issues
show
Bug introduced by
The method setlocale() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

155
                        $this->translator->/** @scrutinizer ignore-call */ 
156
                                           setlocale($lang);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
156
                    }
157
                    $trans = $this->getText($shorteneduri);
158
                    if ($trans) {
159
                        $result[$uri] = array('label' => $trans);
160
                    }
161
                }
162
            }
163
        }
164
165
        return $result;
166
    }
167
168
    /**
169
     * Return the languages present in the configured vocabularies.
170
     * @return array Array with lang codes (string)
171
     */
172
    public function getLanguages($lang)
173
    {
174
        $vocabs = $this->getVocabularyList(false);
175
        $ret = array();
176
        foreach ($vocabs as $vocab) {
177
            foreach ($vocab->getConfig()->getLanguages() as $langcode) {
178
                $langlit = Punic\Language::getName($langcode, $lang);
179
                $ret[$langlit] = $langcode;
180
            }
181
        }
182
        ksort($ret);
183
        return array_unique($ret);
184
    }
185
186
    /**
187
     * Return Symfony Translator object for translations in given language.
188
     * @return Translator Translator object from Symfony package
189
     */
190
    public function getTranslator()
191
    {
192
        return $this->translator;
193
    }
194
195
    /**
196
     * Sets translation language for Symfony Translator objext
197
     * @param string $lang two character language code 'fi' or compound language (locale) name such as 'fi_FI'
198
     */
199
    public function setLocale($locale)
200
    {
201
        $this->translator->setlocale($locale);
202
    }
203
204
    /**
205
     * Gets translation language from Symfony Translator objext
206
     * @return string $lang two character language code 'fi' or compound language (locale) name such as 'fi_FI'
207
     */
208
    public function getLocale()
209
    {
210
        return $this->translator->getlocale();
211
    }
212
213
    /**
214
     * Get text translated in language set by SetLocale function
215
     *
216
     * @param string $text text to be translated
217
     */
218
    public function getText($text)
219
    {
220
        return $this->translator->trans($text);
221
    }
222
223
    /**
224
     * returns a concept's RDF data in downloadable format
225
     * @param string $vocid vocabulary id, or null for global data (from all vocabularies)
226
     * @param string $uri concept URI
227
     * @param string $format the format in which you want to get the result, currently this function supports
228
     * text/turtle, application/rdf+xml and application/json
229
     * @return string RDF data in the requested serialization
230
     */
231
    public function getRDF($vocid, $uri, $format)
232
    {
233
234
        if ($format == 'text/turtle') {
235
            $retform = 'turtle';
236
            $serialiser = new EasyRdf\Serialiser\Turtle();
237
        } elseif ($format == 'application/ld+json' || $format == 'application/json') {
238
            $retform = 'jsonld'; // serve JSON-LD for both JSON-LD and plain JSON requests
239
            $serialiser = new EasyRdf\Serialiser\JsonLd();
240
        } else {
241
            $retform = 'rdfxml';
242
            $serialiser = new EasyRdf\Serialiser\RdfXml();
243
        }
244
245
        if ($vocid !== null) {
0 ignored issues
show
introduced by
The condition $vocid !== null is always true.
Loading history...
246
            $vocab = $this->getVocabulary($vocid);
247
            $sparql = $vocab->getSparql();
248
            $arrayClass = $vocab->getConfig()->getArrayClassURI();
249
            $vocabs = array($vocab);
250
        } else {
251
            $sparql = $this->getDefaultSparql();
252
            $arrayClass = null;
253
            $vocabs = null;
254
        }
255
        $result = $sparql->queryConceptInfoGraph($uri, $arrayClass, $vocabs);
256
257
        if (!$result->isEmpty()) {
258
            return $serialiser->serialise($result, $retform);
259
        }
260
        return "";
261
    }
262
263
    /**
264
     * Makes a SPARQL-query to the endpoint that retrieves concept
265
     * references as it's search results.
266
     * @param ConceptSearchParameters $params an object that contains all the parameters needed for the search
267
     * @return array search results
268
     */
269
    public function searchConcepts($params)
270
    {
271
        // don't even try to search for empty prefix if no other search criteria (group or parent concept) has been set
272
        if (($params->getSearchTerm() === "" || !preg_match('/[^*]/', $params->getSearchTerm())) && !$params->getGroupLimit() && !$params->getParentLimit()) {
273
            return array();
274
        }
275
276
        $vocabs = $params->getVocabs();
277
        $showDeprecated = false;
278
        if (sizeof($vocabs) === 1) { // search within vocabulary
279
            $voc = $vocabs[0];
280
            $sparql = $voc->getSparql();
281
            $showDeprecated = $voc->getConfig()->getShowDeprecated();
282
        } else { // multi-vocabulary or global search
283
            $voc = null;
284
            $sparql = $this->getDefaultSparql();
285
            // @TODO : in a global search showDeprecated will always be false and cannot be set globally
286
        }
287
288
        $results = $sparql->queryConcepts($vocabs, $params->getAdditionalFields(), $params->getUnique(), $params, $showDeprecated);
289
        if ($params->getRest() && $results && $params->getSearchLimit() !== 0) {
290
            $results = array_slice($results, $params->getOffset(), $params->getSearchLimit());
291
        }
292
        $ret = array();
293
294
        foreach ($results as $hit) {
295
            if (sizeof($vocabs) == 1) {
296
                $hitvoc = $voc;
297
                $hit['vocab'] = $vocabs[0]->getId();
298
            } else {
299
                try {
300
                    $hitvoc = $this->getVocabularyByGraph($hit['graph']);
301
                    $hit['vocab'] = $hitvoc->getId();
302
                } catch (ValueError $e) {
303
                    trigger_error($e->getMessage(), E_USER_WARNING);
304
                    $hitvoc = null;
305
                    $hit['vocab'] = "???";
306
                }
307
            }
308
            unset($hit['graph']);
309
310
            $hit['voc'] = $hitvoc;
311
312
            if ($hitvoc === null || !$hitvoc->containsURI($hit['uri'])) {
313
                // if uri is a external vocab uri that is included in the current vocab
314
                $realvoc = $this->guessVocabularyFromURI($hit['uri'], $voc !== null ? $voc->getId() : null);
315
                if ($realvoc !== $hitvoc) {
316
                    unset($hit['localname']);
317
                    $hit['exvocab'] = ($realvoc !== null) ? $realvoc->getId() : "???";
318
                }
319
            }
320
321
            $ret[] = $hit;
322
        }
323
324
        return $ret;
325
    }
326
327
    /**
328
     * Function for performing a search for concepts and their data fields.
329
     * @param ConceptSearchParameters $params an object that contains all the parameters needed for the search
330
     * @return array array with keys 'count' and 'results'
331
     */
332
    public function searchConceptsAndInfo($params)
333
    {
334
        $params->setUnique(true);
335
        $allhits = $this->searchConcepts($params);
336
        $count = sizeof($allhits);
337
        $hits = array_slice($allhits, intval($params->getOffset()), $params->getSearchLimit());
338
339
        $ret = array();
340
        $uris = array();
341
        $vocabs = array();
342
        $uniqueVocabs = array();
343
        foreach ($hits as $hit) {
344
            $uniqueVocabs[$hit['voc']->getId()] = $hit['voc']->getId();
345
            $vocabs[] = $hit['voc'];
346
            $uris[] = $hit['uri'];
347
        }
348
        if (sizeof($uniqueVocabs) == 1) {
349
            $voc = $vocabs[0];
350
            $sparql = $voc->getSparql();
351
            $arrayClass = $voc->getConfig()->getArrayClassURI();
352
        } else {
353
            $arrayClass = null;
354
            $sparql = $this->getDefaultSparql();
355
        }
356
        if (sizeof($uris) > 0) {
357
            $ret = $sparql->queryConceptInfo($uris, $arrayClass, $vocabs, $params->getSearchLang());
358
        }
359
360
        // For marking that the concept has been found through an alternative label, hidden
361
        // label or a label in another language
362
        foreach ($hits as $idx => $hit) {
363
            if (isset($hit['altLabel']) && isset($ret[$idx])) {
364
                $ret[$idx]->setFoundBy($hit['altLabel'], 'alt');
365
            }
366
367
            if (isset($hit['hiddenLabel']) && isset($ret[$idx])) {
368
                $ret[$idx]->setFoundBy($hit['hiddenLabel'], 'hidden');
369
            }
370
371
            if (isset($hit['matchedPrefLabel'])) {
372
                $ret[$idx]->setFoundBy($hit['matchedPrefLabel'], 'lang');
373
            }
374
375
            if ($ret[$idx] && isset($hit['lang'])) {
376
                $ret[$idx]->setContentLang($hit['lang']);
377
            }
378
        }
379
380
        return array('count' => $count, 'results' => $ret);
381
    }
382
383
    /**
384
     * Creates dataobjects from an input array.
385
     * @param string $class the type of class eg. 'Vocabulary'.
386
     * @param array $resarr contains the EasyRdf\Resources.
387
     */
388
    private function createDataObjects($class, $resarr)
389
    {
390
        $ret = array();
391
        foreach ($resarr as $res) {
392
            $ret[] = new $class($this, $res);
393
        }
394
395
        return $ret;
396
    }
397
398
    /**
399
     * Returns the cached vocabularies.
400
     * @return array of Vocabulary dataobjects
401
     */
402
    public function getVocabularies()
403
    {
404
        if ($this->allVocabularies === null) { // initialize cache
405
            $vocs = $this->globalConfig->getGraph()->allOfType('skosmos:Vocabulary');
406
            $this->allVocabularies = $this->createDataObjects("Vocabulary", $vocs);
407
            foreach ($this->allVocabularies as $voc) {
408
                // register vocabulary ids as RDF namespace prefixes
409
                $prefix = preg_replace('/\W+/', '', $voc->getId()); // strip non-word characters
410
                try {
411
                    if ($prefix != '' && EasyRdf\RdfNamespace::get($prefix) === null) { // if not already defined
412
                        EasyRdf\RdfNamespace::set($prefix, $voc->getUriSpace());
413
                    }
414
415
                } catch (Exception $e) {
416
                    // not valid as namespace identifier, ignore
417
                }
418
            }
419
        }
420
421
        return $this->allVocabularies;
422
    }
423
424
    /**
425
     * Returns the cached vocabularies from a category.
426
     * @param EasyRdf\Resource $cat the category in question
427
     * @return array of vocabulary dataobjects
428
     */
429
    public function getVocabulariesInCategory($cat)
430
    {
431
        $vocs = $this->globalConfig->getGraph()->resourcesMatching('dc:subject', $cat);
432
        return $this->createDataObjects("Vocabulary", $vocs);
433
    }
434
435
    /**
436
     * Creates dataobjects of all the different vocabulary categories (Health etc.).
437
     * @return array of Dataobjects of the type VocabularyCategory.
438
     */
439
    public function getVocabularyCategories()
440
    {
441
        $cats = $this->globalConfig->getGraph()->allOfType('skos:Concept');
442
        if (empty($cats)) {
443
            return array(new VocabularyCategory($this, null));
444
        }
445
446
        return $this->createDataObjects("VocabularyCategory", $cats);
447
    }
448
449
    /**
450
     * Returns the label defined in config.ttl with the appropriate language.
451
     * @param string $lang language code of returned labels, eg. 'fi'
452
     * @return string the label for vocabulary categories.
453
     */
454
    public function getClassificationLabel($lang)
455
    {
456
        $cats = $this->globalConfig->getGraph()->allOfType('skos:ConceptScheme');
457
        return $cats ? $cats[0]->label($lang) : null;
0 ignored issues
show
Bug introduced by
The method label() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

457
        return $cats ? $cats[0]->/** @scrutinizer ignore-call */ label($lang) : null;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method label() does not exist on EasyRdf\Literal. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

457
        return $cats ? $cats[0]->/** @scrutinizer ignore-call */ label($lang) : null;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
458
    }
459
460
    /**
461
     * Returns a single cached vocabulary.
462
     * @param string $vocid the vocabulary id eg. 'mesh'.
463
     * @return Vocabulary dataobject
464
     */
465
    public function getVocabulary($vocid): Vocabulary
466
    {
467
        $vocs = $this->getVocabularies();
468
        foreach ($vocs as $voc) {
469
            if ($voc->getId() == $vocid) {
470
                return $voc;
471
            }
472
        }
473
        throw new ValueError("Vocabulary id '$vocid' not found in configuration.");
474
    }
475
476
    /**
477
     * Return the vocabulary that is stored in the given graph on the given endpoint.
478
     *
479
     * @param $graph string graph URI
480
     * @param $endpoint string endpoint URL (default SPARQL endpoint if omitted)
481
     * @return Vocabulary vocabulary of this URI, or null if not found
482
     */
483
    public function getVocabularyByGraph($graph, $endpoint = null)
484
    {
485
        if ($endpoint === null) {
486
            $endpoint = $this->getConfig()->getDefaultEndpoint();
487
        }
488
        if ($this->vocabsByGraph === null) { // initialize cache
489
            $this->vocabsByGraph = array();
490
            foreach ($this->getVocabularies() as $voc) {
491
                $key = json_encode(array($voc->getGraph(), $voc->getEndpoint()));
492
                $this->vocabsByGraph[$key] = $voc;
493
            }
494
        }
495
496
        $key = json_encode(array($graph, $endpoint));
497
        if (array_key_exists($key, $this->vocabsByGraph)) {
498
            return $this->vocabsByGraph[$key];
499
        } else {
500
            throw new ValueError("no vocabulary found for graph $graph and endpoint $endpoint");
501
        }
502
503
    }
504
505
    /**
506
     * When multiple vocabularies share same URI namespace, return the
507
     * vocabulary in which the URI is actually defined (has a label).
508
     *
509
     * @param Vocabulary[] $vocabs vocabularies to search
510
     * @param string $uri URI to look for
511
     * @param $preferredVocabId string ID of the preferred vocabulary to return if more than one is found
512
     * @return Vocabulary the vocabulary with the URI
513
     */
514
515
    private function disambiguateVocabulary($vocabs, $uri, $preferredVocabId = null)
516
    {
517
        // if there is only one candidate vocabulary, return it
518
        if (sizeof($vocabs) == 1) {
519
            return $vocabs[0];
520
        }
521
522
        // if there are multiple vocabularies and one is the preferred vocabulary, return it
523
        if ($preferredVocabId != null) {
524
            foreach ($vocabs as $vocab) {
525
                if ($vocab->getId() == $preferredVocabId) {
526
                    try {
527
                        // double check that a label exists in the preferred vocabulary
528
                        if ($vocab->getConceptLabel($uri, null) !== null) {
529
                            return $vocab;
530
                        } else {
531
                            // not found in preferred vocabulary, fall back to next method
532
                            break;
533
                        }
534
                    } catch (EasyRdf\Http\Exception | EasyRdf\Exception | Throwable $e) {
535
                        if ($this->getConfig()->getLogCaughtExceptions()) {
536
                            error_log('Caught exception: ' . $e->getMessage());
537
                        }
538
                        break;
539
                    }
540
                }
541
            }
542
        }
543
544
        // no preferred vocabulary, or it was not found, search in which vocabulary the concept has a label
545
        foreach ($vocabs as $vocab) {
546
            try {
547
                if ($vocab->getConceptLabel($uri, null) !== null) {
548
                    return $vocab;
549
                }
550
            } catch (EasyRdf\Http\Exception | EasyRdf\Exception | Throwable $e) {
551
                if ($this->getConfig()->getLogCaughtExceptions()) {
552
                    error_log('Caught exception: ' . $e->getMessage());
553
                }
554
                break;
555
            }
556
        }
557
558
        // if the URI couldn't be found, fall back to the first vocabulary
559
        return $vocabs[0];
560
    }
561
562
    /**
563
     * Guess which vocabulary a URI originates from, based on the declared
564
     * vocabulary URI spaces.
565
     *
566
     * @param $uri string URI to search
567
     * @param $preferredVocabId string ID of the preferred vocabulary to return if more than one is found
568
     * @return Vocabulary vocabulary of this URI, or null if not found
569
     */
570
    public function guessVocabularyFromURI($uri, $preferredVocabId = null)
571
    {
572
        if ($this->vocabsByUriSpace === null) { // initialize cache
573
            $this->vocabsByUriSpace = array();
574
            foreach ($this->getVocabularies() as $voc) {
575
                $uriSpace = $voc->getUriSpace();
576
                if ($uriSpace) {
577
                    $this->vocabsByUriSpace[$uriSpace][] = $voc;
578
                }
579
            }
580
        }
581
582
        // try to guess the URI space and look it up in the cache
583
        $res = new EasyRdf\Resource($uri);
584
        $namespace = substr(strval($uri), 0, -strlen(strval($res->localName())));
585
        if ($namespace && array_key_exists($namespace, $this->vocabsByUriSpace)) {
586
            $vocabs = $this->vocabsByUriSpace[$namespace];
587
            return $this->disambiguateVocabulary($vocabs, $uri, $preferredVocabId);
588
        }
589
590
        // didn't work, try to match with each URI space separately
591
        foreach ($this->vocabsByUriSpace as $urispace => $vocabs) {
592
            if (strpos($uri, $urispace) === 0) {
593
                return $this->disambiguateVocabulary($vocabs, $uri, $preferredVocabId);
594
            }
595
        }
596
597
        // not found
598
        return null;
599
    }
600
601
    /**
602
     * Get the label for a resource, preferring 1. the given language 2. configured languages 3. any language.
603
     * @param EasyRdf\Resource $res resource whose label to return
604
     * @param string $lang preferred language
605
     * @return EasyRdf\Literal label as an EasyRdf\Literal object, or null if not found
606
     */
607
    public function getResourceLabel($res, $lang)
608
    {
609
        $langs = array_merge(array($lang), array_keys($this->getConfig()->getLanguages()));
610
        foreach ($langs as $l) {
611
            $label = $res->label($l);
612
            if ($label !== null) {
613
                return $label;
614
            }
615
616
        }
617
        return $res->label(); // desperate check for label in any language; will return null if even this fails
618
    }
619
620
    public function getResourceFromUri($uri)
621
    {
622
        // using apc cache for the resource if available
623
        if ($this->globalConfig->getCache()->isAvailable()) {
624
            // @codeCoverageIgnoreStart
625
            $key = 'fetch: ' . $uri;
626
            $resource = $this->globalConfig->getCache()->fetch($key);
627
            if ($resource === null || $resource === false) { // was not found in cache, or previous request failed
628
                $resource = $this->resolver->resolve($uri, $this->getConfig()->getHttpTimeout());
629
                $this->globalConfig->getCache()->store($key, $resource, self::URI_FETCH_TTL);
630
            }
631
            // @codeCoverageIgnoreEnd
632
        } else { // APC not available, parse on every request
633
            $resource = $this->resolver->resolve($uri, $this->getConfig()->getHttpTimeout());
634
        }
635
        return $resource;
636
    }
637
638
    /**
639
     * Returns a SPARQL endpoint object.
640
     * @param string $dialect eg. 'JenaText'.
641
     * @param string $endpoint url address of endpoint
642
     * @param string|null $graph uri for the target graph.
643
     */
644
    public function getSparqlImplementation($dialect, $endpoint, $graph)
645
    {
646
        $classname = $dialect . "Sparql";
647
648
        return new $classname($endpoint, $graph, $this);
649
    }
650
651
    /**
652
     * Returns a SPARQL endpoint object using the default implementation set in the config.ttl.
653
     */
654
    public function getDefaultSparql()
655
    {
656
        return $this->getSparqlImplementation($this->getConfig()->getDefaultSparqlDialect(), $this->getConfig()->getDefaultEndpoint(), '?graph');
657
    }
658
659
}
660