XaminProject / handlebars.php

Handlebars processor for php
331 stars 134 forks source link

When-helper nested in each loop: clears variables? #160

Closed maan1234 closed 7 years ago

maan1234 commented 7 years ago

I have stumbled on a problem where the #when-helper seems to clear all variables, if it is nested in a #each. The code to replicate the problem is below. I am using the latest version of the library.

I am new to this Handlebars library, so if there is something wrong with my way of coding, a little push in the right direction is also greatly appreciated.

$engine = new \Handlebars\Handlebars();

// Helper comes straight out of HandlebarsTest.php
$engine->registerHelper('when', function($value1, $operator, $value2, $options) {
    $valid = false;
    switch (true) {
        case $operator == 'eq' && $value1 == $value2:
        case $operator == '==' && $value1 == $value2:
        case $operator == 'req' && $value1 === $value2:
        case $operator == '===' && $value1 === $value2:
        case $operator == 'neq' && $value1 != $value2:
        case $operator == '!=' && $value1 != $value2:
        case $operator == 'rneq' && $value1 !== $value2:
        case $operator == '!==' && $value1 !== $value2:
        case $operator == 'lt' && $value1 < $value2:
        case $operator == '<' && $value1 < $value2:
        case $operator == 'lte' && $value1 <= $value2:
        case $operator == '<=' && $value1 <= $value2:
        case $operator == 'gt' && $value1 > $value2:
        case $operator == '>' && $value1 > $value2:
        case $operator == 'gte' && $value1 >= $value2:
        case $operator == '>=' && $value1 >= $value2:
        case $operator == 'and' && $value1 && $value2:
        case $operator == '&&' && ($value1 && $value2):
        case $operator == 'or' && ($value1 || $value2):
        case $operator == '||' && ($value1 || $value2):
            $valid = true;
            break;
    }

    if($valid) {
        return $options['fn']();
    }

    return $options['inverse']();
});

// Each with IF
$engine->render('{{#each data}}{{#if this}}{{this}}{{/if}}{{/each}} {{finish}}', ['data' => [0,1,2,3],'finish'=>'ok']);
// Expected output:  '123 ok'
// Result: '123 ok'

// Each with WHEN
$engine->render('{{#each data}}{{#when this ">" "0"}}{{this}}{{/when}}{{/each}} {{finish}}', ['data' => [0,1,2,3],'finish'=>'ok']);
// Expected output: '123 ok'
// Result: '123'
// So {{finish}} is unavailable
everplays commented 7 years ago

@maan1234 I suggest to add break points in EachHelper before and after each to see if you can figure out what happens to context and probably debug it.

maan1234 commented 7 years ago

@everplays Thank you for the reply. I am not an hardcore debugger. var_dump is my debug-tool.

The context changes because of the #when helper. This does not happen with the build-in #if helper.

I am only using the library to add Handlebars templates in my project and not familiar with the libraries inner workings, so it is hard for me to debug it.

  protected 'stack' => 
    array (size=5)
      0 => 
        array (size=2)
          'data' => 
            array (size=4)
              ...
          'finish' => string 'ok' (length=2)
      1 => int 0 // = new at end of #each helper
      2 => int 1 // = new
      3 => int 2 // = new
      4 => int 3 // = new

It see that there are two pushes to the Context. One by #each and one by #when (by calling ['inverse']() or ['fn']() ). There is only one pop by #each. So it seems ['inverse']() or ['fn']() fail to clear their push() to the stack.

My guess it that Handlebars.php line 321 and line 362 should be changed from:

                    if ($defined) {
                        $inContext->pop();
                    }

to

                    if (!$defined) {
                        $inContext->pop();
                    }

So the push and pop are both applied in the same condition.

everplays commented 7 years ago

@maan1234 indeed that was the problem. I have added some test coverage for it as well. Thanks for reporting and debugging it.