getMainParamsString()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package Mailcode
4
 * @subpackage Commands
5
 */
6
7
declare(strict_types=1);
8
9
namespace Mailcode;
10
11
use AppUtils\ConvertHelper;
12
use AppUtils\OperationResult;
13
14
/**
15
 * Handles parsing logic keywords in commands, if any.
16
 *
17
 * @package Mailcode
18
 * @subpackage Commands
19
 * @author Sebastian Mordziol <[email protected]>
20
 * 
21
 * @property Mailcode_Commands_Command $subject
22
 */
23
class Mailcode_Commands_LogicKeywords extends OperationResult
24
{
25
    public const ERROR_CANNOT_APPEND_INVALID_KEYWORD = 60501;
26
    public const ERROR_KEYWORD_MATCHED_STRING_NOT_FOUND = 60502;
27
    
28
    public const VALIDATION_CANNOT_MIX_LOGIC_KEYWORDS = 60701;
29
    public const VALIDATION_INVALID_SUB_COMMAND = 60702;
30
    public const ERROR_KEYWORD_SUBSTRING_NOT_FOUND = 60503;
31
32
    private string $paramsString;
33
    
34
   /**
35
    * @var string[]
36
    */
37
    private array $names = array(
38
        'and', 
39
        'or'
40
    );
41
    
42
   /**
43
    * @var Mailcode_Commands_LogicKeywords_Keyword[]
44
    */
45
    private array $keywords = array();
46
    
47
    private string $mainParams = '';
48
    
49
    public function __construct(Mailcode_Commands_Command $command, string $paramsString)
50
    {
51
        parent::__construct($command);
52
        
53
        $this->paramsString = $paramsString;
54
        
55
        $this->parse();
56
        $this->validate();
57
    }
58
    
59
    public function getCommand() : Mailcode_Commands_Command
60
    {
61
        return $this->subject;
62
    }
63
    
64
    private function parse() : void
65
    {
66
        foreach($this->names as $name)
67
        {
68
            if(stripos($this->paramsString, $name) === false) {
69
                continue;
70
            }
71
72
            array_push(
73
                $this->keywords,
74
                ...$this->detectKeywords($name)
75
            );
76
        }
77
    }
78
    
79
    private function validate() : void
80
    {
81
        $names = $this->getDetectedNames();
82
        $amount = count($names);
83
        
84
        if($amount > 1)
85
        {
86
            $this->makeError(
87
                t(
88
                    'Cannot mix the logical keywords %1$s:',
89
                    ConvertHelper::implodeWithAnd($names, ', ', ' '.t('and').' ')
90
                ).' '.
91
                t('Only one flavor of keyword may be used within the same command.').' '.
92
                t('To illustrate, you may use the %1$s keyword several times, but not the %1$s keyword and %2$s keyword at the same time.', '"and"', '"or"'),
93
                self::VALIDATION_CANNOT_MIX_LOGIC_KEYWORDS
94
            );
95
            
96
            return;
97
        }
98
        
99
        $this->splitParams();
100
    }
101
    
102
    private function splitParams() : void
103
    {
104
        if(empty($this->keywords))
105
        {
106
            $this->mainParams = $this->paramsString;
107
            
108
            return;
109
        }
110
        
111
        $params = $this->detectParameters();
112
        
113
        foreach($this->keywords as $keyword)
114
        {
115
            $kParams = (string)array_shift($params);
116
            
117
            $keyword->setParamsString($kParams);
118
            
119
            if(!$keyword->isValid())
120
            {
121
                $this->makeError(
122
                    t('Error #%1$s:', $keyword->getCode()).' '.$keyword->getErrorMessage(),
123
                    self::VALIDATION_INVALID_SUB_COMMAND
124
                );
125
                
126
                return;
127
            }
128
        }
129
    }
130
    
131
   /**
132
    * @return string[]
133
    */
134
    private function detectParameters() : array
135
    {
136
        $params = $this->paramsString;
137
        $stack = array();
138
        
139
        foreach($this->keywords as $keyword)
140
        {
141
            $params = $this->detectParamsKeyword($params, $keyword, $stack);
142
        }
143
        
144
        $stack[] = $params;
145
        
146
        $this->mainParams = (string)array_shift($stack);
147
        
148
        return $stack;
149
    }
150
151
   /**
152
    * @param string $params
153
    * @param Mailcode_Commands_LogicKeywords_Keyword $keyword
154
    * @param string[] $stack
155
    * @throws Mailcode_Exception
156
    * @return string
157
    */
158
    private function detectParamsKeyword(string $params, Mailcode_Commands_LogicKeywords_Keyword $keyword, array &$stack) : string
159
    {
160
        $search = $keyword->getMatchedString();
161
        $pos = strpos($params, $search, 0);
162
        
163
        if($pos === false)
164
        {
165
            throw new Mailcode_Exception(
166
                'Keyword matched string not found',
167
                null,
168
                self::ERROR_KEYWORD_MATCHED_STRING_NOT_FOUND
169
            );
170
        }
171
        
172
        $length = strlen($search);
173
        
174
        $store = substr($params, 0, $pos);
175
176
        if($store === false)
177
        {
178
            throw new Mailcode_Exception(
179
                'Keyword substring not found',
180
                null,
181
                self::ERROR_KEYWORD_SUBSTRING_NOT_FOUND
182
            );
183
        }
184
185
        $params = trim(substr($params, $pos+$length));
186
        
187
        $stack[] = $store;
188
        
189
        return $params;
190
    }
191
    
192
   /**
193
    * Extracts the parameters string to use for the 
194
    * original command itself, omitting all the logic
195
    * keywords for the sub-commands.
196
    * 
197
    * @return string
198
    */
199
    public function getMainParamsString() : string
200
    {
201
        return $this->mainParams;
202
    }
203
    
204
   /**
205
    * Retrieves the detected keyword names.
206
    * @return string[]
207
    */
208
    public function getDetectedNames() : array
209
    {
210
        $names = array();
211
        
212
        foreach($this->keywords as $keyword)
213
        {
214
            $name = $keyword->getName();
215
            
216
            if(!in_array($name, $names, true)) {
217
                $names[] = $name;
218
            }
219
        }
220
        
221
        return $names;
222
    }
223
    
224
   /**
225
    * Retrieves all keywords detected in the
226
    * command's parameter string, if any.
227
    * 
228
    * @return Mailcode_Commands_LogicKeywords_Keyword[]
229
    */
230
    public function getKeywords() : array
231
    {
232
        return $this->keywords;
233
    }
234
    
235
   /**
236
    * Detects any keyword statements in the parameters by keyword name.
237
    * 
238
    * @param string $name
239
    * @return Mailcode_Commands_LogicKeywords_Keyword[]
240
    */
241
    private function detectKeywords(string $name) : array
242
    {
243
        $regex = sprintf('/%1$s\s+([a-z\-0-9]+):|%1$s:/x', $name);
244
        
245
        $matches = array();
246
        preg_match_all($regex, $this->paramsString, $matches, PREG_PATTERN_ORDER);
247
        
248
        if(empty($matches[0][0])) {
249
            return array();
250
        }
251
        
252
        $amount = count($matches[0]);
253
        $result = array();
254
        
255
        for($i=0; $i < $amount; $i++)
256
        {
257
            $result[] = $this->createKeyword(
258
                $name, 
259
                $matches[1][$i],
260
                $matches[0][$i] 
261
            );
262
        }
263
        
264
        return $result;
265
    }
266
    
267
    public function hasKeywords() : bool
268
    {
269
        return !empty($this->keywords);
270
    }
271
    
272
    public function appendAND(string $paramsString, string $type='') : Mailcode_Commands_LogicKeywords_Keyword
273
    {
274
        return $this->appendKeyword('and', $paramsString, $type);
275
    }
276
    
277
    public function appendOR(string $paramsString, string $type='') : Mailcode_Commands_LogicKeywords_Keyword
278
    {
279
        return $this->appendKeyword('or', $paramsString, $type);
280
    }
281
    
282
    public function appendKeyword(string $name, string $paramsString, string $type='') : Mailcode_Commands_LogicKeywords_Keyword
283
    {
284
        $keyword = $this->createKeyword($name, $type);
285
        $keyword->setParamsString($paramsString);
286
        
287
        if(!$keyword->isValid())
288
        {
289
            throw new Mailcode_Exception(
290
                'Cannot append invalid logic keyword',
291
                sprintf(
292
                    'The keyword [%s] cannot be added with parameters [%s] and type [%s]: it is invalid. Validation details: #%s %s',
293
                    $name,
294
                    $paramsString,
295
                    $type,
296
                    $keyword->getCode(),
297
                    $keyword->getErrorMessage()
298
                ),
299
                self::ERROR_CANNOT_APPEND_INVALID_KEYWORD
300
            );
301
        }
302
        
303
        $this->keywords[] = $keyword;
304
        
305
        return $keyword;
306
    }
307
    
308
    private function createKeyword(string $name, string $type='', string $matchedString='') : Mailcode_Commands_LogicKeywords_Keyword
309
    {
310
        if(empty($matchedString))
311
        {
312
            $matchedString = $name;
313
            
314
            if(!empty($type))
315
            {
316
                $matchedString .= ' '.$type;
317
            }
318
            
319
            $matchedString .= ':';
320
        }
321
        
322
        return new Mailcode_Commands_LogicKeywords_Keyword(
323
            $this,
324
            $name,
325
            $matchedString,
326
            $type
327
        );
328
    }
329
}
330