├── README.md └── php2twig /README.md: -------------------------------------------------------------------------------- 1 | # php-twig-converter 2 | 3 | This little php script is intended to convert at best template files written 4 | with the PHPTemplate syntax to Twig syntax. 5 | 6 | ## Usage 7 | 8 | You can pass directly content to the script via stdin, it will return the 9 | converted template through stdout. 10 | 11 | $ cat page.tpl.php | ./php2twig 12 | 13 | You can pass file names to the script, it will create a `.tpl.twig` file at the 14 | same location: 15 | 16 | $ ls 17 | node.tpl.php page.tpl.php 18 | $ ./php2twig node.tpl.php page.tpl.php 19 | $ ls 20 | node.tpl.php node.tpl.twig page.tpl.php page.tpl.twig 21 | 22 | 23 | ## Options 24 | 25 | By default when converting files, the PHPTemplate extension is `.tpl.php`, you 26 | can override it with `-e`: 27 | 28 | $ ls 29 | node.template 30 | $ ./php2twig node.template -e ".template" 31 | $ ls 32 | node.template node.tpl.twig 33 | 34 | 35 | By default when converting files, the new Twig extension is `.tpl.twig`, you 36 | can override it with `-ne`: 37 | 38 | $ ls 39 | node.tpl.php 40 | $ ./php2twig node.tpl.php -ne ".html.twig" 41 | $ ls 42 | node.html.twig node.tpl.php 43 | 44 | ## Demo 45 | 46 | `$ ./php2twig modules/block/block.tpl.php` 47 | 48 | ``` 49 |
> 50 | 51 | 52 | subject): ?> 53 | >subject ?> 54 | 55 | 56 | 57 |
> 58 | 59 |
60 |
61 | ``` 62 | 63 | will convert to 64 | 65 | ``` 66 |
67 | 68 | {{ title_prefix }} 69 | {% if block.subject %} 70 | {{ block.subject}} 71 | {% endif %} 72 | {{ title_suffix }} 73 | 74 |
75 | {{ content }} 76 |
77 |
78 | ``` 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /php2twig: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | '.tpl.php', 6 | 'new_extension' => '.tpl.twig', 7 | 'recursive' => false, 8 | ]; 9 | 10 | // First we need to get some arguments 11 | if ($argc > 1) { 12 | $files = []; 13 | $current_dir = getcwd(); 14 | 15 | // Look for commonly found arguments 16 | $cargv = count($argv); 17 | for ($i = 1; $i <= $argc && $i <= $cargv - 1; $i++) { 18 | switch ($argv[$i]) { 19 | case '-h': 20 | case '--help': 21 | print <<", "", $value); 202 | 203 | } elseif (in_array($name, [ 204 | T_WHITESPACE, 205 | T_LOGICAL_AND, 206 | T_LOGICAL_OR, 207 | T_CONSTANT_ENCAPSED_STRING, 208 | T_LNUMBER, 209 | T_DNUMBER, 210 | T_IS_GREATER_OR_EQUAL, 211 | T_IS_SMALLER_OR_EQUAL, 212 | ])) { 213 | // These are the same in twig 214 | $output .= $value; 215 | 216 | } elseif ($name == T_VARIABLE) { 217 | // Variable, remove leading '$' 218 | $output .= substr($value, 1); 219 | 220 | } elseif ($name == T_ECHO || $name == T_PRINT) { 221 | // Print something 222 | $output .= '{{'; 223 | $nesting[] = '}}'; 224 | 225 | } elseif ($name == T_STRING) { 226 | // Function call 227 | if ($value == 't') { 228 | $nest = array_pop($nesting); 229 | $nesting[] = '|t '.$nest; 230 | } elseif ($value == 'render') { 231 | // Ignore 232 | } elseif (in_array('inside_object_method', $flags) && next($tokens) !== '(') { 233 | $output .= $value; 234 | } else { 235 | $output .= $value.'('; 236 | $nesting[] = ')'; 237 | $flags[] = 'inside_func_call'; 238 | echo "Unsupported function $value().\n"; 239 | } 240 | // TODO maybe handle theme() ? 241 | 242 | } elseif ($name == T_IS_EQUAL || $name == T_IS_IDENTICAL) { 243 | $output .= ' is '; 244 | 245 | } elseif ($name == T_IS_NOT_EQUAL || $name == T_IS_NOT_IDENTICAL) { 246 | $output .= ' is not '; 247 | 248 | } elseif ($name == T_BOOLEAN_AND) { 249 | $output .= ' and '; 250 | 251 | } elseif ($name == T_BOOLEAN_OR) { 252 | $output .= ' or '; 253 | 254 | } elseif ($name == T_IF) { 255 | // There should be an expression after this, so we close it in ':' or '{' 256 | $output .= '{% if'; 257 | $nesting[] = '%}'; 258 | $flags[] = 'inside_condition'; 259 | $flags[] = 'inside_if'; 260 | 261 | } elseif ($name == T_ENDIF) { 262 | if (in_array('inside_if', $flags)) { 263 | unset($flags[array_search('inside_if', $flags)]); 264 | } 265 | $output .= '{% endif %}'; 266 | 267 | } elseif ($name == T_ENDFOR || $name == T_ENDFOREACH) { 268 | $output .= '{% endfor %}'; 269 | 270 | } elseif ($name == T_ELSEIF) { 271 | // There should be an expression after this, so we close it in ':' or '{' 272 | $output .= '{% elseif '; 273 | $flags[] = 'inside_condition'; 274 | $nesting[] = '%}'; 275 | 276 | } elseif ($name == T_ELSE) { 277 | $output .= '{% else %} '; 278 | 279 | } elseif ($name == T_OBJECT_OPERATOR) { 280 | // This is object call or property -> 281 | $flags[] = 'inside_object_method'; 282 | $output .= '.'; 283 | 284 | } elseif ($name == T_EMPTY) { 285 | // !empty() is managed in '!', flag to be closed in '?' 286 | $flags[] = 'inside_empty_condition'; 287 | $nesting[] = 'is empty '; 288 | 289 | } elseif ($name == T_COMMENT) { 290 | if ($value[1] == '*') { 291 | // Multiline comment, remove ' * ' on each line 292 | $value = preg_replace('@/\*(.+)\s+\*/@sU', '{# $1 #}', $value); 293 | $value = preg_replace('@\s{3}\*\s@s', '', $value); 294 | $output .= $value; 295 | } else { 296 | // Single line comment that may be multiline (lol), so take care of this 297 | $output .= "{# "; 298 | prev($tokens); 299 | $matches = null; 300 | while (($token = next($tokens)) && is_array($token) && in_array($token[0], 301 | [T_WHITESPACE, T_COMMENT])) { 302 | if ($token[0] == T_COMMENT) { 303 | if (isset($matches[2])) { 304 | $output .= "$matches[2]"; 305 | } 306 | preg_match('@^//\s?(.*)(\s*)@', $token[1], $matches); 307 | $output .= "$matches[1]"; 308 | } else { 309 | $matches[2] .= "$token[1]"; 310 | } 311 | } 312 | $output .= " #}".$matches[2]; 313 | prev($tokens); 314 | } 315 | 316 | } elseif ($name == T_FOREACH) { 317 | // foreach($iterated as $index => $value) -> for(value in iterated) _or_ for(index, value in iterated) 318 | $output .= '{% for'; 319 | while (!is_array($token) || !in_array($token[0], [T_VARIABLE, T_STRING])) { 320 | $token = next($tokens); 321 | } 322 | $iterated = substr($token[1], 1); 323 | while (!is_array($token) || $token[0] !== T_AS) { 324 | $token = next($tokens); 325 | } 326 | while (!is_array($token) || $token[0] !== T_VARIABLE) { 327 | $token = next($tokens); 328 | } 329 | $index_name = substr($token[1], 1); 330 | 331 | // Try to find value in next four tokens 332 | $i = 6; 333 | while (($token = next($tokens)) && $i) { 334 | if (is_array($token) && $token[0] == T_VARIABLE) { 335 | $value = substr($token[1], 1); 336 | $output .= " $index_name, $value in $iterated %}"; 337 | break; 338 | } 339 | $i--; 340 | } 341 | if (!$i) { 342 | $output .= " $index_name in $iterated %}"; 343 | // We've got too far, go back a bit 344 | prev($tokens); 345 | prev($tokens); 346 | prev($tokens); 347 | prev($tokens); 348 | } 349 | 350 | } else { 351 | // unsupported 352 | echo "WARNING: UNSUPPORTED ".token_name($token[0])."\n"; 353 | } 354 | } else { 355 | // Characters 356 | switch ($token) { 357 | case ';': 358 | // This close a function call or a print 359 | if (in_array('inside_func_call', $flags)) { 360 | $output .= array_pop($nesting); 361 | unset($flags[array_search('inside_func_call', $flags)]); 362 | } 363 | if (in_array('}}', $nesting)) { 364 | $output .= ' '.array_pop($nesting); 365 | } 366 | break; 367 | 368 | case ':': 369 | // This must print inside ternary, else we don't care 370 | if (in_array('inside_ternary', $flags)) { 371 | $output .= $token; 372 | unset($flags[array_search('inside_ternary', $flags)]); 373 | } 374 | // This might close a if expression 375 | if (in_array('inside_condition', $flags)) { 376 | $output .= ' '.array_pop($nesting); 377 | unset($flags[array_search('inside_condition', $flags)]); 378 | } 379 | break; 380 | 381 | case '(': 382 | case ')': 383 | case ']': 384 | // Ignored 385 | break; 386 | 387 | case '{': 388 | // This might close an expression 389 | if (count($nesting)) { 390 | $output .= array_pop($nesting); 391 | } 392 | break; 393 | 394 | case '}': 395 | // This might close a if block 396 | if (in_array('inside_if', $flags)) { 397 | $output .= '{% endif %}'; 398 | unset($flags[array_search('inside_if', $flags)]); 399 | } 400 | break; 401 | 402 | case '[': 403 | // Accessing key of an array 404 | $next = next($tokens); 405 | if (is_array($next) && $next[0] == T_CONSTANT_ENCAPSED_STRING) { 406 | if ($next[1][1] == '#') { 407 | // verify unsupported first characters 408 | $output .= "[$next[1]]"; 409 | } else { 410 | // else remove quotes 411 | $output .= '.'.substr($next[1], 1, -1); 412 | } 413 | } elseif (is_array($next) && $next[0] == T_VARIABLE) { 414 | // Remove leading '$' 415 | $output .= '.'.substr($next[1], 1); 416 | } elseif (is_array($next) && $next[0] == T_LNUMBER) { 417 | // no treatment on numbers 418 | $output .= '.'.$next[1]; 419 | } elseif (is_array($next)) { 420 | die("Unsupported array/object traversing with ".token_name($next[0])); 421 | } else { 422 | die("Unsupported array/object traversing with $next"); 423 | } 424 | break; 425 | 426 | case '!': 427 | // Verify next token is not empty() 428 | $next = next($tokens); 429 | if (is_array($next) && $next[0] == T_EMPTY) { 430 | // if empty(), ignore 431 | } else { 432 | prev($tokens); 433 | $output .= "not "; 434 | } 435 | break; 436 | 437 | case '.': 438 | // Concatenation 439 | $output .= '~'; 440 | break; 441 | 442 | case '?': 443 | // Ternary condition 444 | if (in_array('inside_empty_condition', $flags)) { 445 | $output .= array_pop($nesting); 446 | unset($flags[array_search('inside_empty_condition', $flags)]); 447 | } 448 | $flags[] = 'inside_ternary'; 449 | $output .= $token; 450 | break; 451 | 452 | case ',': 453 | case '%': 454 | case '*': 455 | case '/': 456 | case '+': 457 | case '-': 458 | // Same as twig 459 | $output .= $token; 460 | break; 461 | } 462 | } 463 | next($tokens); 464 | } 465 | 466 | if ($debug) { 467 | echo $str, $output, "\n"; 468 | } 469 | 470 | return $output; 471 | } 472 | --------------------------------------------------------------------------------