Passed
Pull Request — master (#7159)
by
unknown
09:00
created

XapianSearchService   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 89
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 39
c 1
b 0
f 0
dl 0
loc 89
rs 10
wmc 8

2 Methods

Rating   Name   Duplication   Size   Complexity  
B search() 0 70 7
A __construct() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Search\Xapian;
8
9
use RuntimeException;
10
11
/**
12
 * High-level Xapian search service for Chamilo 2.
13
 *
14
 * Demo version: free-text search over documents indexed by XapianIndexService.
15
 */
16
final class XapianSearchService
17
{
18
    public function __construct(
19
        private readonly SearchIndexPathResolver $indexPathResolver,
20
    ) {
21
    }
22
23
    /**
24
     * Execute a simple search query against the Xapian index.
25
     *
26
     * @param string $queryString Free text query string
27
     * @param int    $offset      First result offset (0-based)
28
     * @param int    $length      Maximum number of results
29
     *
30
     * @return array{
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{ at position 2 could not be parsed: the token is null at position 2.
Loading history...
31
     *     count:int,
32
     *     results:array<int,array<string,mixed>>
33
     * }
34
     */
35
    public function search(
36
        string $queryString,
37
        int $offset = 0,
38
        int $length = 10,
39
        array $extra = [],    // Kept for signature compatibility but unused in the demo
40
        int $countType = 0,   // Kept for signature compatibility but unused in the demo
41
    ): array {
42
        if (!\class_exists(\XapianDatabase::class)) {
43
            throw new RuntimeException('Xapian PHP extension is not loaded.');
44
        }
45
46
        $indexDir = $this->indexPathResolver->getIndexDir();
47
        $this->indexPathResolver->ensureIndexDirectoryExists();
48
49
        try {
50
            $db = new \XapianDatabase($indexDir);
51
        } catch (\Throwable $e) {
52
            throw new RuntimeException(
53
                \sprintf('Unable to open Xapian database at "%s": %s', $indexDir, $e->getMessage()),
54
                0,
55
                $e
56
            );
57
        }
58
59
        $enquire = new \XapianEnquire($db);
60
61
        if ($queryString !== '') {
62
            // Free-text query with stemming
63
            $queryParser = new \XapianQueryParser();
64
            $stemmer     = new \XapianStem('english'); // TODO: pick language from user/platform
65
66
            $queryParser->set_stemmer($stemmer);
67
            $queryParser->set_database($db);
68
            $queryParser->set_stemming_strategy(\XapianQueryParser::STEM_SOME);
69
70
            $parsedQuery = $queryParser->parse_query($queryString);
71
            $query       = $parsedQuery;
72
        } else {
73
            // Empty query = match everything
74
            $query = new \XapianQuery('');
75
        }
76
77
        $enquire->set_query($query);
78
79
        $matches = $enquire->get_mset($offset, $length);
80
81
        $results = [];
82
83
        for ($m = $matches->begin(); !$m->equals($matches->end()); $m->next()) {
84
            $document = $m->get_document();
85
86
            if (!$document instanceof \XapianDocument) {
87
                continue;
88
            }
89
90
            $rawData = $document->get_data();
91
            $data    = $rawData !== '' ? @\unserialize($rawData) : null;
92
93
            $results[] = [
94
                'doc_id' => $m->get_docid(),
95
                'score'  => $m->get_percent(),
96
                'data'   => $data,
97
            ];
98
        }
99
100
        $count = $matches->get_matches_estimated();
101
102
        return [
103
            'count'   => $count,
104
            'results' => $results,
105
        ];
106
    }
107
}
108