PHPTAL_Php_Transformer   F
last analyzed

Complexity

Total Complexity 97

Size/Duplication

Total Lines 381
Duplicated Lines 7.09 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 27
loc 381
rs 2
c 0
b 0
f 0
wmc 97
lcom 1
cbo 1

5 Methods

Rating   Name   Duplication   Size   Complexity  
F transform() 27 331 85
A isAlpha() 0 5 2
A isDigit() 0 4 2
A isDigitCompound() 0 4 3
A isVarNameChar() 0 4 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PHPTAL_Php_Transformer 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 PHPTAL_Php_Transformer, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * PHPTAL templating engine
4
 *
5
 * PHP Version 5
6
 *
7
 * @category HTML
8
 * @package  PHPTAL
9
 * @author   Laurent Bedubourg <[email protected]>
10
 * @author   Kornel Lesiński <[email protected]>
11
 * @license  http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
12
 * @version  SVN: $Id$
13
 * @link     http://phptal.org/
14
 */
15
16
/**
17
 * Tranform php: expressions into their php equivalent.
18
 *
19
 * This transformer produce php code for expressions like :
20
 *
21
 * - a.b["key"].c().someVar[10].foo()
22
 * - (a or b) and (c or d)
23
 * - not myBool
24
 * - ...
25
 *
26
 * The $prefix variable may be changed to change the context lookup.
27
 *
28
 * example:
29
 *
30
 *      $res = PHPTAL_Php_Transformer::transform('a.b.c[x]', '$ctx->');
31
 *      $res == '$ctx->a->b->c[$ctx->x]';
32
 *
33
 * @package PHPTAL
34
 * @subpackage Php
35
 * @author Laurent Bedubourg <[email protected]>
36
 */
37
class PHPTAL_Php_Transformer
38
{
39
    const ST_WHITE  = -1; // start of string or whitespace
40
    const ST_NONE   = 0;  // pass through (operators, parens, etc.)
41
    const ST_STR    = 1;  // 'foo'
42
    const ST_ESTR   = 2;  // "foo ${x} bar"
43
    const ST_VAR    = 3;  // abcd
44
    const ST_NUM    = 4;  // 123.02
45
    const ST_EVAL   = 5;  // $somevar
46
    const ST_MEMBER = 6;  // abcd.x
47
    const ST_STATIC = 7;  // class::[$]static|const
48
    const ST_DEFINE = 8;  // @MY_DEFINE
49
50
    /**
51
     * transform PHPTAL's php-like syntax into real PHP
52
     */
53
    public static function transform($str, $prefix='$')
54
    {
55
        $len = strlen($str);
56
        $state = self::ST_WHITE;
57
        $result = '';
58
        $i = 0;
0 ignored issues
show
Unused Code introduced by
$i is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
59
        $inString = false;
0 ignored issues
show
Unused Code introduced by
$inString is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
60
        $backslashed = false;
61
        $instanceof = false;
62
        $eval = false;
63
64
65
        for ($i = 0; $i <= $len; $i++) {
66
            if ($i == $len) $c = "\0";
67
            else $c = $str[$i];
68
69
            switch ($state) {
70
71
                // after whitespace a variable-variable may start, ${var} → $ctx->{$ctx->var}
72
                case self::ST_WHITE:
73
                    if ($c === '$' && $i+1 < $len && $str[$i+1] === '{')
74
                    {
75
                        $result .= $prefix;
76
                        $state = self::ST_NONE;
77
                        break;
78
                    }
79
                    /* NO BREAK - ST_WHITE is almost the same as ST_NONE */
80
81
                // no specific state defined, just eat char and see what to do with it.
82
                case self::ST_NONE:
83
                    // begin of eval without {
84
                    if ($c === '$' && $i+1 < $len && self::isAlpha($str[$i+1])) {
85
                        $state = self::ST_EVAL;
86
                        $mark = $i+1;
87
                        $result .= $prefix.'{';
88
                    }
89
                    elseif (self::isDigit($c))
90
                    {
91
                        $state = self::ST_NUM;
92
                        $mark = $i;
93
                    }
94
                    // that an alphabetic char, then it should be the begining
95
                    // of a var or static
96
                    // && !self::isDigit($c) checked earlier
97
                    elseif (self::isVarNameChar($c)) {
98
                        $state = self::ST_VAR;
99
                        $mark = $i;
100
                    }
101
                    // begining of double quoted string
102
                    elseif ($c === '"') {
103
                        $state = self::ST_ESTR;
104
                        $mark = $i;
105
                        $inString = true;
0 ignored issues
show
Unused Code introduced by
$inString is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
106
                    }
107
                    // begining of single quoted string
108
                    elseif ($c === '\'') {
109
                        $state = self::ST_STR;
110
                        $mark = $i;
111
                        $inString = true;
0 ignored issues
show
Unused Code introduced by
$inString is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
112
                    }
113
                    // closing a method, an array access or an evaluation
114
                    elseif ($c === ')' || $c === ']' || $c === '}') {
115
                        $result .= $c;
116
                        // if next char is dot then an object member must
117
                        // follow
118
                        if ($i+1 < $len && $str[$i+1] === '.') {
119
                            $result .= '->';
120
                            $state = self::ST_MEMBER;
121
                            $mark = $i+2;
122
                            $i+=2;
123
                        }
124
                    }
125
                    // @ is an access to some defined variable
126
                    elseif ($c === '@') {
127
                        $state = self::ST_DEFINE;
128
                        $mark = $i+1;
129
                    }
130
                    elseif (ctype_space($c)) {
131
                        $state = self::ST_WHITE;
132
                        $result .= $c;
133
                    }
134
                    // character we don't mind about
135
                    else {
136
                        $result .= $c;
137
                    }
138
                    break;
139
140
                // $xxx
141 View Code Duplication
                case self::ST_EVAL:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
142
                    if (!self::isVarNameChar($c)) {
143
                        $result .= $prefix . substr($str, $mark, $i-$mark);
0 ignored issues
show
Bug introduced by
The variable $mark does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
144
                        $result .= '}';
145
                        $state = self::ST_NONE;
146
                    }
147
                    break;
148
149
                // single quoted string
150
                case self::ST_STR:
151
                    if ($c === '\\') {
152
                        $backslashed = true;
153
                    } elseif ($backslashed) {
154
                        $backslashed = false;
155
                    }
156
                    // end of string, back to none state
157
                    elseif ($c === '\'') {
158
                        $result .= substr($str, $mark, $i-$mark+1);
159
                        $inString = false;
0 ignored issues
show
Unused Code introduced by
$inString is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
160
                        $state = self::ST_NONE;
161
                    }
162
                    break;
163
164
                // double quoted string
165
                case self::ST_ESTR:
166
                    if ($c === '\\') {
167
                        $backslashed = true;
168
                    } elseif ($backslashed) {
169
                        $backslashed = false;
170
                    }
171
                    // end of string, back to none state
172
                    elseif ($c === '"') {
173
                        $result .= substr($str, $mark, $i-$mark+1);
174
                        $inString = false;
0 ignored issues
show
Unused Code introduced by
$inString is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
175
                        $state = self::ST_NONE;
176
                    }
177
                    // instring interpolation, search } and transform the
178
                    // interpollation to insert it into the string
179
                    elseif ($c === '$' && $i+1 < $len && $str[$i+1] === '{') {
180
                        $result .= substr($str, $mark, $i-$mark) . '{';
181
182
                        $sub = 0;
183
                        for ($j = $i; $j<$len; $j++) {
184
                            if ($str[$j] === '{') {
185
                                $sub++;
186
                            } elseif ($str[$j] === '}' && (--$sub) == 0) {
187
                                $part = substr($str, $i+2, $j-$i-2);
188
                                $result .= self::transform($part, $prefix);
189
                                $i = $j;
190
                                $mark = $i;
191
                            }
192
                        }
193
                    }
194
                    break;
195
196
                // var state
197
                case self::ST_VAR:
198
                    if (self::isVarNameChar($c)) {
199
                    }
200
                    // end of var, begin of member (method or var)
201
                    elseif ($c === '.') {
202
                        $result .= $prefix . substr($str, $mark, $i-$mark);
203
                        $result .= '->';
204
                        $state = self::ST_MEMBER;
205
                        $mark = $i+1;
206
                    }
207
                    // static call, the var is a class name
208
                    elseif ($c === ':' && $i+1 < $len && $str[$i+1] === ':') {
209
                        $result .= substr($str, $mark, $i-$mark+1);
210
                        $mark = $i+1;
211
                        $i++;
212
                        $state = self::ST_STATIC;
213
                        break;
214
                    }
215
                    // function invocation, the var is a function name
216
                    elseif ($c === '(') {
217
                        $result .= substr($str, $mark, $i-$mark+1);
218
                        $state = self::ST_NONE;
219
                    }
220
                    // array index, the var is done
221
                    elseif ($c === '[') {
222
                        if ($str[$mark]==='_') { // superglobal?
223
                            $result .= '$' . substr($str, $mark, $i-$mark+1);
224
                        } else {
225
                            $result .= $prefix . substr($str, $mark, $i-$mark+1);
226
                        }
227
                        $state = self::ST_NONE;
228
                    }
229
                    // end of var with non-var-name character, handle keywords
230
                    // and populate the var name
231
                    else {
232
                        $var = substr($str, $mark, $i-$mark);
233
                        $low = strtolower($var);
234
                        // boolean and null
235
                        if ($low === 'true' || $low === 'false' || $low === 'null') {
236
                            $result .= $var;
237
                        }
238
                        // lt, gt, ge, eq, ...
239
                        elseif (array_key_exists($low, self::$TranslationTable)) {
240
                            $result .= self::$TranslationTable[$low];
241
                        }
242
                        // instanceof keyword
243
                        elseif ($low === 'instanceof') {
244
                            $result .= $var;
245
                            $instanceof = true;
246
                        }
247
                        // previous was instanceof
248
                        elseif ($instanceof) {
249
                            // last was instanceof, this var is a class name
250
                            $result .= $var;
251
                            $instanceof = false;
252
                        }
253
                        // regular variable
254
                        else {
255
                            $result .= $prefix . $var;
256
                        }
257
                        $i--;
258
                        $state = self::ST_NONE;
259
                    }
260
                    break;
261
262
                // object member
263
                case self::ST_MEMBER:
264
                    if (self::isVarNameChar($c)) {
265
                    }
266
                    // eval mode ${foo}
267
                    elseif ($c === '$' && ($i >= $len-2 || $str[$i+1] !== '{')) {
268
                        $result .= '{' . $prefix;
269
                        $mark++;
270
                        $eval = true;
271
                    }
272
                    // x.${foo} x->{foo}
273
                    elseif ($c === '$') {
274
                        $mark++;
275
                    }
276
                    // end of var member var, begin of new member
277 View Code Duplication
                    elseif ($c === '.') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
278
                        $result .= substr($str, $mark, $i-$mark);
279
                        if ($eval) { $result .='}'; $eval = false; }
280
                        $result .= '->';
281
                        $mark = $i+1;
282
                        $state = self::ST_MEMBER;
283
                    }
284
                    // begin of static access
285
                    elseif ($c === ':') {
286
                        $result .= substr($str, $mark, $i-$mark+1);
287
                        if ($eval) { $result .='}'; $eval = false; }
288
                        $state = self::ST_STATIC;
289
                        break;
290
                    }
291
                    // the member is a method or an array
292 View Code Duplication
                    elseif ($c === '(' || $c === '[') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
293
                        $result .= substr($str, $mark, $i-$mark+1);
294
                        if ($eval) { $result .='}'; $eval = false; }
295
                        $state = self::ST_NONE;
296
                    }
297
                    // regular end of member, it is a var
298
                    else {
299
                        $var = substr($str, $mark, $i-$mark);
300
                        if ($var !== '' && !preg_match('/^[a-z][a-z0-9_\x7f-\xff]*$/i',$var)) {
301
                            throw new PHPTAL_ParserException("Invalid field name '$var' in expression php:$str");
302
                        }
303
                        $result .= $var;
304
                        if ($eval) { $result .='}'; $eval = false; }
305
                        $state = self::ST_NONE;
306
                        $i--;
307
                    }
308
                    break;
309
310
                // wait for separator
311 View Code Duplication
                case self::ST_DEFINE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
312
                    if (self::isVarNameChar($c)) {
313
                    } else {
314
                        $state = self::ST_NONE;
315
                        $result .= substr($str, $mark, $i-$mark);
316
                        $i--;
317
                    }
318
                    break;
319
320
                // static call, can be const, static var, static method
321
                // Klass::$static
322
                // Klass::const
323
                // Kclass::staticMethod()
324
                //
325
                case self::ST_STATIC:
326
                    if (self::isVarNameChar($c)) {
327
                    }
328
                    // static var
329
                    elseif ($c === '$') {
330
                    }
331
                    // end of static var which is an object and begin of member
332
                    elseif ($c === '.') {
333
                        $result .= substr($str, $mark, $i-$mark);
334
                        $result .= '->';
335
                        $mark = $i+1;
336
                        $state = self::ST_MEMBER;
337
                    }
338
                    // end of static var which is a class name
339
                    elseif ($c === ':') {
340
                        $result .= substr($str, $mark, $i-$mark+1);
341
                        $state = self::ST_STATIC;
342
                        break;
343
                    }
344
                    // static method or array
345
                    elseif ($c === '(' || $c === '[') {
346
                        $result .= substr($str, $mark, $i-$mark+1);
347
                        $state = self::ST_NONE;
348
                    }
349
                    // end of static var or const
350
                    else {
351
                        $result .= substr($str, $mark, $i-$mark);
352
                        $state = self::ST_NONE;
353
                        $i--;
354
                    }
355
                    break;
356
357
                // numeric value
358
                case self::ST_NUM:
359
                    if (!self::isDigitCompound($c)) {
360
                        $var = substr($str, $mark, $i-$mark);
361
362
                        if (self::isAlpha($c) || $c === '_') {
363
                            throw new PHPTAL_ParserException("Syntax error in number '$var$c' in expression php:$str");
364
                        }
365
                        if (!is_numeric($var)) {
366
                            throw new PHPTAL_ParserException("Syntax error in number '$var' in expression php:$str");
367
                        }
368
369
                        $result .= $var;
370
                        $state = self::ST_NONE;
371
                        $i--;
372
                    }
373
                    break;
374
            }
375
        }
376
377
        $result = trim($result);
378
379
        // CodeWriter doesn't like expressions that look like blocks
380
        if ($result[strlen($result)-1] === '}') return '('.$result.')';
381
382
        return $result;
383
    }
384
385
    private static function isAlpha($c)
386
    {
387
        $c = strtolower($c);
388
        return $c >= 'a' && $c <= 'z';
389
    }
390
391
    private static function isDigit($c)
392
    {
393
        return ($c >= '0' && $c <= '9');
394
    }
395
396
    private static function isDigitCompound($c)
397
    {
398
        return ($c >= '0' && $c <= '9' || $c === '.');
399
    }
400
401
    private static function isVarNameChar($c)
402
    {
403
        return self::isAlpha($c) || ($c >= '0' && $c <= '9') || $c === '_' || $c === '\\';
404
    }
405
406
    private static $TranslationTable = array(
407
        'not' => '!',
408
        'ne'  => '!=',
409
        'and' => '&&',
410
        'or'  => '||',
411
        'lt'  => '<',
412
        'gt'  => '>',
413
        'ge'  => '>=',
414
        'le'  => '<=',
415
        'eq'  => '==',
416
    );
417
}
418
419