Search code examples
phpfunctiontemplatespass-by-referencetwig

twig functions and pass-by-reference variables


Here is the situation:

{% for entry in entries %}
{{ action('entry_modify', entry) }}
{{ entry.title }}
{% endfor %}

action($action, $params) method is some sort of trigger for plugins that registered themselves for an action. In this case, i call for 'entry_modify' action and a plugin responds to this action with 'entry' parameter which is an array.

function plugin(&$params)
{
    $params['title'] = 'changed to ...'; 
}

But as far as i understand Twig is not passes variables by reference, is there any way to do this ?

All i want is just modify the variable passed to action function.

Thanks...


Solution

  • I might have a solution for your problem. I'm developing a Wordpress Theme with Twig and I wanted to pass variables by reference and add some data to them. My goal was to make something like this

    {{ load_data('all_posts',foo) }}
    

    and I wanted to execute a function named load_all_posts and the result of this function to be assigned to foo(the twig variable I passed as a second argument). Also I wanted the following to also work:

    {% set foo = {'bar':'bar'} %}
    
    {#foo is#}
    array(1) {
      ["bar"]=>
      string(3) "bar"
    }
    
    {{load_data('all_posts',foo.posts)}}
    
    {#foo is#}
    array(2) {
      ["bar"]=>
      string(3) "bar"
      ["posts"]=>
      array(3) {
        [0]=>
        string(5) "post1"
        [1]=>
        string(5) "post2"
        [2]=>
        string(5) "post3"
      }
    }
    

    Since not all variables are passed by reference to the functions. We can't use functions for this purpose. Instead I created my own tag and used it like so:

    {% set foo = {'bar':'bar'} %}
    {% load all_pots in foo.posts %}
    

    To do the following we have to do 2 things:

    • create the "load" token
    • call the correct function and load the data at the correct place

    Creating the token parser:

    The creation of the token is easy. We just need to tell twig how to parse this token with the following class:

    class Load_Data_TokenParser  extends Twig_TokenParser
    {
        public function parse(Twig_Token $token)
        {
            $stream = $this->parser->getStream();
            $variable_array = array(); //an array where we'll store the destination variable, as it can be just foo or foo.post, or foo.bar.foo.bar.posts
    
            $function_name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();  //here we say that we expect a name. In our case it'll be 'all_posts' (the name of the function we will execute)
            $in = $stream->expect(Twig_Token::OPERATOR_TYPE)->getValue();   //here we say that the next thing should be the operator 'in'
            while(Twig_Token::typeToEnglish($stream->getCurrent()->getType()) === 'name'){ //and with this while, we get all the variable names that are separated with '.'
                array_push($variable_array,$stream->getCurrent()->getValue());
    
                $stream->next();
                $new  = $stream->getCurrent();
    
                if(Twig_Token::typeToEnglish($new->getType()) === 'punctuation'){
                    $stream->next();
                } else {
                    break;
                }
            }
            //in our case $variable_array here will be ['foo','posts']
    
            $stream->expect(Twig_Token::BLOCK_END_TYPE);
    
            return new Load_Node($variable_array, $function_name, $token->getLine(), $this->getTag());
        }
    
        public function getTag()
        {
            return 'load';
        }
    }
    

    And at the end we return a new "Load_Node" and passing it the $variable_array, $function_name the line and the current tag.

    Loading the data

    class Load_Node extends Twig_Node
    {
        public function __construct($variable_array, $function_name, $line, $tag = null)
        {
            $value = new Twig_Node();
            parent::__construct(array('value'=>$value),array(
                'variable_array'=>$variable_array,
                'function_name'=>$function_name
            ),$line,$tag);
        }
    
        public function compile(Twig_Compiler $compiler)
        {
            $variable_access_string = '';
            $variable_array = $this->getAttribute('variable_array');
            foreach ($variable_array as $single_variable) {
                $variable_access_string.= '[\''.$single_variable.'\']';
            } //here we prepare the string that will be used to access the correct variables. In our case here $variable_access_string will be "['foo']['posts']"
    
            $compiler
                ->addDebugInfo($this)
                ->write('$context'.$variable_access_string.' = load_'.$this->getAttribute('function_name').'()')
                ->raw(";\n"); //and the here we call load_{function_name} and assign the result to the correct variable
        }
    }
    

    Currently it works only with arrays. If you try to load data inside an object it throws an exception. I'll edit it later and add support for objects. I hope this will help you and other people. Have a nice day :)