Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.45% |
42 / 44 |
|
71.43% |
5 / 7 |
CRAP | |
0.00% |
0 / 1 |
RemoteIcuCollation | |
95.45% |
42 / 44 |
|
71.43% |
5 / 7 |
13 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getSortKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
encode | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
decode | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
getSortKeys | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
2 | |||
getFirstLetter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
doGetSortKeys | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
4.02 |
1 | <?php |
2 | |
3 | use MediaWiki\Shell\ShellboxClientFactory; |
4 | use Shellbox\RPC\RpcClient; |
5 | |
6 | /** |
7 | * An ICU collation that uses a remote server to compute sort keys. This can be |
8 | * used in conjunction with $wgTempCategoryCollations to migrate to a different |
9 | * version of ICU. |
10 | */ |
11 | class RemoteIcuCollation extends Collation { |
12 | private RpcClient $rpcClient; |
13 | private string $locale; |
14 | |
15 | public function __construct( ShellboxClientFactory $shellboxClientFactory, string $locale ) { |
16 | $this->rpcClient = $shellboxClientFactory->getRpcClient( |
17 | [ 'service' => 'icu-collation' ] ); |
18 | $this->locale = $locale; |
19 | } |
20 | |
21 | /** @inheritDoc */ |
22 | public function getSortKey( $string ) { |
23 | return $this->getSortKeys( [ $string ] )[0]; |
24 | } |
25 | |
26 | /** |
27 | * Encode an array of binary strings as a string |
28 | * |
29 | * @param string[] $strings |
30 | * @return string |
31 | */ |
32 | private static function encode( $strings ) { |
33 | $ret = ''; |
34 | foreach ( $strings as $s ) { |
35 | $ret .= sprintf( "%08x", strlen( $s ) ) . $s; |
36 | } |
37 | return $ret; |
38 | } |
39 | |
40 | /** |
41 | * Decode the value returned by encode() |
42 | * |
43 | * @param string $blob |
44 | * @return string[] |
45 | */ |
46 | private static function decode( $blob ) { |
47 | $p = 0; |
48 | $ret = []; |
49 | while ( $p < strlen( $blob ) ) { |
50 | $len = intval( substr( $blob, $p, 8 ), 16 ); |
51 | $p += 8; |
52 | $ret[] = substr( $blob, $p, $len ); |
53 | $p += $len; |
54 | } |
55 | return $ret; |
56 | } |
57 | |
58 | /** @inheritDoc */ |
59 | public function getSortKeys( $strings ) { |
60 | if ( !count( $strings ) ) { |
61 | return []; |
62 | } |
63 | $blob = $this->rpcClient->call( |
64 | 'icu-collation', |
65 | [ self::class, 'doGetSortKeys' ], |
66 | [ |
67 | $this->locale, |
68 | self::encode( array_values( $strings ) ) |
69 | ], |
70 | [ |
71 | 'classes' => [ parent::class, self::class ], |
72 | 'binary' => true |
73 | ] |
74 | ); |
75 | return array_combine( |
76 | array_keys( $strings ), |
77 | self::decode( $blob ) |
78 | ); |
79 | } |
80 | |
81 | /** @inheritDoc */ |
82 | public function getFirstLetter( $string ) { |
83 | // @phan-suppress-previous-line PhanPluginNeverReturnMethod |
84 | throw new RuntimeException( __METHOD__ . ': not implemented' ); |
85 | } |
86 | |
87 | /** |
88 | * The remote entry point. Get sort keys for an encoded list of inputs. |
89 | * |
90 | * @param string $locale The ICU locale |
91 | * @param string $blob The input array encoded with encode() |
92 | * @return string The encoded result |
93 | */ |
94 | public static function doGetSortKeys( $locale, $blob ) { |
95 | $mainCollator = Collator::create( $locale ); |
96 | if ( !$mainCollator ) { |
97 | throw new RuntimeException( "Invalid ICU locale specified for collation: $locale" ); |
98 | } |
99 | |
100 | // If the special suffix for numeric collation is present, turn on numeric collation. |
101 | if ( str_ends_with( $locale, '-u-kn' ) ) { |
102 | $mainCollator->setAttribute( Collator::NUMERIC_COLLATION, Collator::ON ); |
103 | } |
104 | $ret = []; |
105 | foreach ( self::decode( $blob ) as $string ) { |
106 | $ret[] = $mainCollator->getSortKey( $string ); |
107 | } |
108 | return self::encode( $ret ); |
109 | } |
110 | } |