Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.24% covered (success)
95.24%
40 / 42
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
NamespaceMatcher
95.24% covered (success)
95.24%
40 / 42
80.00% covered (warning)
80.00%
4 / 5
17
0.00% covered (danger)
0.00%
0 / 1
 create
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 buildFromProfile
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
4.04
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 identifyNamespace
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 getIndexedNamespaces
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3namespace CirrusSearch;
4
5use CirrusSearch\Profile\SearchProfileService;
6use CirrusSearch\SecondTry\SecondTryRunner;
7use CirrusSearch\SecondTry\SecondTryRunnerFactory;
8use CirrusSearch\SecondTry\SecondTrySearchFactory;
9use MediaWiki\Language\Language;
10
11/**
12 * In memory index of namespaces using SecondTryRunner for normalizing strings at index & search time.
13 */
14class NamespaceMatcher {
15    public const SERVICE = self::class;
16    private SecondTryRunner $indexRunner;
17    private SecondTryRunner $searchRunner;
18    private Language $language;
19    private ?array $indexedNamespaces = null;
20
21    public static function create(
22        Language $language,
23        SecondTrySearchFactory $secondTrySearchFactory,
24        SearchConfig $config
25    ): NamespaceMatcher {
26        $profile = $config->getProfileService()->loadProfile( SearchProfileService::NAMESPACE_MATCHER );
27        return self::buildFromProfile( $profile, $language, $secondTrySearchFactory, $config );
28    }
29
30    public static function buildFromProfile(
31        array $profile,
32        Language $language,
33        SecondTrySearchFactory $secondTrySearchFactory,
34        SearchConfig $config
35    ): NamespaceMatcher {
36        $factory = new SecondTryRunnerFactory( $secondTrySearchFactory, $config );
37        $indexProfile = $profile['index_second_try_profile'] ?? null;
38        $searchProfile = $profile['search_second_try_profile'] ?? null;
39        if ( !is_string( $indexProfile ) ) {
40            throw new \RuntimeException( "Expected index_second_try_profile entry to exist and be a string" );
41        }
42        if ( !is_string( $searchProfile ) ) {
43            throw new \RuntimeException( "Expected search_second_try_profile entry to exist and be a string" );
44        }
45        $indexProfileData = $config->getProfileService()->loadProfileByName( SearchProfileService::SECOND_TRY, $indexProfile );
46        $indexRunner = $factory->buildFromProfile( $indexProfileData );
47        $searchRunner = $indexRunner;
48        if ( $indexProfile !== $searchProfile ) {
49            $searchRunner = $factory->buildFromProfile(
50                $config->getProfileService()->loadProfileByName( SearchProfileService::SECOND_TRY, $searchProfile )
51            );
52        }
53        return new self( $indexRunner, $searchRunner, $language );
54    }
55
56    private function __construct( SecondTryRunner $indexRunner, SecondTryRunner $searchRunner, Language $language ) {
57        $this->indexRunner = $indexRunner;
58        $this->searchRunner = $searchRunner;
59        $this->language = $language;
60    }
61
62    /**
63     * Identify a namespace from a string representation.
64     * May try to apply varying normalization techniques to increase recall compared to a naive
65     * lowercase match.
66     * @param string $namespace
67     * @return int|null the namespace id if found null otherwize
68     */
69    public function identifyNamespace( string $namespace ): ?int {
70        $indexedNs = $this->getIndexedNamespaces();
71        // attempt the raw lc form first
72        $foundNs = $indexedNs[$this->language->lc( $namespace )] ?? null;
73        if ( $foundNs !== null ) {
74            return $foundNs;
75        }
76        $candidateNamespaces = $this->searchRunner->candidates( $namespace );
77        foreach ( $candidateNamespaces as $methodCandidates ) {
78            foreach ( $methodCandidates as $methodCandidate ) {
79                $foundNs = $indexedNs[$methodCandidate] ?? null;
80                if ( $foundNs !== null ) {
81                    break;
82                }
83            }
84        }
85
86        return $foundNs;
87    }
88
89    /**
90     * @return array<string, int>
91     */
92    private function getIndexedNamespaces(): array {
93        if ( $this->indexedNamespaces === null ) {
94            $indexedNs = [];
95            foreach ( $this->language->getNamespaceIds() as $candidate => $nsId ) {
96                $normalizedCandidates = $this->indexRunner->candidates( $candidate );
97                $indexedNs[$candidate] = $nsId;
98                foreach ( $normalizedCandidates as $methodCandidates ) {
99                    foreach ( $methodCandidates as $methodCandidate ) {
100                        if ( !array_key_exists( $methodCandidate, $indexedNs ) ) {
101                            $indexedNs[$methodCandidate] = $nsId;
102                        }
103                    }
104                }
105            }
106            $this->indexedNamespaces = $indexedNs;
107        }
108
109        return $this->indexedNamespaces;
110    }
111}