简体   繁体   中英

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...

Twig functions are always passed objects by reference. You might consider using an object instead of an array to represent each entry.

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 :)

If you replace your associative array by a simple class instance you're just fine.

class Entry {
    public $title;
    // other properties possible
    __construct($title) {
        $this->title = $title
    }
}

then your $entries would be an array of Entry instances that will be passed as reference to any function because it's an object, not an array. Your original twig code snippet would be fine then (perfect syntax), and would modify the title.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM