Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.83% covered (success)
95.83%
23 / 24
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Plural
95.83% covered (success)
95.83%
23 / 24
50.00% covered (danger)
50.00%
1 / 2
9
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
 process
95.65% covered (success)
95.65%
22 / 23
0.00% covered (danger)
0.00%
0 / 1
8
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace Wikimedia\Leximorph\Handler;
8
9use Wikimedia\Leximorph\Provider;
10
11/**
12 * Plural
13 *
14 * The Plural class selects the correct text form based on a numeric count and language-specific
15 * pluralization rules from the Unicode CLDR. It processes a number along with an array of text options,
16 * returning the appropriately pluralized text.
17 *
18 * Usage Example:
19 * <code>
20 *            echo $plural->process( 3, [ 'article', 'articles' ] );
21 * </code>
22 *
23 * @since     1.45
24 * @author    Doğu Abaris (abaris@null.net)
25 * @license   https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
26 */
27class Plural {
28
29    /**
30     * Initializes the Plural handler with the given language code and provider.
31     *
32     * @param Provider $provider The provider instance to use.
33     *
34     * @since 1.45
35     */
36    public function __construct(
37        private readonly Provider $provider,
38    ) {
39    }
40
41    /**
42     * Selects and returns the pluralized text form based on a numeric count.
43     *
44     * This method evaluates the provided numeric count using language-specific pluralization rules
45     * derived from the Unicode CLDR. It then selects the appropriate text form from the provided
46     * array of alternatives, taking into account any explicit plural forms if specified.
47     *
48     * @param float $count The numeric count to evaluate.
49     * @param string[] $forms An array of text forms for pluralization.
50     *
51     * @since 1.45
52     * @return string The pluralized text corresponding to the count.
53     */
54    public function process( float $count, array $forms ): string {
55        // For "explicit" forms such as "0=No items", "1=One item", or "other=Items"
56        // we’ll store them in an associative array if we parse them that way.
57        $explicitForms = [];
58
59        // For "default" (non-explicit) forms such as [ 'item', 'items' ],
60        // we store them in a sequential array with integer keys.
61        $defaultForms = [];
62
63        // Separate explicit forms ("n=text") from default forms
64        foreach ( $forms as $form ) {
65            if ( str_contains( $form, '=' ) ) {
66                [
67                    $key,
68                    $text,
69                ] = explode( '=', $form, 2 );
70                // If key is purely numeric AND matches $count, return immediately:
71                if ( is_numeric( $key ) && (float)$key === $count ) {
72                    return $text;
73                }
74                // Otherwise, treat it as an explicit string key
75                $explicitForms[$key] = $text;
76            } else {
77                // Default form
78                $defaultForms[] = $form;
79            }
80        }
81
82        // Figure out the plural category: "one", "few", "other", etc.
83        $pluralType = $this->provider->getPluralProvider()->getPluralRuleType( $count );
84
85        // If we have an explicit form matching $pluralType` as a key, use it:
86        // e.g., "one" => "Item", "other" => "Items"
87        if ( array_key_exists( $pluralType, $explicitForms ) ) {
88            return $explicitForms[$pluralType];
89        }
90
91        // Otherwise, fallback to the default forms (sequential)
92        // If we find a default that exactly matches $pluralType as a string, use that:
93        $foundKey = array_search( $pluralType, $defaultForms, true );
94        if ( $foundKey !== false ) {
95            return $defaultForms[$foundKey];
96        }
97
98        // Else, use the numeric index from the language’s plural rules
99        // (e.g. 0 => singular form, 1 => plural form, etc.)
100        if ( count( $defaultForms ) > 0 ) {
101            $index = $this->provider->getPluralProvider()->getPluralRuleIndexNumber( $count );
102            // Guard in case $index is out of range
103            $index = min( $index, count( $defaultForms ) - 1 );
104
105            return $defaultForms[$index];
106        }
107
108        // If no forms were provided at all, just return an empty string
109        return '';
110    }
111}