diff --git a/CHANGELOG b/CHANGELOG index 97ad620ae15..87d05f1797b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ * 1.27.0 (2016-XX-XX) + * fixed regression when registering two extensions having the same class name * 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() diff --git a/lib/Twig/Environment.php b/lib/Twig/Environment.php index edc60e91756..fd2660f67b6 100644 --- a/lib/Twig/Environment.php +++ b/lib/Twig/Environment.php @@ -49,7 +49,7 @@ class Twig_Environment private $bcWriteCacheFile = false; private $bcGetCacheFilename = false; private $lastModifiedExtension = 0; - private $legacyExtensionNames = array(); + private $extensionsByClass = array(); private $runtimeLoaders = array(); private $runtimes = array(); private $optionsHash; @@ -816,12 +816,16 @@ public function initRuntime() */ public function hasExtension($class) { - if (isset($this->legacyExtensionNames[$class])) { - $class = $this->legacyExtensionNames[$class]; - @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + $class = ltrim($class, '\\'); + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + return true; } - return isset($this->extensions[ltrim($class, '\\')]); + return isset($this->extensionsByClass[ltrim($class, '\\')]); } /** @@ -841,18 +845,21 @@ public function addRuntimeLoader(Twig_RuntimeLoaderInterface $loader) */ public function getExtension($class) { - if (isset($this->legacyExtensionNames[$class])) { - $class = $this->legacyExtensionNames[$class]; - @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); - } - $class = ltrim($class, '\\'); - if (!isset($this->extensions[$class])) { + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + return $this->extensions[$class]; + } + + if (!isset($this->extensionsByClass[$class])) { throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $class)); } - return $this->extensions[$class]; + return $this->extensionsByClass[$class]; } /** @@ -886,25 +893,21 @@ public function getRuntime($class) */ public function addExtension(Twig_ExtensionInterface $extension) { - $class = get_class($extension); - if ($this->extensionInitialized) { - throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class)); + throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName())); } - $m = new ReflectionMethod($extension, 'getName'); - $legacyName = 'Twig_Extension' !== $m->getDeclaringClass()->getName() ? $extension->getName() : null; - - if (isset($this->extensions[$class]) || (null !== $legacyName && isset($this->legacyExtensionNames[$legacyName]))) { - unset($this->extensions[$this->legacyExtensionNames[$legacyName]], $this->legacyExtensionNames[$legacyName]); - @trigger_error(sprintf('The possibility to register the same extension twice ("%s") is deprecated since version 1.23 and will be removed in Twig 2.0. Use proper PHP inheritance instead.', $class), E_USER_DEPRECATED); + $class = get_class($extension); + if ($class !== $extension->getName()) { + if (isset($this->extensions[$extension->getName()])) { + unset($this->extensions[$extension->getName()], $this->extensionsByClass[$class]); + @trigger_error(sprintf('The possibility to register the same extension twice ("%s") is deprecated since version 1.23 and will be removed in Twig 2.0. Use proper PHP inheritance instead.', $extension->getName()), E_USER_DEPRECATED); + } } $this->lastModifiedExtension = 0; - if ($legacyName !== $class) { - $this->legacyExtensionNames[$legacyName] = $class; - } - $this->extensions[$class] = $extension; + $this->extensionsByClass[$class] = $extension; + $this->extensions[$extension->getName()] = $extension; $this->updateOptionsHash(); } @@ -921,16 +924,20 @@ public function removeExtension($name) { @trigger_error(sprintf('The %s method is deprecated since version 1.12 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - if (isset($this->legacyExtensionNames[$name])) { - $name = $this->legacyExtensionNames[$name]; - @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $name), E_USER_DEPRECATED); - } - if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name)); } - unset($this->extensions[ltrim($name, '\\')]); + $class = ltrim($name, '\\'); + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + unset($this->extensions[$class]); + } + + unset($this->extensions[$class]); $this->updateOptionsHash(); } diff --git a/test/Twig/Tests/EnvironmentTest.php b/test/Twig/Tests/EnvironmentTest.php index 0eba7307cdc..340ec453b47 100644 --- a/test/Twig/Tests/EnvironmentTest.php +++ b/test/Twig/Tests/EnvironmentTest.php @@ -277,6 +277,27 @@ public function testAutoReloadOutdatedCacheHit() $twig->loadTemplate($templateName); } + /** + * @group legacy + */ + public function testHasGetExtensionWithDynamicName() + { + $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); + + $ext1 = new Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName('ext1'); + $ext2 = new Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName('ext2'); + $twig->addExtension($ext1); + $twig->addExtension($ext2); + + $this->assertTrue($twig->hasExtension('ext1')); + $this->assertTrue($twig->hasExtension('ext2')); + + $this->assertTrue($twig->hasExtension('Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName')); + + $this->assertSame($ext1, $twig->getExtension('ext1')); + $this->assertSame($ext2, $twig->getExtension('ext2')); + } + public function testHasGetExtensionByClassName() { $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()); @@ -538,6 +559,21 @@ public function getName() } } +class Twig_Tests_EnvironmentTest_Extension_DynamicWithDeprecatedName extends Twig_Extension +{ + private $name; + + public function __construct($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } +} + class Twig_Tests_EnvironmentTest_TokenParser extends Twig_TokenParser { public function parse(Twig_Token $token)