diff --git a/src/Mustache/Compiler.php b/src/Mustache/Compiler.php index 2b0d1f93..7da49d1e 100644 --- a/src/Mustache/Compiler.php +++ b/src/Mustache/Compiler.php @@ -322,7 +322,7 @@ private function block($nodes) } const SECTION_CALL = ' - $value = $context->%s(%s);%s + $value = $context->%s(%s%s);%s $buffer .= $this->section%s($context, $indent, $value); '; @@ -390,13 +390,14 @@ private function section($nodes, $id, $filters, $start, $end, $otag, $ctag, $lev $method = $this->getFindMethod($id); $id = var_export($id, true); + $findArg = $this->getFindMethodArgs($method); $filters = $this->getFilters($filters, $level); - return sprintf($this->prepare(self::SECTION_CALL, $level), $method, $id, $filters, $key); + return sprintf($this->prepare(self::SECTION_CALL, $level), $method, $id, $findArg, $filters, $key); } const INVERTED_SECTION = ' - $value = $context->%s(%s);%s + $value = $context->%s(%s%s);%s if (empty($value)) { %s } @@ -416,12 +417,13 @@ private function invertedSection($nodes, $id, $filters, $level) { $method = $this->getFindMethod($id); $id = var_export($id, true); + $findArg = $this->getFindMethodArgs($method); $filters = $this->getFilters($filters, $level); - return sprintf($this->prepare(self::INVERTED_SECTION, $level), $method, $id, $filters, $this->walk($nodes, $level)); + return sprintf($this->prepare(self::INVERTED_SECTION, $level), $method, $id, $findArg, $filters, $this->walk($nodes, $level)); } - const DYNAMIC_NAME = '$this->resolveValue($context->%s(%s), $context)'; + const DYNAMIC_NAME = '$this->resolveValue($context->%s(%s%s), $context)'; /** * Generate Mustache Template dynamic name resolution PHP source. @@ -437,12 +439,13 @@ private function resolveDynamicName($id, $dynamic) return var_export($id, true); } - $method = $this->getFindMethod($id); - $id = ($method !== 'last') ? var_export($id, true) : ''; + $method = $this->getFindMethod($id); + $id = ($method !== 'last') ? var_export($id, true) : ''; + $findArg = $this->getFindMethodArgs($method); // TODO: filters? - return sprintf(self::DYNAMIC_NAME, $method, $id); + return sprintf(self::DYNAMIC_NAME, $method, $id, $findArg); } const PARTIAL_INDENT = ', $indent . %s'; @@ -532,7 +535,7 @@ private static function onlyBlockArgs(array $node) } const VARIABLE = ' - $value = $this->resolveValue($context->%s(%s), $context);%s + $value = $this->resolveValue($context->%s(%s%s), $context);%s $buffer .= %s($value === null ? \'\' : %s); '; @@ -550,14 +553,15 @@ private function variable($id, $filters, $escape, $level) { $method = $this->getFindMethod($id); $id = ($method !== 'last') ? var_export($id, true) : ''; + $findArg = $this->getFindMethodArgs($method); $filters = $this->getFilters($filters, $level); $value = $escape ? $this->getEscape() : '$value'; - return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value); + return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $findArg, $filters, $this->flushIndent(), $value); } const FILTER = ' - $filter = $context->%s(%s); + $filter = $context->%s(%s%s); if (!(%s)) { throw new Mustache_Exception_UnknownFilterException(%s); } @@ -581,10 +585,11 @@ private function getFilters(array $filters, $level) $name = array_shift($filters); $method = $this->getFindMethod($name); $filter = ($method !== 'last') ? var_export($name, true) : ''; + $findArg = $this->getFindMethodArgs($method); $callable = $this->getCallable('$filter'); $msg = var_export($name, true); - return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilters($filters, $level)); + return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $findArg, $callable, $msg, $this->getFilters($filters, $level)); } const LINE = '$buffer .= "\n";'; @@ -681,6 +686,22 @@ private function getFindMethod($id) return 'findDot'; } + /** + * Get the args needed for a given find method. + * + * In this case, it's "true" iff it's a "find dot" method and strict callables is enabled. + * + * @param string $method Find method name + */ + private function getFindMethodArgs($method) + { + if (($method === 'findDot' || $method === 'findAnchoredDot') && $this->strictCallables) { + return ', true'; + } + + return ''; + } + const IS_CALLABLE = '!is_string(%s) && is_callable(%s)'; const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)'; diff --git a/src/Mustache/Context.php b/src/Mustache/Context.php index f06f12d3..4b193a94 100644 --- a/src/Mustache/Context.php +++ b/src/Mustache/Context.php @@ -125,18 +125,28 @@ public function find($id) * ... the `name` value is only searched for within the `child` value of the global Context, not within parent * Context frames. * - * @param string $id Dotted variable selector + * @param string $id Dotted variable selector + * @param bool $strictCallables (default: false) * * @return mixed Variable value, or '' if not found */ - public function findDot($id) + public function findDot($id, $strictCallables = false) { $chunks = explode('.', $id); $first = array_shift($chunks); $value = $this->findVariableInStack($first, $this->stack); + // This wasn't really a dotted name, so we can just return the value. + if (empty($chunks)) { + return $value; + } + foreach ($chunks as $chunk) { - if ($value === '') { + $isCallable = $strictCallables ? (is_object($value) && is_callable($value)) : (!is_string($value) && is_callable($value)); + + if ($isCallable) { + $value = $value(); + } elseif ($value === '') { return $value; }