1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) Fabien Potencier
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12use Twig\Cache\FilesystemCache;
13use Twig\Environment;
14use Twig\Extension\AbstractExtension;
15use Twig\Extension\GlobalsInterface;
16use Twig\Extension\InitRuntimeInterface;
17use Twig\Loader\ArrayLoader;
18use Twig\Loader\LoaderInterface;
19use Twig\Loader\SourceContextLoaderInterface;
20use Twig\NodeVisitor\NodeVisitorInterface;
21use Twig\Source;
22use Twig\Token;
23use Twig\TokenParser\AbstractTokenParser;
24use Twig\TwigFilter;
25use Twig\TwigFunction;
26use Twig\TwigTest;
27
28require_once __DIR__.'/FilesystemHelper.php';
29
30class Twig_Tests_EnvironmentTest extends \PHPUnit\Framework\TestCase
31{
32    private $deprecations = [];
33
34    /**
35     * @group legacy
36     */
37    public function testLegacyTokenizeSignature()
38    {
39        $env = new Environment();
40        $stream = $env->tokenize('{{ foo }}', 'foo');
41        $this->assertEquals('{{ foo }}', $stream->getSource());
42        $this->assertEquals('foo', $stream->getFilename());
43    }
44
45    /**
46     * @group legacy
47     */
48    public function testLegacyCompileSourceSignature()
49    {
50        $loader = new ArrayLoader(['foo' => '{{ foo }}']);
51        $env = new Environment($loader);
52        $this->assertContains('getTemplateName', $env->compileSource('{{ foo }}', 'foo'));
53    }
54
55    /**
56     * @expectedException        \LogicException
57     * @expectedExceptionMessage You must set a loader first.
58     * @group legacy
59     */
60    public function testRenderNoLoader()
61    {
62        $env = new Environment();
63        $env->render('test');
64    }
65
66    public function testAutoescapeOption()
67    {
68        $loader = new ArrayLoader([
69            'html' => '{{ foo }} {{ foo }}',
70            'js' => '{{ bar }} {{ bar }}',
71        ]);
72
73        $twig = new Environment($loader, [
74            'debug' => true,
75            'cache' => false,
76            'autoescape' => [$this, 'escapingStrategyCallback'],
77        ]);
78
79        $this->assertEquals('foo&lt;br/ &gt; foo&lt;br/ &gt;', $twig->render('html', ['foo' => 'foo<br/ >']));
80        $this->assertEquals('foo\u003Cbr\/\u0020\u003E foo\u003Cbr\/\u0020\u003E', $twig->render('js', ['bar' => 'foo<br/ >']));
81    }
82
83    public function escapingStrategyCallback($name)
84    {
85        return $name;
86    }
87
88    public function testGlobals()
89    {
90        // to be removed in 2.0
91        $loader = $this->getMockBuilder('Twig_EnvironmentTestLoaderInterface')->getMock();
92        //$loader = $this->getMockBuilder(['\Twig\Loader\LoaderInterface', '\Twig\Loader\SourceContextLoaderInterface'])->getMock();
93        $loader->expects($this->any())->method('getSourceContext')->will($this->returnValue(new Source('', '')));
94
95        // globals can be added after calling getGlobals
96
97        $twig = new Environment($loader);
98        $twig->addGlobal('foo', 'foo');
99        $twig->getGlobals();
100        $twig->addGlobal('foo', 'bar');
101        $globals = $twig->getGlobals();
102        $this->assertEquals('bar', $globals['foo']);
103
104        // globals can be modified after a template has been loaded
105        $twig = new Environment($loader);
106        $twig->addGlobal('foo', 'foo');
107        $twig->getGlobals();
108        $twig->load('index');
109        $twig->addGlobal('foo', 'bar');
110        $globals = $twig->getGlobals();
111        $this->assertEquals('bar', $globals['foo']);
112
113        // globals can be modified after extensions init
114        $twig = new Environment($loader);
115        $twig->addGlobal('foo', 'foo');
116        $twig->getGlobals();
117        $twig->getFunctions();
118        $twig->addGlobal('foo', 'bar');
119        $globals = $twig->getGlobals();
120        $this->assertEquals('bar', $globals['foo']);
121
122        // globals can be modified after extensions and a template has been loaded
123        $arrayLoader = new ArrayLoader(['index' => '{{foo}}']);
124        $twig = new Environment($arrayLoader);
125        $twig->addGlobal('foo', 'foo');
126        $twig->getGlobals();
127        $twig->getFunctions();
128        $twig->load('index');
129        $twig->addGlobal('foo', 'bar');
130        $globals = $twig->getGlobals();
131        $this->assertEquals('bar', $globals['foo']);
132
133        $twig = new Environment($arrayLoader);
134        $twig->getGlobals();
135        $twig->addGlobal('foo', 'bar');
136        $template = $twig->load('index');
137        $this->assertEquals('bar', $template->render([]));
138
139        /* to be uncomment in Twig 2.0
140        // globals cannot be added after a template has been loaded
141        $twig = new Environment($loader);
142        $twig->addGlobal('foo', 'foo');
143        $twig->getGlobals();
144        $twig->load('index');
145        try {
146            $twig->addGlobal('bar', 'bar');
147            $this->fail();
148        } catch (\LogicException $e) {
149            $this->assertFalse(array_key_exists('bar', $twig->getGlobals()));
150        }
151
152        // globals cannot be added after extensions init
153        $twig = new Environment($loader);
154        $twig->addGlobal('foo', 'foo');
155        $twig->getGlobals();
156        $twig->getFunctions();
157        try {
158            $twig->addGlobal('bar', 'bar');
159            $this->fail();
160        } catch (\LogicException $e) {
161            $this->assertFalse(array_key_exists('bar', $twig->getGlobals()));
162        }
163
164        // globals cannot be added after extensions and a template has been loaded
165        $twig = new Environment($loader);
166        $twig->addGlobal('foo', 'foo');
167        $twig->getGlobals();
168        $twig->getFunctions();
169        $twig->load('index');
170        try {
171            $twig->addGlobal('bar', 'bar');
172            $this->fail();
173        } catch (\LogicException $e) {
174            $this->assertFalse(array_key_exists('bar', $twig->getGlobals()));
175        }
176
177        // test adding globals after a template has been loaded without call to getGlobals
178        $twig = new Environment($loader);
179        $twig->load('index');
180        try {
181            $twig->addGlobal('bar', 'bar');
182            $this->fail();
183        } catch (\LogicException $e) {
184            $this->assertFalse(array_key_exists('bar', $twig->getGlobals()));
185        }
186        */
187    }
188
189    public function testExtensionsAreNotInitializedWhenRenderingACompiledTemplate()
190    {
191        $cache = new FilesystemCache($dir = sys_get_temp_dir().'/twig');
192        $options = ['cache' => $cache, 'auto_reload' => false, 'debug' => false];
193
194        // force compilation
195        $twig = new Environment($loader = new ArrayLoader(['index' => '{{ foo }}']), $options);
196
197        $key = $cache->generateKey('index', $twig->getTemplateClass('index'));
198        $cache->write($key, $twig->compileSource(new Source('{{ foo }}', 'index')));
199
200        // check that extensions won't be initialized when rendering a template that is already in the cache
201        $twig = $this
202            ->getMockBuilder('\Twig\Environment')
203            ->setConstructorArgs([$loader, $options])
204            ->setMethods(['initExtensions'])
205            ->getMock()
206        ;
207
208        $twig->expects($this->never())->method('initExtensions');
209
210        // render template
211        $output = $twig->render('index', ['foo' => 'bar']);
212        $this->assertEquals('bar', $output);
213
214        Twig_Tests_FilesystemHelper::removeDir($dir);
215    }
216
217    public function testAutoReloadCacheMiss()
218    {
219        $templateName = __FUNCTION__;
220        $templateContent = __FUNCTION__;
221
222        $cache = $this->getMockBuilder('\Twig\Cache\CacheInterface')->getMock();
223        $loader = $this->getMockLoader($templateName, $templateContent);
224        $twig = new Environment($loader, ['cache' => $cache, 'auto_reload' => true, 'debug' => false]);
225
226        // Cache miss: getTimestamp returns 0 and as a result the load() is
227        // skipped.
228        $cache->expects($this->once())
229            ->method('generateKey')
230            ->will($this->returnValue('key'));
231        $cache->expects($this->once())
232            ->method('getTimestamp')
233            ->will($this->returnValue(0));
234        $loader->expects($this->never())
235            ->method('isFresh');
236        $cache->expects($this->once())
237            ->method('write');
238        $cache->expects($this->once())
239            ->method('load');
240
241        $twig->load($templateName);
242    }
243
244    public function testAutoReloadCacheHit()
245    {
246        $templateName = __FUNCTION__;
247        $templateContent = __FUNCTION__;
248
249        $cache = $this->getMockBuilder('\Twig\Cache\CacheInterface')->getMock();
250        $loader = $this->getMockLoader($templateName, $templateContent);
251        $twig = new Environment($loader, ['cache' => $cache, 'auto_reload' => true, 'debug' => false]);
252
253        $now = time();
254
255        // Cache hit: getTimestamp returns something > extension timestamps and
256        // the loader returns true for isFresh().
257        $cache->expects($this->once())
258            ->method('generateKey')
259            ->will($this->returnValue('key'));
260        $cache->expects($this->once())
261            ->method('getTimestamp')
262            ->will($this->returnValue($now));
263        $loader->expects($this->once())
264            ->method('isFresh')
265            ->will($this->returnValue(true));
266        $cache->expects($this->atLeastOnce())
267            ->method('load');
268
269        $twig->load($templateName);
270    }
271
272    public function testAutoReloadOutdatedCacheHit()
273    {
274        $templateName = __FUNCTION__;
275        $templateContent = __FUNCTION__;
276
277        $cache = $this->getMockBuilder('\Twig\Cache\CacheInterface')->getMock();
278        $loader = $this->getMockLoader($templateName, $templateContent);
279        $twig = new Environment($loader, ['cache' => $cache, 'auto_reload' => true, 'debug' => false]);
280
281        $now = time();
282
283        $cache->expects($this->once())
284            ->method('generateKey')
285            ->will($this->returnValue('key'));
286        $cache->expects($this->once())
287            ->method('getTimestamp')
288            ->will($this->returnValue($now));
289        $loader->expects($this->once())
290            ->method('isFresh')
291            ->will($this->returnValue(false));
292        $cache->expects($this->once())
293            ->method('write');
294        $cache->expects($this->once())
295            ->method('load');
296
297        $twig->load($templateName);
298    }
299
300    /**
301     * @group legacy
302     */
303    public function testHasGetExtensionWithDynamicName()
304    {
305        $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock());
306
307        $ext1 = new Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName('ext1');
308        $ext2 = new Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName('ext2');
309        $twig->addExtension($ext1);
310        $twig->addExtension($ext2);
311
312        $this->assertTrue($twig->hasExtension('ext1'));
313        $this->assertTrue($twig->hasExtension('ext2'));
314
315        $this->assertTrue($twig->hasExtension('Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName'));
316
317        $this->assertSame($ext1, $twig->getExtension('ext1'));
318        $this->assertSame($ext2, $twig->getExtension('ext2'));
319    }
320
321    public function testHasGetExtensionByClassName()
322    {
323        $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock());
324        $twig->addExtension($ext = new Twig_Tests_EnvironmentTest_Extension());
325        $this->assertTrue($twig->hasExtension('Twig_Tests_EnvironmentTest_Extension'));
326        $this->assertTrue($twig->hasExtension('\Twig_Tests_EnvironmentTest_Extension'));
327
328        $this->assertSame($ext, $twig->getExtension('Twig_Tests_EnvironmentTest_Extension'));
329        $this->assertSame($ext, $twig->getExtension('\Twig_Tests_EnvironmentTest_Extension'));
330
331        $this->assertTrue($twig->hasExtension('Twig\Tests\EnvironmentTest\Extension'));
332        $this->assertSame($ext, $twig->getExtension('Twig\Tests\EnvironmentTest\Extension'));
333    }
334
335    public function testAddExtension()
336    {
337        $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock());
338        $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension());
339
340        $this->assertArrayHasKey('test', $twig->getTags());
341        $this->assertArrayHasKey('foo_filter', $twig->getFilters());
342        $this->assertArrayHasKey('foo_function', $twig->getFunctions());
343        $this->assertArrayHasKey('foo_test', $twig->getTests());
344        $this->assertArrayHasKey('foo_unary', $twig->getUnaryOperators());
345        $this->assertArrayHasKey('foo_binary', $twig->getBinaryOperators());
346        $this->assertArrayHasKey('foo_global', $twig->getGlobals());
347        $visitors = $twig->getNodeVisitors();
348        $found = false;
349        foreach ($visitors as $visitor) {
350            if ($visitor instanceof Twig_Tests_EnvironmentTest_NodeVisitor) {
351                $found = true;
352            }
353        }
354        $this->assertTrue($found);
355    }
356
357    /**
358     * @requires PHP 5.3
359     */
360    public function testAddExtensionWithDeprecatedGetGlobals()
361    {
362        $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock());
363        $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension_WithGlobals());
364
365        $this->deprecations = [];
366        set_error_handler([$this, 'handleError']);
367
368        $this->assertArrayHasKey('foo_global', $twig->getGlobals());
369
370        $this->assertCount(1, $this->deprecations);
371        $this->assertContains('Defining the getGlobals() method in the "Twig_Tests_EnvironmentTest_Extension_WithGlobals" extension ', $this->deprecations[0]);
372
373        restore_error_handler();
374    }
375
376    /**
377     * @group legacy
378     */
379    public function testRemoveExtension()
380    {
381        $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock());
382        $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension_WithDeprecatedName());
383        $twig->removeExtension('environment_test');
384
385        $this->assertArrayNotHasKey('test', $twig->getTags());
386        $this->assertArrayNotHasKey('foo_filter', $twig->getFilters());
387        $this->assertArrayNotHasKey('foo_function', $twig->getFunctions());
388        $this->assertArrayNotHasKey('foo_test', $twig->getTests());
389        $this->assertArrayNotHasKey('foo_unary', $twig->getUnaryOperators());
390        $this->assertArrayNotHasKey('foo_binary', $twig->getBinaryOperators());
391        $this->assertArrayNotHasKey('foo_global', $twig->getGlobals());
392        $this->assertCount(2, $twig->getNodeVisitors());
393    }
394
395    public function testAddMockExtension()
396    {
397        // should be replaced by the following in 2.0 (this current code is just to avoid a dep notice)
398        // $extension = $this->getMockBuilder('\Twig\Extension\AbstractExtension')->getMock();
399        $extension = eval(<<<EOF
400use Twig\Extension\AbstractExtension;
401
402class Twig_Tests_EnvironmentTest_ExtensionInEval extends AbstractExtension
403{
404}
405EOF
406        );
407        $extension = new Twig_Tests_EnvironmentTest_ExtensionInEval();
408
409        $loader = new ArrayLoader(['page' => 'hey']);
410
411        $twig = new Environment($loader);
412        $twig->addExtension($extension);
413
414        $this->assertInstanceOf('\Twig\Extension\ExtensionInterface', $twig->getExtension(\get_class($extension)));
415        $this->assertTrue($twig->isTemplateFresh('page', time()));
416    }
417
418    public function testInitRuntimeWithAnExtensionUsingInitRuntimeNoDeprecation()
419    {
420        $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock());
421        $twig->addExtension(new Twig_Tests_EnvironmentTest_ExtensionWithoutDeprecationInitRuntime());
422        $twig->initRuntime();
423
424        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
425        // can be executed without throwing any deprecations
426        $this->addToAssertionCount(1);
427    }
428
429    /**
430     * @requires PHP 5.3
431     */
432    public function testInitRuntimeWithAnExtensionUsingInitRuntimeDeprecation()
433    {
434        $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock());
435        $twig->addExtension(new Twig_Tests_EnvironmentTest_ExtensionWithDeprecationInitRuntime());
436
437        $this->deprecations = [];
438        set_error_handler([$this, 'handleError']);
439
440        $twig->initRuntime();
441
442        $this->assertCount(1, $this->deprecations);
443        $this->assertContains('Defining the initRuntime() method in the "Twig_Tests_EnvironmentTest_ExtensionWithDeprecationInitRuntime" extension is deprecated since version 1.23.', $this->deprecations[0]);
444
445        restore_error_handler();
446    }
447
448    public function handleError($type, $msg)
449    {
450        if (E_USER_DEPRECATED === $type) {
451            $this->deprecations[] = $msg;
452        }
453    }
454
455    /**
456     * @requires PHP 5.3
457     */
458    public function testOverrideExtension()
459    {
460        $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock());
461        $twig->addExtension(new Twig_Tests_EnvironmentTest_ExtensionWithDeprecationInitRuntime());
462
463        $this->deprecations = [];
464        set_error_handler([$this, 'handleError']);
465
466        $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension_WithDeprecatedName());
467        $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension_WithDeprecatedName());
468
469        $this->assertCount(1, $this->deprecations);
470        $this->assertContains('The possibility to register the same extension twice', $this->deprecations[0]);
471
472        restore_error_handler();
473    }
474
475    public function testAddRuntimeLoader()
476    {
477        $runtimeLoader = $this->getMockBuilder('\Twig\RuntimeLoader\RuntimeLoaderInterface')->getMock();
478        $runtimeLoader->expects($this->any())->method('load')->will($this->returnValue(new Twig_Tests_EnvironmentTest_Runtime()));
479
480        $loader = new ArrayLoader([
481            'func_array' => '{{ from_runtime_array("foo") }}',
482            'func_array_default' => '{{ from_runtime_array() }}',
483            'func_array_named_args' => '{{ from_runtime_array(name="foo") }}',
484            'func_string' => '{{ from_runtime_string("foo") }}',
485            'func_string_default' => '{{ from_runtime_string() }}',
486            'func_string_named_args' => '{{ from_runtime_string(name="foo") }}',
487        ]);
488
489        $twig = new Environment($loader);
490        $twig->addExtension(new Twig_Tests_EnvironmentTest_ExtensionWithoutRuntime());
491        $twig->addRuntimeLoader($runtimeLoader);
492
493        $this->assertEquals('foo', $twig->render('func_array'));
494        $this->assertEquals('bar', $twig->render('func_array_default'));
495        $this->assertEquals('foo', $twig->render('func_array_named_args'));
496        $this->assertEquals('foo', $twig->render('func_string'));
497        $this->assertEquals('bar', $twig->render('func_string_default'));
498        $this->assertEquals('foo', $twig->render('func_string_named_args'));
499    }
500
501    /**
502     * @expectedException \Twig\Error\RuntimeError
503     * @expectedExceptionMessage Circular reference detected for Twig template "base.html.twig", path: base.html.twig -> base.html.twig in "base.html.twig" at line 1
504     */
505    public function testFailLoadTemplateOnCircularReference()
506    {
507        $twig = new Environment(new ArrayLoader([
508            'base.html.twig' => '{% extends "base.html.twig" %}',
509        ]));
510
511        $twig->load('base.html.twig');
512    }
513
514    /**
515     * @expectedException \Twig\Error\RuntimeError
516     * @expectedExceptionMessage Circular reference detected for Twig template "base1.html.twig", path: base1.html.twig -> base2.html.twig -> base1.html.twig in "base1.html.twig" at line 1
517     */
518    public function testFailLoadTemplateOnComplexCircularReference()
519    {
520        $twig = new Environment(new ArrayLoader([
521            'base1.html.twig' => '{% extends "base2.html.twig" %}',
522            'base2.html.twig' => '{% extends "base1.html.twig" %}',
523        ]));
524
525        $twig->load('base1.html.twig');
526    }
527
528    protected function getMockLoader($templateName, $templateContent)
529    {
530        // to be removed in 2.0
531        $loader = $this->getMockBuilder('Twig_EnvironmentTestLoaderInterface')->getMock();
532        //$loader = $this->getMockBuilder(['\Twig\Loader\LoaderInterface', '\Twig\Loader\SourceContextLoaderInterface'])->getMock();
533        $loader->expects($this->any())
534          ->method('getSourceContext')
535          ->with($templateName)
536          ->will($this->returnValue(new Source($templateContent, $templateName)));
537        $loader->expects($this->any())
538          ->method('getCacheKey')
539          ->with($templateName)
540          ->will($this->returnValue($templateName));
541
542        return $loader;
543    }
544}
545
546class Twig_Tests_EnvironmentTest_Extension_WithGlobals extends AbstractExtension
547{
548    public function getGlobals()
549    {
550        return [
551            'foo_global' => 'foo_global',
552        ];
553    }
554}
555
556class Twig_Tests_EnvironmentTest_Extension extends AbstractExtension implements GlobalsInterface
557{
558    public function getTokenParsers()
559    {
560        return [
561            new Twig_Tests_EnvironmentTest_TokenParser(),
562        ];
563    }
564
565    public function getNodeVisitors()
566    {
567        return [
568            new Twig_Tests_EnvironmentTest_NodeVisitor(),
569        ];
570    }
571
572    public function getFilters()
573    {
574        return [
575            new TwigFilter('foo_filter', 'foo_filter'),
576        ];
577    }
578
579    public function getTests()
580    {
581        return [
582            new TwigTest('foo_test', 'foo_test'),
583        ];
584    }
585
586    public function getFunctions()
587    {
588        return [
589            new TwigFunction('foo_function', 'foo_function'),
590        ];
591    }
592
593    public function getOperators()
594    {
595        return [
596            ['foo_unary' => []],
597            ['foo_binary' => []],
598        ];
599    }
600
601    public function getGlobals()
602    {
603        return [
604            'foo_global' => 'foo_global',
605        ];
606    }
607}
608class_alias('Twig_Tests_EnvironmentTest_Extension', 'Twig\Tests\EnvironmentTest\Extension', false);
609
610class Twig_Tests_EnvironmentTest_Extension_WithDeprecatedName extends AbstractExtension
611{
612    public function getName()
613    {
614        return 'environment_test';
615    }
616}
617
618class Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName extends AbstractExtension
619{
620    private $name;
621
622    public function __construct($name)
623    {
624        $this->name = $name;
625    }
626
627    public function getName()
628    {
629        return $this->name;
630    }
631}
632
633class Twig_Tests_EnvironmentTest_TokenParser extends AbstractTokenParser
634{
635    public function parse(Token $token)
636    {
637    }
638
639    public function getTag()
640    {
641        return 'test';
642    }
643}
644
645class Twig_Tests_EnvironmentTest_NodeVisitor implements NodeVisitorInterface
646{
647    public function enterNode(Twig_NodeInterface $node, Environment $env)
648    {
649        return $node;
650    }
651
652    public function leaveNode(Twig_NodeInterface $node, Environment $env)
653    {
654        return $node;
655    }
656
657    public function getPriority()
658    {
659        return 0;
660    }
661}
662
663class Twig_Tests_EnvironmentTest_ExtensionWithDeprecationInitRuntime extends AbstractExtension
664{
665    public function initRuntime(Environment $env)
666    {
667    }
668}
669
670class Twig_Tests_EnvironmentTest_ExtensionWithoutDeprecationInitRuntime extends AbstractExtension implements InitRuntimeInterface
671{
672    public function initRuntime(Environment $env)
673    {
674    }
675}
676
677class Twig_Tests_EnvironmentTest_ExtensionWithoutRuntime extends AbstractExtension
678{
679    public function getFunctions()
680    {
681        return [
682            new TwigFunction('from_runtime_array', ['Twig_Tests_EnvironmentTest_Runtime', 'fromRuntime']),
683            new TwigFunction('from_runtime_string', 'Twig_Tests_EnvironmentTest_Runtime::fromRuntime'),
684        ];
685    }
686
687    public function getName()
688    {
689        return 'from_runtime';
690    }
691}
692
693class Twig_Tests_EnvironmentTest_Runtime
694{
695    public function fromRuntime($name = 'bar')
696    {
697        return $name;
698    }
699}
700
701// to be removed in 2.0
702interface Twig_EnvironmentTestLoaderInterface extends LoaderInterface, SourceContextLoaderInterface
703{
704}
705