Passed
Push — master ( 9c5f94...c5c65d )
by Sebastian
03:20
created

detectParamsKeyword()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 2
nop 3
dl 0
loc 22
rs 9.8666
c 0
b 0
f 0
1
<?php
2
/**
3
 * File containing the {@see Mailcode_Commands_Command} class.
4
 *
5
 * @package Mailcode
6
 * @subpackage Commands
7
 * @see Mailcode_Commands_Command
8
 */
9
10
declare(strict_types=1);
11
12
namespace Mailcode;
13
14
use AppUtils\ConvertHelper;
15
use AppUtils\OperationResult;
16
17
/**
18
 * Handles parsing logic keywords in commands, if any.
19
 *
20
 * @package Mailcode
21
 * @subpackage Commands
22
 * @author Sebastian Mordziol <[email protected]>
23
 * 
24
 * @property Mailcode_Commands_Command $subject
25
 */
26
class Mailcode_Commands_LogicKeywords extends OperationResult
27
{
28
    const ERROR_CANNOT_APPEND_INVALID_KEYWORD = 60501;
29
    const ERROR_KEYWORD_MATCHED_STRING_NOT_FOUND = 60502;
30
    
31
    const VALIDATION_CANNOT_MIX_LOGIC_KEYWORDS = 60701;
32
    const VALIDATION_INVALID_SUB_COMMAND = 60702;
33
    
34
   /**
35
    * @var string
36
    */
37
    private $paramsString;
38
    
39
   /**
40
    * @var string[]
41
    */
42
    private $names = array(
43
        'and', 
44
        'or'
45
    );
46
    
47
   /**
48
    * @var Mailcode_Commands_LogicKeywords_Keyword[]
49
    */
50
    private $keywords = array();
51
    
52
   /**
53
    * @var string
54
    */
55
    private $mainParams = '';
56
    
57
    public function __construct(Mailcode_Commands_Command $command, string $paramsString)
58
    {
59
        parent::__construct($command);
60
        
61
        $this->paramsString = $paramsString;
62
        
63
        $this->parse();
64
        $this->validate();
65
    }
66
    
67
    public function getCommand() : Mailcode_Commands_Command
68
    {
69
        return $this->subject;
70
    }
71
    
72
    private function parse() : void
73
    {
74
        foreach($this->names as $name)
75
        {
76
            if(!stristr($this->paramsString, $name))
77
            {
78
                continue;
79
            }
80
            
81
            $this->keywords = array_merge(
82
                $this->keywords, 
83
                $this->detectKeywords($name)
84
            );
85
        }
86
    }
87
    
88
    private function validate() : void
89
    {
90
        $names = $this->getDetectedNames();
91
        $amount = count($names);
92
        
93
        if($amount > 1)
94
        {
95
            $this->makeError(
96
                t(
97
                    'Cannot mix the logical keywords %1$s:',
98
                    ConvertHelper::implodeWithAnd($names, ', ', ' '.t('and').' ')
99
                ).' '.
100
                t('Only one keyword may be used within the same command.'),
101
                self::VALIDATION_CANNOT_MIX_LOGIC_KEYWORDS
102
            );
103
            
104
            return;
105
        }
106
        
107
        $this->splitParams();
108
    }
109
    
110
    private function splitParams() : void
111
    {
112
        if(empty($this->keywords))
113
        {
114
            $this->mainParams = $this->paramsString;
115
            
116
            return;
117
        }
118
        
119
        $params = $this->detectParameters();
120
        
121
        foreach($this->keywords as $keyword)
122
        {
123
            $kParams = array_shift($params);
124
            
125
            $keyword->setParamsString($kParams);
126
            
127
            if(!$keyword->isValid())
128
            {
129
                $this->makeError(
130
                    t('Error #%1$s:', $keyword->getCode()).' '.$keyword->getErrorMessage(),
131
                    self::VALIDATION_INVALID_SUB_COMMAND
132
                );
133
                
134
                return;
135
            }
136
        }
137
    }
138
    
139
   /**
140
    * @return string[]
141
    */
142
    private function detectParameters() : array
143
    {
144
        $params = $this->paramsString;
145
        $stack = array();
146
        
147
        foreach($this->keywords as $keyword)
148
        {
149
            $params = $this->detectParamsKeyword($params, $keyword, $stack);
150
        }
151
        
152
        $stack[] = $params;
153
        
154
        $this->mainParams = array_shift($stack);
155
        
156
        return $stack;
157
    }
158
159
   /**
160
    * @param string $params
161
    * @param Mailcode_Commands_LogicKeywords_Keyword $keyword
162
    * @param string[] $stack
163
    * @throws Mailcode_Exception
164
    * @return string
165
    */
166
    private function detectParamsKeyword(string $params, Mailcode_Commands_LogicKeywords_Keyword $keyword, array &$stack) : string
167
    {
168
        $search = $keyword->getMatchedString();
169
        $pos = strpos($params, $search, 0);
170
        
171
        if($pos === false)
172
        {
173
            throw new Mailcode_Exception(
174
                'Keyword matched string not found',
175
                null,
176
                self::ERROR_KEYWORD_MATCHED_STRING_NOT_FOUND
177
            );
178
        }
179
        
180
        $length = strlen($search);
181
        
182
        $store = substr($params, 0, $pos);
183
        $params = trim(substr($params, $pos+$length));
184
        
185
        $stack[] = $store;
186
        
187
        return $params;
188
    }
189
    
190
   /**
191
    * Extracts the parameters string to use for the 
192
    * original command itself, omitting all the logic
193
    * keywords for the sub-commands.
194
    * 
195
    * @return string
196
    */
197
    public function getMainParamsString() : string
198
    {
199
        return $this->mainParams;
200
    }
201
    
202
   /**
203
    * Retrieves the detected keyword names.
204
    * @return string[]
205
    */
206
    public function getDetectedNames() : array
207
    {
208
        $names = array();
209
        
210
        foreach($this->keywords as $keyword)
211
        {
212
            $name = $keyword->getName();
213
            
214
            if(!in_array($name, $names))
215
            {
216
                $names[] = $name;
217
            }
218
        }
219
        
220
        return $names;
221
    }
222
    
223
   /**
224
    * Retrieves all keywords that were detected in the
225
    * command's parameters string, if any.
226
    * 
227
    * @return Mailcode_Commands_LogicKeywords_Keyword[]
228
    */
229
    public function getKeywords() : array
230
    {
231
        return $this->keywords;
232
    }
233
    
234
   /**
235
    * Detects any keyword statements in the parameters by keyword name.
236
    * 
237
    * @param string $name
238
    * @return Mailcode_Commands_LogicKeywords_Keyword[]
239
    */
240
    private function detectKeywords(string $name) : array
241
    {
242
        $regex = sprintf('/%1$s\s+([a-z\-0-9]+):|%1$s:/x', $name);
243
        
244
        $matches = array();
245
        preg_match_all($regex, $this->paramsString, $matches, PREG_PATTERN_ORDER);
246
        
247
        if(!isset($matches[0][0]) || empty($matches[0][0]))
248
        {
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