Twig:将对象变量从页面拉到自定义标签

I have making a custom twig tag called "story_get_adjacent" to get the next/prev articles based on the input id. But for the life of me I cant get the actual data from the object pulled into the tag for look up. it always gives me back the name not the data. I know this can be done because i tested it with the set tag and it returns the data not the name. Thoughts????

Object on page

Object >>
    Title = "This is a test story"
    StoryID = 1254
    Content ....

tag usage Example:

 {% story_get_adjacent Object.StoryID as adjacent %}

Twig Extension:

class Story_Get_Adjacent_TokenParser extends Twig_TokenParser
{
    public function parse(Twig_Token $token)
    {
        $parser = $this->parser; //story_get_adjacent
        $stream = $parser->getStream(); // space

        $value = $parser->getExpressionParser()->parseExpression(); //story id
        $names = array();
        try {
            $as = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //as
            $ObjName = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //object name
            array_push($names, $ObjName);
        } catch (Exception $e) {
            throw new Exception( 'error: ' . $e);
        }
        $stream->expect(Twig_Token::BLOCK_END_TYPE);
        return new Story_Get_Adjacent_Node($names, $value, $token->getLine(), $this->getTag());
    }

    public function getTag()
    {
        return 'story_get_adjacent';
    }
}

Twig Extension:

class Story_Get_Adjacent_Node extends Twig_Node
{
    public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
    {
        parent::__construct(array('value' => $value), array('name' => $name), $line, $tag);
    }

    public function compile (Twig_Compiler $compiler)
    {
        $Name     = $this->getAttribute('name')[0];
        $StoryAutoID    = $this->getNode('value')->getAttribute('value');

        $compiler->addDebugInfo($this);
        $compiler->write('$context[\''. $Name .'\'] = NewsController::TwigStoryGetAdjacent("'.$StoryAutoID.'");')->raw("
");
    }
}

The problem is located in the compiler, when you try to access the value attribute of your expression.

  1. The expression parser need to be subcompiled into an expression in PHP.
  2. As the compiled expression is a non-evaluated expression, you shouldn't put quotes ( ' ) when you call TwigStoryGetAdjacent.

Try with the following class:

<?php

class Story_Get_Adjacent_Node extends Twig_Node
{

    public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
    {
        parent::__construct(array ('value' => $value), array ('name' => $name), $line, $tag);
    }

    public function compile(Twig_Compiler $compiler)
    {
        $Name = reset($this->getAttribute('name'));

        $compiler->addDebugInfo($this);
        $compiler
           ->write("\$context['$Name'] = NewsController::TwigStoryGetAdjacent(")
           ->subcompile($this->getNode('value'))
           ->write(");")
           ->raw("
")
        ;
    }

}

Working demo

test.php

<?php

require(__DIR__ . '/vendor/autoload.php');

require("TestExtension.php");
require("TestTokenParser.php");
require("TestNode.php");

class NewsController
{

    static public function TwigStoryGetAdjacent($id)
    {
        return "I return the story ID = {$id}.";
    }

}

$loader = new Twig_Loader_Filesystem('./');
$twig = new Twig_Environment($loader, array (
        'cache' => __DIR__ . '/gen'
));

$object = new \stdClass;
$object->StoryID = 42;

$twig->addExtension(new Test_Extension());
echo $twig->render("test.twig", array ('Object' => $object));

test.twig

{% story_get_adjacent Object.StoryID as adjacent %}
{{ adjacent }}

TestExtension.php

<?php

class Test_Extension extends \Twig_Extension
{

    public function getTokenParsers()
    {
        return array (
                new Test_TokenParser(),
        );
    }

    public function getName()
    {
        return 'test';
    }

}

TestTokenParser.php

<?php

class Test_TokenParser extends Twig_TokenParser
{
    public function parse(Twig_Token $token)
    {
        $parser = $this->parser; //story_get_adjacent
        $stream = $parser->getStream(); // space

        $value = $parser->getExpressionParser()->parseExpression(); //story id
        $names = array();
        try {
            $as = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //as
            $ObjName = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //object name
            array_push($names, $ObjName);
        } catch (Exception $e) {
            throw new Exception( 'error: ' . $e);
        }
        $stream->expect(Twig_Token::BLOCK_END_TYPE);
        return new Test_Node($names, $value, $token->getLine(), $this->getTag());
    }

    public function getTag()
    {
        return 'story_get_adjacent';
    }
}

TestNode.php

<?php

class Test_Node extends Twig_Node
{

    public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
    {
        parent::__construct(array ('value' => $value), array ('name' => $name), $line, $tag);
    }

    public function compile(Twig_Compiler $compiler)
    {
        $Name = reset($this->getAttribute('name'));

        $compiler->addDebugInfo($this);
        $compiler
           ->write("\$context['$Name'] = NewsController::TwigStoryGetAdjacent(")
           ->subcompile($this->getNode('value'))
           ->write(");")
           ->raw("
")
        ;
    }

}

Run

$ composer require "twig/twig" "~1.0"
$ php test.php

Result

I return the story ID = 42.

Bonus The compiled doDisplay method corresponding to the template

    protected function doDisplay(array $context, array $blocks = array())
    {
        // line 1
        $context['adjacent'] = NewsController::TwigStoryGetAdjacent($this->getAttribute((isset($context["Object"]) ? $context["Object"] : null), "StoryID", array())        );
        // line 2
        echo twig_escape_filter($this->env, (isset($context["adjacent"]) ? $context["adjacent"] : null), "html", null, true);
    }

Alain Tiemblo solves your problem. However, why do you make your life so hard? Why don't you simply use a twig function?

{% set adjacent = story_get_adjacent(Object.StoryID) %}
class StoryExtension extends \Twig_Extension
{
    public function getFunctions()
    {
        return array(
            \Twig_SimpleFunction('story_get_adjacent', array($this, 'getAdjacent'), array('is_safe' => array('html'))),
        );
    }

    public function getAdjacent($storyId)
    {
        return NewsController::TwigStoryGetAdjacent($storyId);
    }

    public function getName()
    {
        return 'story';
    }
}

(the is_safe option tells twig it's safe HTML, so you don't have to disable escaping when using {{ story_get_adjacent(Object.StoryID) }}).