Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.45% covered (success)
95.45%
42 / 44
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemoteIcuCollation
95.45% covered (success)
95.45%
42 / 44
71.43% covered (warning)
71.43%
5 / 7
13
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getSortKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 encode
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 decode
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getSortKeys
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
 getFirstLetter
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 doGetSortKeys
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
1<?php
2
3use MediaWiki\Shell\ShellboxClientFactory;
4use 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 */
11class 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}