Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.00% covered (warning)
75.00%
24 / 32
33.33% covered (danger)
33.33%
4 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediaSearchASTEntitiesExtractor
75.00% covered (warning)
75.00%
24 / 32
33.33% covered (danger)
33.33%
4 / 12
20.00
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
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 MediaSearchEntitiesFetcher */
28    private $entitiesFetcher;
29
30    /** @var SplObjectStorage */
31    private $terms;
32
33    /** @var SplObjectStorage */
34    private $entities;
35
36    public function __construct( MediaSearchEntitiesFetcher $entitiesFetcher ) {
37        parent::__construct();
38        $this->entitiesFetcher = $entitiesFetcher;
39    }
40
41    public function getEntities( ParsedQuery $parsedQuery, ParsedNode $parsedNode ): array {
42        if ( !isset( $this->entities[$parsedQuery] ) ) {
43            // walk the AST and extract all terms
44            $terms = $this->getTerms( $parsedQuery );
45            // then fetch entities for all of them
46            // re-init entities map; runtime code will usually only have only 1 query,
47            // but tests might execute plenty & we don't want to keep results for an
48            // entire suite in memory
49            $this->entities = new SplObjectStorage();
50            $this->entities[$parsedQuery] = $this->entitiesFetcher->get( $terms );
51        }
52
53        if ( !isset( $this->terms[$parsedNode] ) ) {
54            return [];
55        }
56
57        $term = $this->terms[$parsedNode];
58        if ( !isset( $this->entities[$parsedQuery][$term] ) ) {
59            return [];
60        }
61
62        return $this->entities[$parsedQuery][$term];
63    }
64
65    private function getTerms( ParsedQuery $parsedQuery ): array {
66        $this->terms = new SplObjectStorage();
67        $this->parsedQuery = $parsedQuery;
68        $this->parsedQuery->getRoot()->accept( $this );
69
70        $terms = [];
71        foreach ( $this->terms as $node ) {
72            $terms[] = $this->terms[$node];
73        }
74
75        return $terms;
76    }
77
78    private function getTerm( ParsedNode $node ): string {
79        return substr(
80            $this->parsedQuery->getQuery(),
81            $node->getStartOffset(),
82            $node->getEndOffset() - $node->getStartOffset()
83        );
84    }
85
86    /**
87     * @inheritDoc
88     */
89    public function visitWordsQueryNode( WordsQueryNode $node ) {
90        $this->terms[$node] = $this->getTerm( $node );
91    }
92
93    /**
94     * @inheritDoc
95     */
96    public function visitPhraseQueryNode( PhraseQueryNode $node ) {
97        $this->terms[$node] = $this->getTerm( $node );
98    }
99
100    /**
101     * @inheritDoc
102     */
103    public function visitPhrasePrefixNode( PhrasePrefixNode $node ) {
104        // @todo implement this
105        // $this->terms[$node] = $this->getTerm( $node );
106    }
107
108    /**
109     * @inheritDoc
110     */
111    public function visitFuzzyNode( FuzzyNode $node ) {
112        // @todo implement this
113        // $this->terms[$node] = $this->getTerm( $node );
114    }
115
116    /**
117     * @inheritDoc
118     */
119    public function visitPrefixNode( PrefixNode $node ) {
120        // @todo implement this
121        // $this->terms[$node] = $this->getTerm( $node );
122    }
123
124    /**
125     * @inheritDoc
126     */
127    public function visitWildcardNode( WildcardNode $node ) {
128        // @todo implement this
129        // $this->terms[$node] = $this->getTerm( $node );
130    }
131
132    /**
133     * @inheritDoc
134     */
135    public function visitEmptyQueryNode( EmptyQueryNode $node ) {
136        // @todo implement this
137        // $this->terms[$node] = $this->getTerm( $node );
138    }
139
140    /**
141     * @inheritDoc
142     */
143    public function visitKeywordFeatureNode( KeywordFeatureNode $node ) {
144        // not relevant here
145    }
146}