Test Failed
Push — master ( f51294...9c5f94 )
by Sebastian
03:08
created

Mailcode_Commands_Command   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 480
Duplicated Lines 0 %

Importance

Changes 9
Bugs 0 Features 2
Metric Value
eloc 153
dl 0
loc 480
rs 3.6
c 9
b 0
f 2
wmc 60

31 Methods

Rating   Name   Duplication   Size   Complexity  
A isDummy() 0 3 1
A setComment() 0 5 1
A getMatchedText() 0 3 1
A getType() 0 8 2
A getHash() 0 9 2
A getParams() 0 3 1
A getVariables() 0 3 1
A getCommandType() 0 29 5
A getHighlighted() 0 9 2
A __construct() 0 8 1
A getParamsString() 0 8 2
A hasParameters() 0 3 2
A validateSyntaxMethod() 0 20 2
A requireNonDummy() 0 11 2
A getNormalized() 0 5 1
A validate() 0 14 2
A getComment() 0 3 1
A getValidationResult() 0 11 2
A validateSyntax_params_keywords() 0 20 3
A isValid() 0 3 1
A getID() 0 6 1
A getLogicKeywords() 0 11 3
A validateSyntax_type_supported() 0 18 4
A validateSyntax_params_empty() 0 14 3
A validateSyntax_type_unsupported() 0 10 3
A validateSyntax() 0 10 3
A hasType() 0 3 2
A getSupportedTypes() 0 3 1
A init() 0 2 1
A __toString() 0 3 1
A validateSyntax_params_parse() 0 16 3

How to fix   Complexity   

Complex Class

Complex classes like Mailcode_Commands_Command often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Mailcode_Commands_Command, and based on these observations, apply Extract Interface, too.

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
/**
15
 * Base command class with the common functionality for all commands.
16
 *
17
 * @package Mailcode
18
 * @subpackage Commands
19
 * @author Sebastian Mordziol <[email protected]>
20
 */
21
abstract class Mailcode_Commands_Command
22
{
23
    const ERROR_NON_DUMMY_OPERATION = 46001;
24
    const ERROR_NO_VALIDATION_RESULT_AVAILABLE = 46002;
25
    const ERROR_MISSING_VALIDATION_METHOD = 46003;
26
    const ERROR_MISSING_TYPE_INTERFACE = 46004;
27
    const ERROR_LOGIC_COMMANDS_NOT_SUPPORTED = 46005;
28
    
29
    const VALIDATION_MISSING_PARAMETERS = 48301;
30
    const VALIDATION_ADDONS_NOT_SUPPORTED = 48302;
31
    const VALIDATION_ADDON_NOT_SUPPORTED = 48303;
32
    const VALIDATION_UNKNOWN_COMMAND_NAME = 48304;
33
    const VALIDATION_INVALID_PARAMS_STATEMENT = 48305;
34
35
   /**
36
    * @var string
37
    */
38
    protected $type = '';
39
40
   /**
41
    * @var string
42
    */
43
    protected $paramsString = '';
44
    
45
   /**
46
    * @var string
47
    */
48
    protected $matchedText = '';
49
50
   /**
51
    * @var string
52
    */
53
    protected $hash = '';
54
    
55
   /**
56
    * @var \AppUtils\OperationResult
57
    */
58
    protected $validationResult = null;
59
    
60
   /**
61
    * @var \Mailcode\Mailcode
62
    */
63
    protected $mailcode;
64
    
65
   /**
66
    * @var \Mailcode\Mailcode_Parser_Statement
67
    */
68
    protected $params;
69
70
   /**
71
    * @var string[] 
72
    */
73
    protected $validations = array(
74
        'params_empty',
75
        'params_keywords',
76
        'params_parse',
77
        'type_supported',
78
        'type_unsupported'
79
    );
80
    
81
   /**
82
    * @var string
83
    */
84
    protected $comment = '';
85
    
86
   /**
87
    * @var Mailcode_Commands_LogicKeywords|NULL
88
    */
89
    protected $logicKeywords;
90
    
91
    public function __construct(string $type='', string $paramsString='', string $matchedText='')
92
    {
93
        $this->type = $type;
94
        $this->paramsString = html_entity_decode($paramsString);
95
        $this->matchedText = $matchedText;
96
        $this->mailcode = Mailcode::create();
97
        
98
        $this->init();
99
    }
100
    
101
    protected function init() : void
102
    {
103
        
104
    }
105
    
106
   /**
107
    * @return string The ID of the command = the name of the command class file.
108
    */
109
    public function getID() : string
110
    {
111
        // account for commands with types: If_Variable should still return If.
112
        $base = str_replace(Mailcode_Commands_Command::class.'_', '', get_class($this));
113
        $tokens = explode('_', $base);
114
        return array_shift($tokens);
115
    }
116
    
117
   /**
118
    * Sets an optional comment that is not used anywhere, but
119
    * can be used by the application to track why a command is
120
    * used somewhere. 
121
    * 
122
    * @param string $comment
123
    * @return Mailcode_Commands_Command
124
    */
125
    public function setComment(string $comment) : Mailcode_Commands_Command
126
    {
127
        $this->comment = $comment;
128
        
129
        return $this;
130
    }
131
    
132
   /**
133
    * Retrieves the previously set comment, if any.
134
    * 
135
    * @return string
136
    */
137
    public function getComment() : string
138
    {
139
        return $this->comment;
140
    }
141
    
142
   /**
143
    * Checks whether this is a dummy command, which is only
144
    * used to access information on the command type. It cannot
145
    * be used as an actual live command.
146
    * 
147
    * @return bool
148
    */
149
    public function isDummy() : bool
150
    {
151
        return $this->type === '__dummy';
152
    }
153
    
154
   /**
155
    * Retrieves a hash of the actual matched command string,
156
    * which is used in collections to detect duplicate commands.
157
    * 
158
    * @return string
159
    */
160
    public function getHash() : string
161
    {
162
        $this->requireNonDummy();
163
        
164
        if($this->hash === '') {
165
            $this->hash = md5($this->matchedText);
166
        }
167
        
168
        return $this->hash;
169
    }
170
    
171
    protected function requireNonDummy() : void
172
    {
173
        if(!$this->isDummy())
174
        {
175
            return;
176
        }
177
        
178
        throw new Mailcode_Exception(
179
            'Operation not allowed with dummy commands',
180
            null,
181
            self::ERROR_NON_DUMMY_OPERATION
182
        );
183
    }
184
    
185
    public function isValid() : bool
186
    {
187
        return $this->validate()->isValid();
188
    }
189
    
190
    protected function validate() : \AppUtils\OperationResult
191
    {
192
        $this->requireNonDummy();
193
        
194
        if(isset($this->validationResult)) 
195
        {
196
            return $this->validationResult;
197
        }
198
        
199
        $this->validationResult = new \AppUtils\OperationResult($this);
200
201
        $this->validateSyntax();
202
        
203
        return $this->validationResult;
204
    }
205
    
206
    public function getValidationResult() :  \AppUtils\OperationResult
207
    {
208
        if(isset($this->validationResult)) 
209
        {
210
            return $this->validationResult;
211
        }
212
        
213
        throw new Mailcode_Exception(
214
            'No validation result available',
215
            'The command has no validation error, the validation result cannot be accessed.',
216
            self::ERROR_NO_VALIDATION_RESULT_AVAILABLE
217
        );
218
    }
219
    
220
    protected function validateSyntax() : void
221
    {
222
        $validations = array_merge($this->validations, $this->getValidations());
223
        
224
        foreach($validations as $validation)
225
        {
226
            // break off at the first validation issue
227
            if(!$this->validateSyntaxMethod($validation))
228
            {
229
                return;
230
            }
231
        }
232
    }
233
    
234
    protected function validateSyntaxMethod(string $validation) : bool
235
    {
236
        $method = 'validateSyntax_'.$validation;
237
        
238
        if(!method_exists($this, $method))
239
        {
240
            throw new Mailcode_Exception(
241
                'Missing validation method',
242
                sprintf(
243
                    'The method [%s] is missing from class [%s].',
244
                    $method,
245
                    get_class($this)
246
                ),
247
                self::ERROR_MISSING_VALIDATION_METHOD
248
            );
249
        }
250
        
251
        $this->$method();
252
        
253
        return $this->validationResult->isValid();
254
    }
255
    
256
   /**
257
    * @return string[]
258
    */
259
    abstract protected function getValidations() : array;
260
    
261
    protected function validateSyntax_params_empty() : void
262
    {
263
        if(!$this->requiresParameters())
264
        {
265
            return;
266
        }
267
        
268
        if(empty($this->paramsString))
269
        {
270
            $this->validationResult->makeError(
271
                t('Parameters have to be specified.'),
272
                self::VALIDATION_MISSING_PARAMETERS
273
            );
274
            return;
275
        }
276
    }
277
    
278
    protected function validateSyntax_params_keywords() : void
279
    {
280
        if(!$this->supportsLogicKeywords())
281
        {
282
            return;
283
        }
284
        
285
        $this->logicKeywords = new Mailcode_Commands_LogicKeywords($this, $this->paramsString);
286
        
287
        if(!$this->logicKeywords->isValid())
288
        {
289
            $this->validationResult->makeError(
290
                t('Invalid parameters:').' '.$this->logicKeywords->getErrorMessage(),
291
                $this->logicKeywords->getCode()
292
            );
293
            
294
            return;
295
        }
296
        
297
        $this->paramsString = $this->logicKeywords->getMainParamsString();
298
    }
299
    
300
    protected function validateSyntax_params_parse() : void
301
    {
302
        if(!$this->requiresParameters())
303
        {
304
            return;
305
        }
306
        
307
        $this->params = $this->mailcode->getParser()->createStatement($this->paramsString);
308
        
309
        if(!$this->params->isValid())
310
        {
311
            $error = $this->params->getValidationResult();
312
            
313
            $this->validationResult->makeError(
314
                t('Invalid parameters:').' '.$error->getErrorMessage(), 
315
                self::VALIDATION_INVALID_PARAMS_STATEMENT
316
            );
317
        }
318
    }
319
    
320
    protected function validateSyntax_type_supported() : void
321
    {
322
        if(!$this->supportsType() || empty($this->type))
323
        {
324
            return;
325
        }
326
        
327
        $types = $this->getSupportedTypes();
328
329
        if(!in_array($this->type, $types))
330
        {
331
            $this->validationResult->makeError(
332
                t('The command addon %1$s is not supported.', $this->type).' '.
333
                t('Valid addons are %1$s.', implode(', ', $types)),
334
                self::VALIDATION_ADDON_NOT_SUPPORTED
335
            );
336
            
337
            return;
338
        }
339
    }
340
    
341
    protected function validateSyntax_type_unsupported() : void
342
    {
343
        if($this->supportsType() || empty($this->type))
344
        {
345
            return;
346
        }
347
        
348
        $this->validationResult->makeError(
349
            t('Command addons are not supported (the %1$s part).', $this->type),
350
            self::VALIDATION_ADDONS_NOT_SUPPORTED
351
        );
352
    }
353
    
354
    public function hasType() : bool
355
    {
356
        return $this->supportsType() && !empty($this->type);
357
    }
358
    
359
    public function getType() : string
360
    {
361
        if($this->supportsType())
362
        {
363
            return $this->type;
364
        }
365
        
366
        return '';
367
    }
368
    
369
    public function hasParameters() : bool
370
    {
371
        return $this->requiresParameters() && !empty($this->paramsString);
372
    }
373
    
374
    public function getMatchedText() : string
375
    {
376
        return $this->matchedText;
377
    }
378
    
379
    public function getHighlighted() : string
380
    {
381
        if(!$this->isValid())
382
        {
383
            return '';
384
        }
385
        
386
        $highlighter = new Mailcode_Commands_Highlighter($this);
387
        return $highlighter->highlight();
388
    }
389
    
390
    public function getParamsString() : string
391
    {
392
        if($this->requiresParameters())
393
        {
394
            return $this->paramsString;
395
        }
396
        
397
        return '';
398
    }
399
    
400
    public function getParams() : ?Mailcode_Parser_Statement
401
    {
402
        return $this->params;
403
    }
404
    
405
    abstract public function getName() : string;
406
    
407
    abstract public function getLabel() : string;
408
    
409
    abstract public function requiresParameters() : bool;
410
    
411
    abstract public function supportsType() : bool;
412
    
413
   /**
414
    * Whether the command allows using logic keywords like "and:" or "or:"
415
    * in the command parameters.
416
    * 
417
    * @return bool
418
    */
419
    abstract public function supportsLogicKeywords() : bool;
420
    
421
    abstract public function generatesContent() : bool;
422
423
    abstract public function getDefaultType() : string;
424
    
425
    public final function getCommandType() : string
426
    {
427
        if($this instanceof Mailcode_Commands_Command_Type_Closing)
428
        {
429
            return 'Closing';
430
        }
431
        
432
        if($this instanceof Mailcode_Commands_Command_Type_Opening)
433
        {
434
            return 'Opening';
435
        }
436
        
437
        if($this instanceof Mailcode_Commands_Command_Type_Sibling)
438
        {
439
            return 'Sibling';
440
        }
441
        
442
        if($this instanceof Mailcode_Commands_Command_Type_Standalone)
443
        {
444
            return 'Standalone';
445
        }
446
        
447
        throw new Mailcode_Exception(
448
            'Invalid command type',
449
            sprintf(
450
                'The command [%s] does not implement any of the type interfaces.',
451
                get_class($this)
452
            ),
453
            self::ERROR_MISSING_TYPE_INTERFACE
454
        );
455
    }
456
    
457
    public function getNormalized() : string
458
    {
459
        $normalizer = new Mailcode_Commands_Normalizer($this);
460
        
461
        return $normalizer->normalize();
462
    }
463
    
464
   /**
465
    * Retrieves the names of all the command's supported types: the part
466
    * between the command name and the colon. Example: {command type: params}.
467
    * 
468
    * @return string[]
469
    */
470
    public function getSupportedTypes() : array
471
    {
472
        return array();
473
    }
474
    
475
   /**
476
    * Retrieves all variable names used in the command.
477
    * 
478
    * @return Mailcode_Variables_Collection_Regular
479
    */
480
    public function getVariables() : Mailcode_Variables_Collection_Regular
481
    {
482
        return Mailcode::create()->findVariables($this->paramsString);
483
    }
484
    
485
    public function __toString()
486
    {
487
        return $this->getNormalized();
488
    }
489
    
490
    public function getLogicKeywords() : Mailcode_Commands_LogicKeywords
491
    {
492
        if($this->supportsLogicKeywords() && isset($this->logicKeywords))
493
        {
494
            return $this->logicKeywords;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->logicKeywords could return the type null which is incompatible with the type-hinted return Mailcode\Mailcode_Commands_LogicKeywords. Consider adding an additional type-check to rule them out.
Loading history...
495
        }
496
        
497
        throw new Mailcode_Exception(
498
            'Logic keywords are not supported',
499
            'Cannot retrieve the logic keywords instance: it is only available for commands supporting logic commands.',
500
            self::ERROR_LOGIC_COMMANDS_NOT_SUPPORTED
501
        );
502
    }
503
}
504