I released Qiq 2.1.0 last week, with a new feature made possible only by using PHP tokens during the Qiq compiling process.

I.

Qiq is a template system for PHP. It uses native <?php ... ?> for the template syntax, but it allows for using a Qiq {{ ... }} syntax as well. Qiq syntax is the same as PHP, with a light dusting of syntax sugar added to make it more concise. That means a helper call in Qiq syntax ...

{{= anchor('http://example.com') }}

... gets $this-> added to it when Qiq compiles it to PHP ...

<?= $this->anchor('http://example.com') ?>

... making helper usage more concise in Qiq. It also means you can mix PHP in with Qiq as much or as little as you like, because Qiq code is pretty much just PHP code.

II.

One thing which always irked me a bit was that if you use a helper under Qiq syntax, but not as the opening keyword, you would still need to use $this-> inside the Qiq tags. For example, the following variable assignment would generate an error ...

{{ $a = anchor('http://example.net') }}

... because the compiler looked only at the opening keyword, and did not know to add $this-> to the anchor() call. Instead, if you were past the first keyword, you would have to add $this-> yourself:

{{ $a = $this->anchor('http://example.net') }}

That adds a bit of cognitive friction, but it didn't bother me too much, until someone else brought it up.

III.

Having a real bug report/feature request motivated me to address the issue. The problem was, how could the Qiq compiler examine the remaining code inside the Qiq tags, look for words that qualify as function calls, and add $this-> to them?

My first thought was to bring in nikic/php-parser and use it as part of a secondary compiling process. The idea was to convert the Qiq {{ ... }} syntax to PHP first, then run that PHP code through php-parser, find all plain function calls, and add $this-> to them.

That turned out to be more difficult than I thought it would be; walking the AST properly involved more effort than I would have liked for this endeavor. In addition, the php-parser pretty-printer for generating the converted output did not look suited to this particular task. I want very much for the Qiq code and the resulting PHP code to match up very closely, and the pretty-printer cannot guarantee that at this point.

IV.

As a result, I decided to fall back to the plain old PhpToken functionality (it's an improved version of token_get_all()). This turned out to be much more applicable to the problem at hand. The Qiq compiler process doesn't need a full AST as provided by php-parser, it just needs identify function calls.

Using an extended php token class, the Qiq compiler can tokenize the Qiq-enclosed PHP code, then examine each token to see if it looks like a function call. It does this by ...

  • checking that the current token is a non-keyword non-encapsed string;
  • looking ahead to see if the next significant token is an opening parenthesis;
  • and looking back to make sure the previous significant token is not ->, ?->, ::, or the function keyword.

Under those conditions, the current token is presumed to be a function call, and the compiler adds $this-> to it. (It's possible that these conditions might need some improvement, but testing on existing templates did not reveal any trouble.)

This approach took some trial-and-error, but the resulting solution is only 70 lines of code, and well-targeted to the problem at hand.

V.

As a result, you can now use a helper anywhere inside Qiq {{ ... }} tags, without having to add $this-> yourself. That means the Qiq code from the opening example now assigns the variable without error ...

{{ $a = anchor('http://example.net') }}

... which eliminates the last little bit of cognitive friction I have felt with Qiq.

Many thanks to apple-x-co for submitting the report that motivated this improvement!

Postscript

Try Qiq for your template system, because you like PHP syntax, but you also want:

  • Concise, explicit, context-specific escaping
  • Views, layouts, and partials
  • Blocks and inheritance
  • Rich and extensible HTML helpers
  • Easy-to-implement static analysis
  • Full documentation and unit-testing
Are you stuck with a legacy PHP application? You should buy my book because it gives you a step-by-step guide to improving you codebase, all while keeping it running the whole time.