├── 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 |
--------------------------------------------------------------------------------