Completed
Push — master ( c6ec0f...bab290 )
by Magnar Ovedal
03:36
created

Pspell::getConvertedWords()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 1
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stadly\PasswordPolice\WordList;
6
7
use ErrorException;
8
use InvalidArgumentException;
9
use RuntimeException;
10
use Traversable;
11
use Stadly\PasswordPolice\WordConverter\WordConverterInterface;
12
13
final class Pspell implements WordListInterface
14
{
15
    /**
16
     * @var int Pspell dictionary.
17
     */
18
    private $pspell;
19
20
    /**
21
     * @var WordConverterInterface[] Word converters.
22
     */
23
    private $wordConverters;
24
25
    /**
26
     * Pspell dictionaries are case-sensitive.
27
     * Specify word converters if tests should also be performed for the word converted to other cases.
28
     *
29
     * @param int $pspell Pspell dictionary link, as generated by `pspell_new` and friends.
30
     * @param WordConverterInterface... $wordConverters Word converters.
0 ignored issues
show
Documentation Bug introduced by
The doc comment WordConverterInterface... at position 0 could not be parsed: Unknown type name 'WordConverterInterface...' at position 0 in WordConverterInterface....
Loading history...
31
     */
32 2
    public function __construct(int $pspell, WordConverterInterface... $wordConverters)
33
    {
34 2
        $this->pspell = $pspell;
35 2
        $this->wordConverters = $wordConverters;
36 2
    }
37
38
    /**
39
     * Pspell dictionaries are case-sensitive.
40
     * Specify word converters if tests should also be performed for the word converted to other cases.
41
     *
42
     * @param string $locale Locale of the pspell dictionary to load. For example `en-US` or `de`.
43
     * @param WordConverterInterface... $wordConverters Word converters.
0 ignored issues
show
Documentation Bug introduced by
The doc comment WordConverterInterface... at position 0 could not be parsed: Unknown type name 'WordConverterInterface...' at position 0 in WordConverterInterface....
Loading history...
44
     * @throws RuntimeException If the pspell dictionary could not be loaded.
45
     * @return self Pspell word list.
46
     */
47 4
    public static function fromLocale(string $locale, WordConverterInterface... $wordConverters): self
48
    {
49 4
        if (preg_match('{^[a-z]{2}(?:[-_][A-Z]{2})?$}', $locale) !== 1) {
50 2
            throw new InvalidArgumentException(sprintf('%s is not a valid locale.', $locale));
51
        }
52
53 2
        set_error_handler([self::class, 'errorHandler']);
54
        try {
55 2
            $pspell = pspell_new($locale);
56 1
        } catch (ErrorException $exception) {
57 1
            throw new RuntimeException('An error occurred while loading the word list.', /*code*/0, $exception);
58 1
        } finally {
59 2
            restore_error_handler();
60
        }
61
62 1
        assert($pspell !== false);
63
64 1
        return new self($pspell, ...$wordConverters);
65
    }
66
67
    /**
68
     * {@inheritDoc}
69
     */
70 7
    public function contains(string $word): bool
71
    {
72 7
        foreach ($this->getWordsToCheck($word) as $wordVariant) {
73 7
            set_error_handler([self::class, 'errorHandler']);
74
            try {
75 7
                $check = pspell_check($this->pspell, $wordVariant);
76 2
            } catch (ErrorException $exception) {
77 2
                throw new RuntimeException('An error occurred while using the word list.', /*code*/0, $exception);
78 5
            } finally {
79 7
                restore_error_handler();
80
            }
81
82 5
            if ($check) {
83 5
                return true;
84
            }
85
        }
86
87 3
        return false;
88
    }
89
90
    /**
91
     * @param string $word Word to check.
92
     * @return Traversable<string> Variants of the word to check.
93
     */
94 7
    private function getWordsToCheck($word): Traversable
95
    {
96 7
        $checked = [];
97 7
        foreach ($this->getConvertedWords($word) as $wordToCheck) {
98 7
            if (isset($checked[$wordToCheck])) {
99 2
                continue;
100
            }
101
102 7
            $checked[$wordToCheck] = true;
103 7
            yield $wordToCheck;
104
        }
105 3
    }
106
107
    /**
108
     * @param string $word Word to convert.
109
     * @return Traversable<string> Converted words. May contain duplicates.
110
     */
111 7
    private function getConvertedWords(string $word): Traversable
112
    {
113 7
        yield $word;
114
115 3
        foreach ($this->wordConverters as $wordConverter) {
116 2
            foreach ($wordConverter->convert($word) as $converted) {
117 2
                yield $converted;
118
            }
119
        }
120 3
    }
121
122
    /**
123
     * @throws ErrorException Error converted to an exception.
124
     */
125 4
    private static function errorHandler(int $severity, string $message, string $filename, int $line): void
126
    {
127 4
        throw new ErrorException($message, /*code*/0, $severity, $filename, $line);
128
    }
129
}
130