Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.19% covered (warning)
74.19%
23 / 31
33.33% covered (danger)
33.33%
4 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediaSearchASTEntitiesExtractor
74.19% covered (warning)
74.19%
23 / 31
33.33% covered (danger)
33.33%
4 / 12
20.40
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEntities
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 getTerms
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 getTerm
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 visitWordsQueryNode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitPhraseQueryNode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitPhrasePrefixNode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitFuzzyNode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitPrefixNode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitWildcardNode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitEmptyQueryNode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitKeywordFeatureNode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Wikibase\MediaInfo\Search;
4
5use CirrusSearch\Parser\AST\EmptyQueryNode;
6use CirrusSearch\Parser\AST\FuzzyNode;
7use CirrusSearch\Parser\AST\KeywordFeatureNode;
8use CirrusSearch\Parser\AST\ParsedNode;
9use CirrusSearch\Parser\AST\ParsedQuery;
10use CirrusSearch\Parser\AST\PhrasePrefixNode;
11use CirrusSearch\Parser\AST\PhraseQueryNode;
12use CirrusSearch\Parser\AST\PrefixNode;
13use CirrusSearch\Parser\AST\Visitor\LeafVisitor;
14use CirrusSearch\Parser\AST\WildcardNode;
15use CirrusSearch\Parser\AST\WordsQueryNode;
16use SplObjectStorage;
17
18/**
19 * This visitor simply extracts all individual search terms out of
20 * a query that might contain multiple clauses (AND, OR, ...) so that
21 * we can look them all up individually to fetch relevant entities.
22 */
23class MediaSearchASTEntitiesExtractor extends LeafVisitor {
24    /** @var ParsedQuery */
25    private $parsedQuery;
26
27    /** @var SplObjectStorage */
28    private $terms;
29
30    /** @var SplObjectStorage */
31    private $entities;
32
33    public function __construct(
34        private readonly MediaSearchEntitiesFetcher $entitiesFetcher,
35    ) {
36        parent::__construct();
37    }
38
39    public function getEntities( ParsedQuery $parsedQuery, ParsedNode $parsedNode ): array {
40        if ( !isset( $this->entities[$parsedQuery] ) ) {
41            // walk the AST and extract all terms
42            $terms = $this->getTerms( $parsedQuery );
43            // then fetch entities for all of them
44            // re-init entities map; runtime code will usually only have only 1 query,
45            // but tests might execute plenty & we don't want to keep results for an
46            // entire suite in memory
47            $this->entities = new SplObjectStorage();
48            $this->entities[$parsedQuery] = $this->entitiesFetcher->get( $terms );
49        }
50
51        if ( !isset( $this->terms[$parsedNode] ) ) {
52            return [];
53        }
54
55        $term = $this->terms[$parsedNode];
56        if ( !isset( $this->entities[$parsedQuery][$term] ) ) {
57            return [];
58        }
59
60        return $this->entities[$parsedQuery][$term];
61    }
62
63    private function getTerms( ParsedQuery $parsedQuery ): array {
64        $this->terms = new SplObjectStorage();
65        $this->parsedQuery = $parsedQuery;
66        $this->parsedQuery->getRoot()->accept( $this );
67
68        $terms = [];
69        foreach ( $this->terms as $node ) {
70            $terms[] = $this->terms[$node];
71        }
72
73        return $terms;
74    }
75
76    private function getTerm( ParsedNode $node ): string {
77        return substr(
78            $this->parsedQuery->getQuery(),
79            $node->getStartOffset(),
80            $node->getEndOffset() - $node->getStartOffset()
81        );
82    }
83
84    /**
85     * @inheritDoc
86     */
87    public function visitWordsQueryNode( WordsQueryNode $node ) {
88        $this->terms[$node] = $this->getTerm( $node );
89    }
90
91    /**
92     * @inheritDoc
93     */
94    public function visitPhraseQueryNode( PhraseQueryNode $node ) {
95        $this->terms[$node] = $this->getTerm( $node );
96    }
97
98    /**
99     * @inheritDoc
100     */
101    public function visitPhrasePrefixNode( PhrasePrefixNode $node ) {
102        // @todo implement this
103        // $this->terms[$node] = $this->getTerm( $node );
104    }
105
106    /**
107     * @inheritDoc
108     */
109    public function visitFuzzyNode( FuzzyNode $node ) {
110        // @todo implement this
111        // $this->terms[$node] = $this->getTerm( $node );
112    }
113
114    /**
115     * @inheritDoc
116     */
117    public function visitPrefixNode( PrefixNode $node ) {
118        // @todo implement this
119        // $this->terms[$node] = $this->getTerm( $node );
120    }
121
122    /**
123     * @inheritDoc
124     */
125    public function visitWildcardNode( WildcardNode $node ) {
126        // @todo implement this
127        // $this->terms[$node] = $this->getTerm( $node );
128    }
129
130    /**
131     * @inheritDoc
132     */
133    public function visitEmptyQueryNode( EmptyQueryNode $node ) {
134        // @todo implement this
135        // $this->terms[$node] = $this->getTerm( $node );
136    }
137
138    /**
139     * @inheritDoc
140     */
141    public function visitKeywordFeatureNode( KeywordFeatureNode $node ) {
142        // not relevant here
143    }
144}