added support for __call() in expression resolution (closes #2)
authorFabien Potencier <fabien.potencier@gmail.com>
Mon, 10 May 2010 10:46:52 +0000 (12:46 +0200)
committerFabien Potencier <fabien.potencier@gmail.com>
Mon, 10 May 2010 10:47:39 +0000 (12:47 +0200)
The new algorithm for object method resolution is now the following:

  * get all methods declared for `$object` using ReflectionObject::getMethods()
  * check if `$item` is in the list of method names
  * check if `get$item` is in the list of method names
  * check if `__call` is defined and pass `$item` as the method name

CHANGELOG
lib/Twig/Resource.php
test/fixtures/expressions/magic_call.test [new file with mode: 0644]

index c005080..34ddfa1 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,7 @@
 * 0.9.6-DEV
 
+ * added support for __call() in expression resolution
+ * fixed node visiting for macros (macros are now visited by visitors as any other node)
  * fixed nested block definitions with a parent call (rarely useful but nonetheless supported now)
  * added the cycle filter
  * fixed the Lexer when mbstring.func_overload is used with an mbstring.internal_encoding different from ASCII
index d33e4cd..37058ad 100644 (file)
 abstract class Twig_Resource
 {
   protected $env;
+  protected $cache;
 
   public function __construct(Twig_Environment $env)
   {
     $this->env = $env;
+    $this->cache = array();
   }
 
   public function getEnvironment()
@@ -36,12 +38,12 @@ abstract class Twig_Resource
       return $object[$item];
     }
 
-    if ($arrayOnly)
+    if ($arrayOnly || !is_object($object))
     {
       return null;
     }
 
-    if (is_object($object) && isset($object->$item))
+    if (isset($object->$item))
     {
       if ($this->env->hasExtension('sandbox'))
       {
@@ -51,13 +53,32 @@ abstract class Twig_Resource
       return $object->$item;
     }
 
-    if (
-      !is_object($object) ||
-      (
-        !method_exists($object, $method = $item) &&
-        !method_exists($object, $method = 'get'.$item)
-      )
-    )
+    $class = get_class($object);
+
+    if (!isset($this->cache[$class]))
+    {
+      $r = new ReflectionClass($class);
+      $this->cache[$class] = array();
+      foreach ($r->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_FINAL) as $method)
+      {
+        $this->cache[$class][strtolower($method->getName())] = true;
+      }
+    }
+
+    $item = strtolower($item);
+    if (isset($this->cache[$class][$item]))
+    {
+      $method = $item;
+    }
+    elseif (isset($this->cache[$class]['get'.$item]))
+    {
+      $method = 'get'.$item;
+    }
+    elseif (isset($this->cache[$class]['__call']))
+    {
+      $method = $item;
+    }
+    else
     {
       return null;
     }
diff --git a/test/fixtures/expressions/magic_call.test b/test/fixtures/expressions/magic_call.test
new file mode 100644 (file)
index 0000000..159db96
--- /dev/null
@@ -0,0 +1,27 @@
+--TEST--
+Twig supports __call() for attributes
+--TEMPLATE--
+{{ foo.foo }}
+{{ foo.bar }}
+--DATA--
+class TestClassForMagicCallAttributes
+{
+  public function getBar()
+  {
+    return 'bar_from_getbar';
+  }
+
+  public function __call($method, $arguments)
+  {
+    if ('foo' === $method)
+    {
+      return 'foo_from_call';
+    }
+
+    return false;
+  }
+}
+return array('foo' => new TestClassForMagicCallAttributes())
+--EXPECT--
+foo_from_call
+bar_from_getbar