Passed
Push — master ( 1b5df6...213867 )
by Sebastian
13:22
created

Mailcode_Commands_Command   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 435
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 2
Metric Value
eloc 140
c 8
b 0
f 2
dl 0
loc 435
rs 5.5199
wmc 56

27 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 __construct() 0 8 1
A getHighlighted() 0 9 2
A getParamsString() 0 8 2
A hasParameters() 0 3 2
A requireNonDummy() 0 11 2
A getNormalized() 0 24 6
A validate() 0 14 2
A getComment() 0 3 1
A getValidationResult() 0 11 2
A isValid() 0 3 1
A getID() 0 6 1
A validateSyntax_type_supported() 0 18 4
A validateSyntax_type_unsupported() 0 10 3
A validateSyntax() 0 27 4
A hasType() 0 3 2
A getSupportedTypes() 0 3 1
A validateSyntax_params() 0 25 4
A init() 0 2 1
A __toString() 0 3 1

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