Passed
Push — master ( 4d0b4e...5aa6fa )
by Johnny
02:02
created

Alternation::parse()   C

Complexity

Conditions 12
Paths 4

Size

Total Lines 79
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 35
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 79
rs 6.9666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * This file is part of Rivescript-php
4
 *
5
 * (c) Johnny Mast <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Axiom\Rivescript\Cortex\Triggers;
12
13
use Axiom\Rivescript\Cortex\Input;
14
use Axiom\Rivescript\Traits\Regex;
15
16
/**
17
 * Alternation class
18
 *
19
 * The Alternation class determines if a provided trigger
20
 * is an Alternation.
21
 *
22
 * PHP version 7.4 and higher.
23
 *
24
 * @category Core
25
 * @package  Cortext\Triggers
26
 * @author   Johnny Mast <[email protected]>
27
 * @license  https://opensource.org/licenses/MIT MIT
28
 * @link     https://github.com/axiom-labs/rivescript-php
29
 * @since    0.4.0
30
 */
31
class Alternation extends Trigger
32
{
33
    use Regex;
34
35
    /**
36
     * @var array|string[]
37
     */
38
    protected array $signatures = [
39
        'alternation' => "__\x01\x20__",
40
        'optional' => "__\x01\x21__",
41
    ];
42
43
    /**
44
     * The Regex pattern to find sets
45
     * in the trigger.
46
     *
47
     * Note: This pattern ignores the set if a @ character
48
     * is inside to make sure we don't confuse them with arrays.
49
     *
50
     * @var string
51
     */
52
//    protected string $pattern = "/(\()(?!\@)(.+?=*)(\))/ui"; // oud alleen alternation
53
//    protected string $pattern = "/((\(|\[)(?!\@)(.+?=*)(\)|]))/ui"; // goed
54
    /**
55
     * @var string
56
     */
57
    protected string $pattern = "/(\(|\[)(?!@)(.+?=*)(\)|\])/iu"; // goed
58
59
    /**
60
     * Parse the trigger.
61
     *
62
     * @return bool|string
63
     */
64
    public function parse(string $trigger, Input $input): bool
65
    {
66
        if ($this->matchesPattern($this->pattern, $trigger) === true) {
67
            $triggerString = $trigger;
68
            $matches = $this->getMatchesFromPattern($this->pattern, $triggerString);
69
            $sets = [];
70
71
            /**
72
             * Replace every "set" in the trigger to their index number
73
             * found in the string.
74
             *
75
             * Example:
76
             *
77
             * "I (am|love) a robot. I like (my|style)"
78
             *
79
             * Will be replaced with:
80
             *
81
             * "I {0} a robot. I like {1}"
82
             */
83
84
            foreach ($matches as $index => $match) {
0 ignored issues
show
Bug introduced by
The expression $matches of type false is not traversable.
Loading history...
85
                $set = explode("|", $match[2]);
86
87
                if (count($set) > 0) {
88
                    if ($match[1] === '(') {
89
                        foreach ($set as $setIndex => $item) {
90
                            $set[$setIndex] = $this->signatures['alternation'] . $item;
91
                        }
92
                        $triggerString = str_replace($match[0], "{{$index}}", $triggerString);
93
                        $sets [] = $set;
94
                    }
95
96
                    /**
97
                     * We need to add possible optionals to the
98
                     * set as well. This programming is a bit odd
99
                     * but there is no way around it.
100
                     */
101
                    if ($match[1] === '[') {
102
                        $set[] = $this->signatures['optional']; // "__\x01\x20__";
103
                        $triggerString = str_replace($match[0], "{{$index}}", $triggerString);
104
                        $sets [] = $set;
105
                    }
106
                }
107
            }
108
109
110
            $combinations = $this->getCombinations(...$sets);
111
112
            if (count($combinations) > 0) {
113
                $sentences = [];
114
115
                foreach ($combinations as $combination) {
116
                    $tmp = $triggerString;
117
                    foreach ($combination as $index => $string) {
118
                        $tmp = str_replace("{{$index}}", $string, $tmp);
119
                    }
120
121
                    $tmp = str_replace([$this->signatures['optional'] . " ", $this->signatures['optional']], "", $tmp);
122
                    $tmp = trim($tmp);
123
124
                    $sentences [] = $tmp;
125
                }
126
127
                $signature = $this->signatures['alternation'];
128
                $cmp = [$this, 'isMatchesWithoutSignature'];
129
130
                $result = array_filter($sentences, static function (string $sentence) use ($input, $signature, $cmp) {
131
                    if (strpos($sentence, $signature) > -1) {
132
                        return $cmp(strtolower($sentence), strtolower($input->source()));
133
                    }
134
                    return false;
135
                });
136
137
                if (count($result) > 0) {
138
                    return $input->source();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $input->source() returns the type string which is incompatible with the type-hinted return boolean.
Loading history...
139
                }
140
            }
141
        }
142
        return false;
143
    }
144
145
    /**
146
     * Find out if 2 string match exactly if the alternation signature
147
     * was removed.
148
     *
149
     * @param string $withSignature    The string with signatures before alternatives.
150
     * @param string $withoutSignature The string without signatures.
151
     *
152
     * @return bool
153
     */
154
    private function isMatchesWithoutSignature(string $withSignature, string $withoutSignature): bool
155
    {
156
        $with = explode(" ", $withSignature);
157
        $without = explode(" ", $withoutSignature);
158
159
        $max = count($with);
160
        for ($i = 0; $i < $max; $i++) {
161
            if (isset($without[$i]) === false) {
162
                return false;
163
            }
164
165
            $strWith = str_replace($this->signatures['alternation'], "", $with[$i]);
166
            $strWithout = $without[$i];
167
168
            if ($strWith !== $strWithout) {
169
                return false;
170
            }
171
        }
172
173
        return true;
174
    }
175
176
    /**
177
     * Create a set of possible combinations for given arrays.
178
     *
179
     * Note: This function is taken from stackoverflow.com
180
     * first posted by Guilhermo Luna and later edited by user Amlette.
181
     *
182
     * @see https://stackoverflow.com/questions/8567082/how-to-generate-in-php-all-combinations-of-items-in-multiple-arrays/33259643#33259643
183
     *
184
     * @param array ...$arrays A set of arrays to combine.
185
     *
186
     * @return array|array[]
187
     *
188
     */
189
    private function getCombinations(array ...$arrays): array
190
    {
191
        $result = [[]];
192
        foreach ($arrays as $property => $property_values) {
193
            $tmp = [];
194
            foreach ($result as $result_item) {
195
                foreach ($property_values as $property_value) {
196
                    $tmp[] = array_merge($result_item, [$property => $property_value]);
197
                }
198
            }
199
            $result = $tmp;
200
        }
201
        return $result;
202
    }
203
}
204