Passed
Push — master ( f5ce10...46cb27 )
by Philippe
01:30
created

PHPPspell::checkBefore81()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 16
nc 4
nop 3
dl 0
loc 27
rs 9.7333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpSpellcheck\Spellchecker;
6
7
use PhpSpellcheck\Exception\RuntimeException;
8
use PhpSpellcheck\Misspelling;
9
use PhpSpellcheck\MisspellingInterface;
10
use Webmozart\Assert\Assert;
11
12
class PHPPspell implements SpellcheckerInterface
13
{
14
    /**
15
     * @var int
16
     */
17
    private $mode;
18
19
    /**
20
     * @var int
21
     */
22
    private $numberOfCharactersLowerLimit;
23
24
    /**
25
     * @var Aspell
26
     */
27
    private $aspell;
28
29
    /**
30
     * @see http://php.net/manual/en/function.pspell-config-mode.php
31
     * @see http://php.net/manual/en/function.pspell-config-ignore.php
32 3
     *
33
     * @param int|null $mode the mode parameter is the mode in which the spellchecker will work
34 3
     * @param int $numberOfCharactersLowerLimit Words less than n characters will be skipped
35
     * @param Aspell|null $aspell Aspell spellchecker that pspell extension is using underneath. Used to help retrieve supported languages
36
     */
37
    public function __construct(
38 3
        ?int $mode = null,
39
        int $numberOfCharactersLowerLimit = 0,
40 3
        Aspell $aspell = null
41 3
    ) {
42 3
        if (!\extension_loaded('pspell')) {
43
            throw new RuntimeException('Pspell extension must be loaded to use the PHPPspell spellchecker');
44
        }
45
46
        if ($mode === null) {
47 2
            $mode = PSPELL_FAST;
48
        }
49
50
        Assert::greaterThanEq($numberOfCharactersLowerLimit, 0);
51
52
        $this->mode = $mode;
53 2
        $this->numberOfCharactersLowerLimit = $numberOfCharactersLowerLimit;
54 2
        $this->aspell = $aspell ?? Aspell::create();
55
    }
56 1
57 1
    /**
58 1
     * {@inheritdoc}
59 1
     */
60
    public function check(
61 1
        string $text,
62 1
        array $languages,
63 1
        array $context
64 1
    ): iterable {
65 1
        if (PHP_VERSION_ID < 80100) {
66 1
            return $this->checkBefore81($text, $languages, $context);
67
        }
68
69
        return $this->checkAfter81($text, $languages, $context);
70 1
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 1
    public function getSupportedLanguages(): iterable
76
    {
77 1
        return $this->aspell->getSupportedLanguages();
78
    }
79
80
    /**
81
     * @param array<mixed> $context
82
     * @param array<string> $languages
83
     *
84
     * @return iterable<MisspellingInterface>
85
     */
86
    private function checkBefore81(
87
        string $text,
88
        array $languages,
89
        array $context
90
    ): iterable {
91
        Assert::count($languages, 1, 'PHPPspell spellchecker doesn\'t support multi-language check');
92
93
        $chosenLanguage = current($languages);
94
        $pspellConfig = \Safe\pspell_config_create(current($languages));
95
        \Safe\pspell_config_mode($pspellConfig, $this->mode);
96
        \Safe\pspell_config_ignore($pspellConfig, $this->numberOfCharactersLowerLimit);
97
        $dictionary = \Safe\pspell_new_config($pspellConfig);
98
99
        $lines = explode(PHP_EOL, $text);
100
101
        /** @var string $line */
102
        foreach ($lines as $lineNumber => $line) {
103
            $words = explode(' ', \Safe\preg_replace("/(?!['’-])(\p{P}|\+|--)/u", '', $line));
104
            foreach ($words as $word) {
105
                if (!pspell_check($dictionary, $word)) {
106
                    $suggestions = pspell_suggest($dictionary, $word);
107
                    Assert::isArray(
108
                        $suggestions,
109
                        \Safe\sprintf('pspell_suggest method failed with language "%s" and word "%s"', $chosenLanguage, $word)
110
                    );
111
112
                    yield new Misspelling($word, 0, $lineNumber + 1, $suggestions, $context);
113
                }
114
            }
115
        }
116
    }
117
118
    /**
119
     * @param array<mixed> $context
120
     * @param array<string> $languages
121
     *
122
     * @return iterable<MisspellingInterface>
123
     */
124
    private function checkAfter81(
125
        string $text,
126
        array $languages,
127
        array $context
128
    ): iterable {
129
        Assert::count($languages, 1, 'PHPPspell spellchecker doesn\'t support multi-language check');
130
131
        $chosenLanguage = current($languages);
132
        $pspellConfig = pspell_config_create($chosenLanguage);
133
        pspell_config_mode($pspellConfig, $this->mode);
134
        pspell_config_ignore($pspellConfig, $this->numberOfCharactersLowerLimit);
135
        $dictionary = pspell_new_config($pspellConfig);
136
137
        $lines = explode(PHP_EOL, $text);
138
139
        /** @var string $line */
140
        foreach ($lines as $lineNumber => $line) {
141
            $words = explode(' ', \Safe\preg_replace("/(?!['’-])(\p{P}|\+|--)/u", '', $line));
142
            foreach ($words as $word) {
143
                if (!pspell_check($dictionary, $word)) {
144
                    $suggestions = pspell_suggest($dictionary, $word);
145
                    Assert::isArray(
146
                        $suggestions,
147
                        \Safe\sprintf('pspell_suggest method failed with language "%s" and word "%s"', $chosenLanguage, $word)
148
                    );
149
150
                    yield new Misspelling($word, 0, $lineNumber + 1, $suggestions, $context);
151
                }
152
            }
153
        }
154
    }
155
}
156