diff --git a/CHANGELOG b/CHANGELOG index 071f850ecd..97ad620ae1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ * 1.27.0 (2016-XX-XX) + * deprecated Twig_LoaderInterface::getSource() (implement Twig_SourceContextLoaderInterface instead) * fixed the filesystem loader with relative paths * deprecated Twig_Node::getLine() in favor of Twig_Node::getTemplateLine() * deprecated Twig_Template::getSource() in favor of Twig_Template::getSourceContext() diff --git a/doc/api.rst b/doc/api.rst index 27af74f81c..fcb7a2f7e7 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -271,6 +271,8 @@ All loaders implement the ``Twig_LoaderInterface``:: * @param string $name string The name of the template to load * * @return string The template source code + * + * @deprecated since 1.27 (to be removed in 2.0), implement Twig_SourceContextLoaderInterface */ function getSource($name); @@ -295,6 +297,11 @@ All loaders implement the ``Twig_LoaderInterface``:: The ``isFresh()`` method must return ``true`` if the current cached template is still fresh, given the last modification time, or ``false`` otherwise. +.. note:: + + As of Twig 1.27, you should also implement + ``Twig_SourceContextLoaderInterface`` to avoid deprecation notices. + .. tip:: As of Twig 1.11.0, you can also implement ``Twig_ExistsLoaderInterface`` diff --git a/doc/deprecated.rst b/doc/deprecated.rst index 987a100b2f..6e6229984e 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -137,6 +137,7 @@ Interfaces * ``Twig_NodeInterface`` (use ``Twig_Node`` instead) * ``Twig_ParserInterface`` (use ``Twig_Parser`` instead) * ``Twig_ExistsLoaderInterface`` (merged with ``Twig_LoaderInterface``) +* ``Twig_SourceContextLoaderInterface`` (merged with ``Twig_LoaderInterface``) * ``Twig_TemplateInterface`` (use ``Twig_Template`` instead, and use those constants Twig_Template::ANY_CALL, Twig_Template::ARRAY_CALL, Twig_Template::METHOD_CALL) @@ -153,6 +154,10 @@ Loaders * As of Twig 1.x, ``Twig_Loader_String`` is deprecated and will be removed in 2.0. You can render a string via ``Twig_Environment::createTemplate()``. +* As of Twig 1.27, ``Twig_LoaderInterface::getSource()`` is deprecated. + Implement ``Twig_SourceContextLoaderInterface`` instead and use + ``getSourceContext()``. + Node Visitors ------------- diff --git a/doc/recipes.rst b/doc/recipes.rst index b871e01613..0f60f21e8b 100644 --- a/doc/recipes.rst +++ b/doc/recipes.rst @@ -417,7 +417,7 @@ We have created a simple ``templates`` table that hosts two templates: Now, let's define a loader able to use this database:: - class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface + class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface { protected $dbh; @@ -435,6 +435,16 @@ Now, let's define a loader able to use this database:: return $source; } + // Twig_SourceContextLoaderInterface as of Twig 1.27 + public function getSourceContext($name) + { + if (false === $source = $this->getValue('source', $name)) { + throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name)); + } + + return new Twig_Source($source, $name); + } + // Twig_ExistsLoaderInterface as of Twig 1.11 public function exists($name) { diff --git a/lib/Twig/Environment.php b/lib/Twig/Environment.php index 482dbc5f3f..edc60e9175 100644 --- a/lib/Twig/Environment.php +++ b/lib/Twig/Environment.php @@ -403,13 +403,7 @@ public function loadTemplate($name, $index = null) } if (!class_exists($cls, false)) { - $loader = $this->getLoader(); - if ($loader instanceof Twig_SourceContextLoaderInterface) { - $source = $loader->getSourceContext($name); - } else { - $source = new Twig_Source($loader->getSource($name), $name); - } - $content = $this->compileSource($source); + $content = $this->compileSource($this->getSourceContext($name)); if ($this->bcWriteCacheFile) { $this->writeCacheFile($key, $content); @@ -735,6 +729,10 @@ public function compileSource($source, $name = null) */ public function setLoader(Twig_LoaderInterface $loader) { + if (!$loader instanceof Twig_SourceContextLoaderInterface && 0 !== strpos(get_class($loader), 'Mock_Twig_LoaderInterface')) { + @trigger_error(sprintf('Twig loader "%s" should implement Twig_SourceContextLoaderInterface since version 1.27.', get_class($loader)), E_USER_DEPRECATED); + } + $this->loader = $loader; } @@ -752,6 +750,21 @@ public function getLoader() return $this->loader; } + /** + * Gets the source context for the given template name. + * + * @return Twig_Source + */ + public function getSourceContext($name) + { + $loader = $this->getLoader(); + if (!$loader instanceof Twig_SourceContextLoaderInterface) { + return new Twig_Source($loader->getSource($name), $name); + } + + return $loader->getSourceContext($name); + } + /** * Sets the default template charset. * diff --git a/lib/Twig/Extension/Core.php b/lib/Twig/Extension/Core.php index e953b8c492..ace4c076ea 100644 --- a/lib/Twig/Extension/Core.php +++ b/lib/Twig/Extension/Core.php @@ -1490,7 +1490,7 @@ function twig_include(Twig_Environment $env, $context, $template, $variables = a function twig_source(Twig_Environment $env, $name, $ignoreMissing = false) { try { - return $env->getLoader()->getSource($name); + return $env->getSourceContext($name)->getCode(); } catch (Twig_Error_Loader $e) { if (!$ignoreMissing) { throw $e; diff --git a/lib/Twig/Loader/Array.php b/lib/Twig/Loader/Array.php index 90221d5db2..4e9ddd2f82 100644 --- a/lib/Twig/Loader/Array.php +++ b/lib/Twig/Loader/Array.php @@ -21,7 +21,7 @@ * * @author Fabien Potencier */ -class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface +class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface { protected $templates = array(); @@ -51,6 +51,8 @@ public function setTemplate($name, $template) */ public function getSource($name) { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + $name = (string) $name; if (!isset($this->templates[$name])) { throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); @@ -59,6 +61,19 @@ public function getSource($name) return $this->templates[$name]; } + /** + * {@inheritdoc} + */ + public function getSourceContext($name) + { + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); + } + + return new Twig_Source($this->templates[$name], $name); + } + /** * {@inheritdoc} */ diff --git a/lib/Twig/Loader/Chain.php b/lib/Twig/Loader/Chain.php index 45ad32329e..f04de66d5f 100644 --- a/lib/Twig/Loader/Chain.php +++ b/lib/Twig/Loader/Chain.php @@ -47,6 +47,8 @@ public function addLoader(Twig_LoaderInterface $loader) */ public function getSource($name) { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + $exceptions = array(); foreach ($this->loaders as $loader) { if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { @@ -109,7 +111,11 @@ public function exists($name) } try { - $loader->getSource($name); + if ($loader instanceof Twig_SourceContextLoaderInterface) { + $loader->getSourceContext($name); + } else { + $loader->getSource($name); + } return $this->hasSourceCache[$name] = true; } catch (Twig_Error_Loader $e) { diff --git a/lib/Twig/Loader/Filesystem.php b/lib/Twig/Loader/Filesystem.php index d64656e137..26f6104420 100644 --- a/lib/Twig/Loader/Filesystem.php +++ b/lib/Twig/Loader/Filesystem.php @@ -138,6 +138,8 @@ public function prependPath($path, $namespace = self::MAIN_NAMESPACE) */ public function getSource($name) { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + return file_get_contents($this->findTemplate($name)); } diff --git a/lib/Twig/Loader/String.php b/lib/Twig/Loader/String.php index 00f507a4f5..46da4a4ede 100644 --- a/lib/Twig/Loader/String.php +++ b/lib/Twig/Loader/String.php @@ -27,16 +27,26 @@ * * @author Fabien Potencier */ -class Twig_Loader_String implements Twig_LoaderInterface, Twig_ExistsLoaderInterface +class Twig_Loader_String implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface { /** * {@inheritdoc} */ public function getSource($name) { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + return $name; } + /** + * {@inheritdoc} + */ + public function getSourceContext($name) + { + return new Twig_Source($name, $name); + } + /** * {@inheritdoc} */ diff --git a/lib/Twig/LoaderInterface.php b/lib/Twig/LoaderInterface.php index 544ea4e326..840ab14cb6 100644 --- a/lib/Twig/LoaderInterface.php +++ b/lib/Twig/LoaderInterface.php @@ -24,6 +24,8 @@ interface Twig_LoaderInterface * @return string The template source code * * @throws Twig_Error_Loader When $name is not found + * + * @deprecated since 1.27 (to be removed in 2.0), implement Twig_SourceContextLoaderInterface */ public function getSource($name); diff --git a/lib/Twig/SourceContextLoaderInterface.php b/lib/Twig/SourceContextLoaderInterface.php index 6e1c223376..9c60220a3e 100644 --- a/lib/Twig/SourceContextLoaderInterface.php +++ b/lib/Twig/SourceContextLoaderInterface.php @@ -9,6 +9,13 @@ * file that was distributed with this source code. */ +/** + * Adds a getSourceContext() method for loaders. + * + * @author Fabien Potencier + * + * @deprecated since 1.27 (to be removed in 3.0) + */ interface Twig_SourceContextLoaderInterface { /** diff --git a/lib/Twig/Test/IntegrationTestCase.php b/lib/Twig/Test/IntegrationTestCase.php index 18067668a2..301ea466a0 100644 --- a/lib/Twig/Test/IntegrationTestCase.php +++ b/lib/Twig/Test/IntegrationTestCase.php @@ -212,8 +212,7 @@ protected function doIntegrationTest($file, $message, $condition, $templates, $e foreach (array_keys($templates) as $name) { echo "Template: $name\n"; - $source = $loader->getSource($name); - echo $twig->compile($twig->parse($twig->tokenize($source, $name))); + echo $twig->compile($twig->parse($twig->tokenize($twig->getSourceContext($name), $name))); } } $this->assertEquals($expected, $output, $message.' (in '.$file.')'); diff --git a/test/Twig/Tests/EnvironmentTest.php b/test/Twig/Tests/EnvironmentTest.php index 0ff07616c5..0eba7307cd 100644 --- a/test/Twig/Tests/EnvironmentTest.php +++ b/test/Twig/Tests/EnvironmentTest.php @@ -71,9 +71,14 @@ public function escapingStrategyCallback($name) public function testGlobals() { + // to be removed in 2.0 + $loader = $this->getMockBuilder('Twig_EnvironmentTestLoaderInterface')->getMock(); + //$loader = $this->getMockBuilder(array('Twig_LoaderInterface', 'Twig_SourceContextLoaderInterface'))->getMock(); + $loader->expects($this->any())->method('getSourceContext')->will($this->returnValue(new Twig_Source('', ''))); + // globals can be added after calling getGlobals - $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig = new Twig_Environment($loader); $twig->addGlobal('foo', 'foo'); $twig->getGlobals(); $twig->addGlobal('foo', 'bar'); @@ -81,7 +86,7 @@ public function testGlobals() $this->assertEquals('bar', $globals['foo']); // globals can be modified after a template has been loaded - $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig = new Twig_Environment($loader); $twig->addGlobal('foo', 'foo'); $twig->getGlobals(); $twig->loadTemplate('index'); @@ -90,7 +95,7 @@ public function testGlobals() $this->assertEquals('bar', $globals['foo']); // globals can be modified after extensions init - $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig = new Twig_Environment($loader); $twig->addGlobal('foo', 'foo'); $twig->getGlobals(); $twig->getFunctions(); @@ -99,7 +104,8 @@ public function testGlobals() $this->assertEquals('bar', $globals['foo']); // globals can be modified after extensions and a template has been loaded - $twig = new Twig_Environment($loader = new Twig_Loader_Array(array('index' => '{{foo}}'))); + $arrayLoader = new Twig_Loader_Array(array('index' => '{{foo}}')); + $twig = new Twig_Environment($arrayLoader); $twig->addGlobal('foo', 'foo'); $twig->getGlobals(); $twig->getFunctions(); @@ -108,7 +114,7 @@ public function testGlobals() $globals = $twig->getGlobals(); $this->assertEquals('bar', $globals['foo']); - $twig = new Twig_Environment($loader); + $twig = new Twig_Environment($arrayLoader); $twig->getGlobals(); $twig->addGlobal('foo', 'bar'); $template = $twig->loadTemplate('index'); @@ -116,7 +122,7 @@ public function testGlobals() /* to be uncomment in Twig 2.0 // globals cannot be added after a template has been loaded - $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig = new Twig_Environment($loader); $twig->addGlobal('foo', 'foo'); $twig->getGlobals(); $twig->loadTemplate('index'); @@ -128,7 +134,7 @@ public function testGlobals() } // globals cannot be added after extensions init - $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig = new Twig_Environment($loader); $twig->addGlobal('foo', 'foo'); $twig->getGlobals(); $twig->getFunctions(); @@ -140,7 +146,7 @@ public function testGlobals() } // globals cannot be added after extensions and a template has been loaded - $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig = new Twig_Environment($loader); $twig->addGlobal('foo', 'foo'); $twig->getGlobals(); $twig->getFunctions(); @@ -153,7 +159,7 @@ public function testGlobals() } // test adding globals after a template has been loaded without call to getGlobals - $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + $twig = new Twig_Environment($loader); $twig->loadTemplate('index'); try { $twig->addGlobal('bar', 'bar'); @@ -445,11 +451,13 @@ public function testAddRuntimeLoader() protected function getMockLoader($templateName, $templateContent) { - $loader = $this->getMockBuilder('Twig_LoaderInterface')->getMock(); + // to be removed in 2.0 + $loader = $this->getMockBuilder('Twig_EnvironmentTestLoaderInterface')->getMock(); + //$loader = $this->getMockBuilder(array('Twig_LoaderInterface', 'Twig_SourceContextLoaderInterface'))->getMock(); $loader->expects($this->any()) - ->method('getSource') + ->method('getSourceContext') ->with($templateName) - ->will($this->returnValue($templateContent)); + ->will($this->returnValue(new Twig_Source($templateContent, $templateName))); $loader->expects($this->any()) ->method('getCacheKey') ->with($templateName) @@ -597,3 +605,7 @@ public function fromRuntime($name = 'bar') return $name; } } + +interface Twig_EnvironmentTestLoaderInterface extends Twig_LoaderInterface, Twig_SourceContextLoaderInterface +{ +} diff --git a/test/Twig/Tests/Loader/ArrayTest.php b/test/Twig/Tests/Loader/ArrayTest.php index 1369a6bd0c..fe009e2bea 100644 --- a/test/Twig/Tests/Loader/ArrayTest.php +++ b/test/Twig/Tests/Loader/ArrayTest.php @@ -11,6 +11,9 @@ class Twig_Tests_Loader_ArrayTest extends PHPUnit_Framework_TestCase { + /** + * @group legacy + */ public function testGetSource() { $loader = new Twig_Loader_Array(array('foo' => 'bar')); @@ -19,6 +22,7 @@ public function testGetSource() } /** + * @group legacy * @expectedException Twig_Error_Loader */ public function testGetSourceWhenTemplateDoesNotExist() @@ -28,6 +32,16 @@ public function testGetSourceWhenTemplateDoesNotExist() $loader->getSource('foo'); } + /** + * @expectedException Twig_Error_Loader + */ + public function testGetSourceContextWhenTemplateDoesNotExist() + { + $loader = new Twig_Loader_Array(array()); + + $loader->getSourceContext('foo'); + } + public function testGetCacheKey() { $loader = new Twig_Loader_Array(array('foo' => 'bar')); @@ -50,7 +64,7 @@ public function testSetTemplate() $loader = new Twig_Loader_Array(array()); $loader->setTemplate('foo', 'bar'); - $this->assertEquals('bar', $loader->getSource('foo')); + $this->assertEquals('bar', $loader->getSourceContext('foo')->getCode()); } public function testIsFresh() @@ -75,7 +89,7 @@ public function testTemplateReference() $loader = new Twig_Loader_Array(array('foo' => 'bar')); $loader->getCacheKey($name); - $loader->getSource($name); + $loader->getSourceContext($name); $loader->isFresh($name, time()); $loader->setTemplate($name, 'foobar'); } diff --git a/test/Twig/Tests/Loader/ChainTest.php b/test/Twig/Tests/Loader/ChainTest.php index 76cc3f1715..7818343d46 100644 --- a/test/Twig/Tests/Loader/ChainTest.php +++ b/test/Twig/Tests/Loader/ChainTest.php @@ -11,6 +11,9 @@ class Twig_Tests_Loader_ChainTest extends PHPUnit_Framework_TestCase { + /** + * @group legacy + */ public function testGetSource() { $loader = new Twig_Loader_Chain(array( @@ -35,7 +38,7 @@ public function testGetSourceContext() $this->assertSame('', $loader->getSourceContext('foo')->getPath()); $this->assertEquals('errors/index.html', $loader->getSourceContext('errors/index.html')->getName()); - $this->assertNull($loader->getSourceContext('errors/index.html')->getPath()); + $this->assertSame('', $loader->getSourceContext('errors/index.html')->getPath()); $this->assertEquals('baz', $loader->getSourceContext('errors/index.html')->getCode()); $this->assertEquals('errors/base.html', $loader->getSourceContext('errors/base.html')->getName()); @@ -46,6 +49,17 @@ public function testGetSourceContext() /** * @expectedException Twig_Error_Loader */ + public function testGetSourceContextWhenTemplateDoesNotExist() + { + $loader = new Twig_Loader_Chain(array()); + + $loader->getSourceContext('foo'); + } + + /** + * @group legacy + * @expectedException Twig_Error_Loader + */ public function testGetSourceWhenTemplateDoesNotExist() { $loader = new Twig_Loader_Chain(array()); @@ -79,17 +93,19 @@ public function testAddLoader() $loader = new Twig_Loader_Chain(); $loader->addLoader(new Twig_Loader_Array(array('foo' => 'bar'))); - $this->assertEquals('bar', $loader->getSource('foo')); + $this->assertEquals('bar', $loader->getSourceContext('foo')->getCode()); } public function testExists() { - $loader1 = $this->getMockBuilder('Twig_Loader_Array')->setMethods(array('exists', 'getSource'))->disableOriginalConstructor()->getMock(); + $loader1 = $this->getMockBuilder('Twig_Loader_Array')->setMethods(array('exists', 'getSourceContext'))->disableOriginalConstructor()->getMock(); $loader1->expects($this->once())->method('exists')->will($this->returnValue(false)); - $loader1->expects($this->never())->method('getSource'); + $loader1->expects($this->never())->method('getSourceContext'); - $loader2 = $this->getMockBuilder('Twig_LoaderInterface')->getMock(); - $loader2->expects($this->once())->method('getSource')->will($this->returnValue('content')); + // can be removed in 2.0 + $loader2 = $this->getMockBuilder('Twig_ChainTestLoaderInterface')->getMock(); + //$loader2 = $this->getMockBuilder(array('Twig_LoaderInterface', 'Twig_SourceContextLoaderInterface'))->getMock(); + $loader2->expects($this->once())->method('getSourceContext')->will($this->returnValue(new Twig_Source('content', 'index'))); $loader = new Twig_Loader_Chain(); $loader->addLoader($loader1); @@ -98,3 +114,7 @@ public function testExists() $this->assertTrue($loader->exists('foo')); } } + +interface Twig_ChainTestLoaderInterface extends Twig_LoaderInterface, Twig_SourceContextLoaderInterface +{ +} diff --git a/test/Twig/Tests/Loader/FilesystemTest.php b/test/Twig/Tests/Loader/FilesystemTest.php index 1e88b51e5c..c8cbaf7184 100644 --- a/test/Twig/Tests/Loader/FilesystemTest.php +++ b/test/Twig/Tests/Loader/FilesystemTest.php @@ -88,9 +88,9 @@ public function testPaths($basePath, $cacheKey, $rootPath) // do not use realpath here as it would make the test unuseful $this->assertEquals($cacheKey, str_replace('\\', '/', $loader->getCacheKey('@named/named_absolute.html'))); - $this->assertEquals("path (final)\n", $loader->getSource('index.html')); - $this->assertEquals("path (final)\n", $loader->getSource('@__main__/index.html')); - $this->assertEquals("named path (final)\n", $loader->getSource('@named/index.html')); + $this->assertEquals("path (final)\n", $loader->getSourceContext('index.html')->getCode()); + $this->assertEquals("path (final)\n", $loader->getSourceContext('@__main__/index.html')->getCode()); + $this->assertEquals("named path (final)\n", $loader->getSourceContext('@named/index.html')->getCode()); } public function getBasePaths() @@ -147,7 +147,7 @@ public function testFindTemplateExceptionNamespace() $loader->addPath($basePath.'/named', 'named'); try { - $loader->getSource('@named/nowhere.html'); + $loader->getSourceContext('@named/nowhere.html'); } catch (Exception $e) { $this->assertInstanceof('Twig_Error_Loader', $e); $this->assertContains('Unable to find template "@named/nowhere.html"', $e->getMessage()); @@ -162,11 +162,11 @@ public function testFindTemplateWithCache() $loader->addPath($basePath.'/named', 'named'); // prime the cache for index.html in the named namespace - $namedSource = $loader->getSource('@named/index.html'); + $namedSource = $loader->getSourceContext('@named/index.html')->getCode(); $this->assertEquals("named path\n", $namedSource); // get index.html from the main namespace - $this->assertEquals("path\n", $loader->getSource('index.html')); + $this->assertEquals("path\n", $loader->getSourceContext('index.html')->getCode()); } public function testLoadTemplateAndRenderBlockWithCache() @@ -221,6 +221,6 @@ public function testLoadTemplateFromPhar() // $f = new Phar('phar-test.phar'); // $f->addFromString('hello.twig', 'hello from phar'); $loader->addPath('phar://'.dirname(__FILE__).'/Fixtures/phar/phar-sample.phar'); - $this->assertSame('hello from phar', $loader->getSource('hello.twig')); + $this->assertSame('hello from phar', $loader->getSourceContext('hello.twig')->getCode()); } }