Passed
Push — master ( d8dc61...dfdb64 )
by Sebastian
09:05
created

ContainsStatementBuilder::renderRegularCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 6
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Mailcode\Translator\Syntax\ApacheVelocity\Contains;
6
7
use Mailcode\Mailcode_Exception;
8
use Mailcode\Mailcode_Parser_Statement_Tokenizer_Token_StringLiteral;
9
use Mailcode\Mailcode_Variables_Variable;
10
use Mailcode\Translator\Syntax\ApacheVelocity;
11
use function Mailcode\dollarize;
12
use function Mailcode\undollarize;
13
14
class ContainsStatementBuilder
15
{
16
    public const ERROR_INVALID_LIST_VARIABLE_NAME = 76701;
17
18
    private Mailcode_Variables_Variable $variable;
19
    private bool $caseSensitive;
20
    private string $containsType;
21
    private ApacheVelocity $translator;
22
    private bool $regexEnabled;
23
24
    /**
25
     * @var Mailcode_Parser_Statement_Tokenizer_Token_StringLiteral[]
26
     */
27
    private array $searchTerms;
28
29
    /**
30
     * Mailcode_Translator_Syntax_ApacheVelocity_Contains_StatementBuilder constructor.
31
     * @param ApacheVelocity $translator
32
     * @param Mailcode_Variables_Variable $variable
33
     * @param bool $caseSensitive
34
     * @param bool $regexEnabled
35
     * @param Mailcode_Parser_Statement_Tokenizer_Token_StringLiteral[] $searchTerms
36
     * @param string $containsType
37
     */
38
    public function __construct(ApacheVelocity $translator, Mailcode_Variables_Variable $variable, bool $caseSensitive, bool $regexEnabled, array $searchTerms, string $containsType)
39
    {
40
        $this->translator = $translator;
41
        $this->variable = $variable;
42
        $this->caseSensitive = $caseSensitive;
43
        $this->searchTerms = $searchTerms;
44
        $this->containsType = $containsType;
45
        $this->regexEnabled = $regexEnabled;
46
    }
47
48
    /**
49
     * Is this a not contains command? (list or regular)
50
     * @return bool
51
     */
52
    public function isNotContains() : bool
53
    {
54
        return strpos($this->containsType, 'not-contains') !== false;
55
    }
56
57
    /**
58
     * Is this a contains command to be used on a list variable?
59
     * @return bool
60
     */
61
    public function isList() : bool
62
    {
63
        return strpos($this->containsType, 'list-') !== false;
64
    }
65
66
    /**
67
     * Gets the sign to prepend the command with, i.e.,
68
     * whether to add the negation "!" or not, depending
69
     * on the type of command.
70
     *
71
     * @return string
72
     */
73
    public function getSign() : string
74
    {
75
        if($this->isNotContains())
76
        {
77
            return '!';
78
        }
79
80
        return '';
81
    }
82
83
    /**
84
     * Gets the logical connector sign to combine several search
85
     * terms with, i.e. "&&" or "||" depending on whether it is
86
     * a regular "contains" or "not contains".
87
     *
88
     * @return string
89
     */
90
    public function getConnector() : string
91
    {
92
        if($this->isNotContains())
93
        {
94
            return '&&';
95
        }
96
97
        return '||';
98
    }
99
100
    /**
101
     * @return string
102
     * @throws Mailcode_Exception
103
     */
104
    public function render() : string
105
    {
106
        $parts = array();
107
108
        foreach($this->searchTerms as $token)
109
        {
110
            $parts[] = $this->renderCommand($token);
111
        }
112
113
        return implode(' '.$this->getConnector().' ', $parts);
114
    }
115
116
    /**
117
     * @param Mailcode_Parser_Statement_Tokenizer_Token_StringLiteral $searchTerm
118
     * @return string
119
     * @throws Mailcode_Exception
120
     */
121
    private function renderCommand(Mailcode_Parser_Statement_Tokenizer_Token_StringLiteral $searchTerm) : string
122
    {
123
        if($this->isList())
124
        {
125
            $command = $this->renderListCommand($searchTerm);
126
        }
127
        else
128
        {
129
            $command = $this->renderRegularCommand($searchTerm);
130
        }
131
132
        return $this->getSign().$command;
133
    }
134
135
    private function renderRegularCommand(Mailcode_Parser_Statement_Tokenizer_Token_StringLiteral $searchTerm) : string
136
    {
137
        return sprintf(
138
            '%s.matches(%s)',
139
            $this->variable->getFullName(),
140
            $this->renderRegex($searchTerm)
141
        );
142
    }
143
144
    /**
145
     * @param Mailcode_Parser_Statement_Tokenizer_Token_StringLiteral $searchTerm
146
     * @return string
147
     * @throws Mailcode_Exception
148
     */
149
    private function renderListCommand(Mailcode_Parser_Statement_Tokenizer_Token_StringLiteral $searchTerm) : string
150
    {
151
        $name = $this->parseVarName();
152
153
        return sprintf(
154
            '$map.hasElement(%s.list(), "%s", %s)',
155
            dollarize($name['path']),
156
            $name['name'],
157
            $this->renderRegex($searchTerm)
158
        );
159
    }
160
161
    private function renderRegex(Mailcode_Parser_Statement_Tokenizer_Token_StringLiteral $searchTerm) : string
162
    {
163
        $opts = 's';
164
        if($this->caseSensitive)
165
        {
166
            $opts = 'is';
167
        }
168
169
        $filtered = trim($searchTerm->getNormalized(), '"');
170
171
        if(!$this->regexEnabled)
172
        {
173
            $filtered = $this->translator->filterRegexString($filtered);
174
            $filtered = $this->addWildcards($filtered);
175
        }
176
177
        return sprintf(
178
            '"(?%s)%s"',
179
            $opts,
180
            $filtered
181
        );
182
    }
183
184
    /**
185
     * Adds the search wildcard before or after the search string
186
     * for the `list-begins-with` and `list-ends-with` command
187
     * flavors, or on both ends for the standard command.
188
     *
189
     * @param string $searchTerm
190
     * @return string
191
     */
192
    private function addWildcards(string $searchTerm) : string
193
    {
194
        if($this->containsType === 'list-begins-with')
195
        {
196
            return $searchTerm.'.*';
197
        }
198
199
        if($this->containsType === 'list-ends-with')
200
        {
201
            return '.*'.$searchTerm;
202
        }
203
204
        if($this->containsType === 'list-equals')
205
        {
206
            return '\A'.$searchTerm.'\Z';
207
        }
208
209
        return '.*'.$searchTerm.'.*';
210
    }
211
212
    /**
213
     * @return array<string,string>
214
     * @throws Mailcode_Exception
215
     */
216
     private function parseVarName() : array
217
     {
218
         $tokens = explode('.', undollarize($this->variable->getFullName()));
219
220
         if(count($tokens) === 2)
221
         {
222
             return array(
223
                 'path' => $tokens[0],
224
                 'name' => $tokens[1]
225
             );
226
         }
227
228
         throw new Mailcode_Exception(
229
             'Invalid variable name for a list property.',
230
             sprintf(
231
                 'Exactly 2 parts are required, variable [%s] has [%s].',
232
                 $this->variable->getFullName(),
233
                 count($tokens)
234
             ),
235
             self::ERROR_INVALID_LIST_VARIABLE_NAME
236
         );
237
     }
238
}
239