Passed
Push — master ( 827600...d1e019 )
by Sebastian
04:24
created

Mailcode_Commands_Command   F

Complexity

Total Complexity 63

Size/Duplication

Total Lines 529
Duplicated Lines 0 %

Importance

Changes 11
Bugs 0 Features 2
Metric Value
eloc 163
dl 0
loc 529
rs 3.36
c 11
b 0
f 2
wmc 63

33 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 9 1
A getTranslationParam() 0 8 2
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 11 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 setTranslationParam() 0 4 1
A __toString() 0 3 1
A validateSyntax_params_parse() 0 22 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
   /**
92
    * @var Mailcode_Parser_Statement_Validator
93
    */
94
    protected $validator;
95
    
96
   /**
97
    * @var boolean
98
    */
99
    private $validated = false;
100
101
    /**
102
     * Collection of parameters for the translation backend.
103
     * @var array<string,mixed>
104
     */
105
    protected $translationParams = array();
106
107
    public function __construct(string $type='', string $paramsString='', string $matchedText='')
108
    {
109
        $this->type = $type;
110
        $this->paramsString = html_entity_decode($paramsString);
111
        $this->matchedText = $matchedText;
112
        $this->mailcode = Mailcode::create();
113
        $this->validationResult = new \AppUtils\OperationResult($this);
114
        
115
        $this->init();
116
    }
117
    
118
    protected function init() : void
119
    {
120
        
121
    }
122
    
123
   /**
124
    * @return string The ID of the command = the name of the command class file.
125
    */
126
    public function getID() : string
127
    {
128
        // account for commands with types: If_Variable should still return If.
129
        $base = str_replace(Mailcode_Commands_Command::class.'_', '', get_class($this));
130
        $tokens = explode('_', $base);
131
        return array_shift($tokens);
132
    }
133
    
134
   /**
135
    * Sets an optional comment that is not used anywhere, but
136
    * can be used by the application to track why a command is
137
    * used somewhere. 
138
    * 
139
    * @param string $comment
140
    * @return Mailcode_Commands_Command
141
    */
142
    public function setComment(string $comment) : Mailcode_Commands_Command
143
    {
144
        $this->comment = $comment;
145
        
146
        return $this;
147
    }
148
    
149
   /**
150
    * Retrieves the previously set comment, if any.
151
    * 
152
    * @return string
153
    */
154
    public function getComment() : string
155
    {
156
        return $this->comment;
157
    }
158
    
159
   /**
160
    * Checks whether this is a dummy command, which is only
161
    * used to access information on the command type. It cannot
162
    * be used as an actual live command.
163
    * 
164
    * @return bool
165
    */
166
    public function isDummy() : bool
167
    {
168
        return $this->type === '__dummy';
169
    }
170
    
171
   /**
172
    * Retrieves a hash of the actual matched command string,
173
    * which is used in collections to detect duplicate commands.
174
    * 
175
    * @return string
176
    */
177
    public function getHash() : string
178
    {
179
        $this->requireNonDummy();
180
        
181
        if($this->hash === '') {
182
            $this->hash = md5($this->matchedText);
183
        }
184
        
185
        return $this->hash;
186
    }
187
    
188
    protected function requireNonDummy() : void
189
    {
190
        if(!$this->isDummy())
191
        {
192
            return;
193
        }
194
        
195
        throw new Mailcode_Exception(
196
            'Operation not allowed with dummy commands',
197
            null,
198
            self::ERROR_NON_DUMMY_OPERATION
199
        );
200
    }
201
    
202
    public function isValid() : bool
203
    {
204
        return $this->validate()->isValid();
205
    }
206
    
207
    protected function validate() : \AppUtils\OperationResult
208
    {
209
        if(!$this->validated)
210
        {
211
            $this->requireNonDummy();
212
            $this->validateSyntax();
213
            
214
            $this->validated = true;
215
        }
216
        
217
        return $this->validationResult;
218
    }
219
    
220
    public function getValidationResult() :  \AppUtils\OperationResult
221
    {
222
        if(isset($this->validationResult)) 
223
        {
224
            return $this->validationResult;
225
        }
226
        
227
        throw new Mailcode_Exception(
228
            'No validation result available',
229
            'The command has no validation error, the validation result cannot be accessed.',
230
            self::ERROR_NO_VALIDATION_RESULT_AVAILABLE
231
        );
232
    }
233
    
234
    protected function validateSyntax() : void
235
    {
236
        $validations = array_merge($this->validations, $this->getValidations());
237
        
238
        foreach($validations as $validation)
239
        {
240
            // break off at the first validation issue
241
            if(!$this->validateSyntaxMethod($validation))
242
            {
243
                return;
244
            }
245
        }
246
    }
247
    
248
    protected function validateSyntaxMethod(string $validation) : bool
249
    {
250
        $method = 'validateSyntax_'.$validation;
251
        
252
        if(!method_exists($this, $method))
253
        {
254
            throw new Mailcode_Exception(
255
                'Missing validation method ['.$validation.']',
256
                sprintf(
257
                    'The method [%s] is missing from class [%s].',
258
                    $method,
259
                    get_class($this)
260
                ),
261
                self::ERROR_MISSING_VALIDATION_METHOD
262
            );
263
        }
264
        
265
        $this->$method();
266
        
267
        return $this->validationResult->isValid();
268
    }
269
    
270
   /**
271
    * @return string[]
272
    */
273
    abstract protected function getValidations() : array;
274
    
275
    protected function validateSyntax_params_empty() : void
276
    {
277
        if(!$this->requiresParameters())
278
        {
279
            return;
280
        }
281
        
282
        if(empty($this->paramsString))
283
        {
284
            $this->validationResult->makeError(
285
                t('Parameters have to be specified.'),
286
                self::VALIDATION_MISSING_PARAMETERS
287
            );
288
            return;
289
        }
290
    }
291
    
292
    protected function validateSyntax_params_keywords() : void
293
    {
294
        if(!$this->supportsLogicKeywords())
295
        {
296
            return;
297
        }
298
        
299
        $this->logicKeywords = new Mailcode_Commands_LogicKeywords($this, $this->paramsString);
300
        
301
        if(!$this->logicKeywords->isValid())
302
        {
303
            $this->validationResult->makeError(
304
                t('Invalid parameters:').' '.$this->logicKeywords->getErrorMessage(),
305
                $this->logicKeywords->getCode()
306
            );
307
            
308
            return;
309
        }
310
        
311
        $this->paramsString = $this->logicKeywords->getMainParamsString();
312
    }
313
    
314
    protected function validateSyntax_params_parse() : void
315
    {
316
        if(!$this->requiresParameters())
317
        {
318
            return;
319
        }
320
        
321
        $this->params = $this->mailcode->getParser()->createStatement($this->paramsString);
322
        
323
        if(!$this->params->isValid())
324
        {
325
            $error = $this->params->getValidationResult();
326
            
327
            $this->validationResult->makeError(
328
                t('Invalid parameters:').' '.$error->getErrorMessage(), 
329
                self::VALIDATION_INVALID_PARAMS_STATEMENT
330
            );
331
            
332
            return;
333
        }
334
        
335
        $this->validator = new Mailcode_Parser_Statement_Validator($this->params);
336
    }
337
    
338
    protected function validateSyntax_type_supported() : void
339
    {
340
        if(!$this->supportsType() || empty($this->type))
341
        {
342
            return;
343
        }
344
        
345
        $types = $this->getSupportedTypes();
346
347
        if(!in_array($this->type, $types))
348
        {
349
            $this->validationResult->makeError(
350
                t('The command addon %1$s is not supported.', $this->type).' '.
351
                t('Valid addons are %1$s.', implode(', ', $types)),
352
                self::VALIDATION_ADDON_NOT_SUPPORTED
353
            );
354
            
355
            return;
356
        }
357
    }
358
    
359
    protected function validateSyntax_type_unsupported() : void
360
    {
361
        if($this->supportsType() || empty($this->type))
362
        {
363
            return;
364
        }
365
        
366
        $this->validationResult->makeError(
367
            t('Command addons are not supported (the %1$s part).', $this->type),
368
            self::VALIDATION_ADDONS_NOT_SUPPORTED
369
        );
370
    }
371
    
372
    public function hasType() : bool
373
    {
374
        return $this->supportsType() && !empty($this->type);
375
    }
376
    
377
    public function getType() : string
378
    {
379
        if($this->supportsType())
380
        {
381
            return $this->type;
382
        }
383
        
384
        return '';
385
    }
386
    
387
    public function hasParameters() : bool
388
    {
389
        return $this->requiresParameters() && !empty($this->paramsString);
390
    }
391
    
392
    public function getMatchedText() : string
393
    {
394
        return $this->matchedText;
395
    }
396
    
397
    public function getHighlighted() : string
398
    {
399
        if(!$this->isValid())
400
        {
401
            return '';
402
        }
403
        
404
        $highlighter = new Mailcode_Commands_Highlighter($this);
405
        return $highlighter->highlight();
406
    }
407
    
408
    public function getParamsString() : string
409
    {
410
        if($this->requiresParameters())
411
        {
412
            return $this->paramsString;
413
        }
414
        
415
        return '';
416
    }
417
    
418
    public function getParams() : ?Mailcode_Parser_Statement
419
    {
420
        return $this->params;
421
    }
422
    
423
    abstract public function getName() : string;
424
    
425
    abstract public function getLabel() : string;
426
    
427
    abstract public function requiresParameters() : bool;
428
    
429
    abstract public function supportsType() : bool;
430
    
431
   /**
432
    * Whether the command allows using logic keywords like "and:" or "or:"
433
    * in the command parameters.
434
    * 
435
    * @return bool
436
    */
437
    abstract public function supportsLogicKeywords() : bool;
438
    
439
    abstract public function generatesContent() : bool;
440
441
    abstract public function getDefaultType() : string;
442
    
443
    public final function getCommandType() : string
444
    {
445
        if($this instanceof Mailcode_Commands_Command_Type_Closing)
446
        {
447
            return 'Closing';
448
        }
449
        
450
        if($this instanceof Mailcode_Commands_Command_Type_Opening)
451
        {
452
            return 'Opening';
453
        }
454
        
455
        if($this instanceof Mailcode_Commands_Command_Type_Sibling)
456
        {
457
            return 'Sibling';
458
        }
459
        
460
        if($this instanceof Mailcode_Commands_Command_Type_Standalone)
461
        {
462
            return 'Standalone';
463
        }
464
        
465
        throw new Mailcode_Exception(
466
            'Invalid command type',
467
            sprintf(
468
                'The command [%s] does not implement any of the type interfaces.',
469
                get_class($this)
470
            ),
471
            self::ERROR_MISSING_TYPE_INTERFACE
472
        );
473
    }
474
    
475
    public function getNormalized() : string
476
    {
477
        $normalizer = new Mailcode_Commands_Normalizer($this);
478
        
479
        return $normalizer->normalize();
480
    }
481
    
482
   /**
483
    * Retrieves the names of all the command's supported types: the part
484
    * between the command name and the colon. Example: {command type: params}.
485
    * 
486
    * @return string[]
487
    */
488
    public function getSupportedTypes() : array
489
    {
490
        return array();
491
    }
492
    
493
   /**
494
    * Retrieves all variable names used in the command.
495
    * 
496
    * @return Mailcode_Variables_Collection_Regular
497
    */
498
    public function getVariables() : Mailcode_Variables_Collection_Regular
499
    {
500
        return Mailcode::create()->findVariables($this->paramsString);
501
    }
502
    
503
    public function __toString()
504
    {
505
        return $this->getNormalized();
506
    }
507
    
508
    public function getLogicKeywords() : Mailcode_Commands_LogicKeywords
509
    {
510
        if($this->supportsLogicKeywords() && isset($this->logicKeywords))
511
        {
512
            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...
513
        }
514
        
515
        throw new Mailcode_Exception(
516
            'Logic keywords are not supported',
517
            'Cannot retrieve the logic keywords instance: it is only available for commands supporting logic commands.',
518
            self::ERROR_LOGIC_COMMANDS_NOT_SUPPORTED
519
        );
520
    }
521
522
   /**
523
    * Sets a parameter for the translation backend. The backend can use
524
    * these to allow command-specific configurations.
525
    *
526
    * @param string $name
527
    * @param mixed $value
528
    * @return $this
529
    */
530
    public function setTranslationParam(string $name, $value)
531
    {
532
        $this->translationParams[$name] = $value;
533
        return $this;
534
    }
535
536
   /**
537
    * Retrieves a previously set translation parameter.
538
    *
539
    * @param string $name
540
    * @return mixed
541
    */
542
    public function getTranslationParam(string $name)
543
    {
544
        if(isset($this->translationParams[$name]))
545
        {
546
            return $this->translationParams[$name];
547
        }
548
549
        return null;
550
    }
551
}
552