phptal / PHPTAL

PHP Template Attribute Language — template engine for XSS-proof well-formed XHTML and HTML5 pages
http://phptal.org
GNU Lesser General Public License v2.1
175 stars 43 forks source link

Ability to use closures in TAL variables #20

Closed ajcrites closed 11 years ago

ajcrites commented 11 years ago

I think it would be very handy to able pass closures to PHPTAL variables, or to objects that are themselves passed to PHPTAL, such as:

$tpl->foon = function () { return 'barn'; };

Currently PHPTAL will try to concatenate $ctx->foon, which results in "Object of Closure could not be converted to string."

You can get around this via:

//template
<tal:block content="func:foon"/>
//php
function phptal_tales_func($src, $nothrow) {
    return 'call_user_func(' . phptal_tales($src, $nothrow) . ')';
}

...but I think this is behavior we can actually build into PHPTAL. I will implement myself if I get positive feedback about this.

kornelski commented 11 years ago

That's a very good idea. Go for it!

tanakahisateru commented 11 years ago

Is the test able to pass when PHP5.2?

ajcrites commented 11 years ago

@tanakahisateru it won't work in PHP5.2 because the closures are not part of the PHP5.2 syntax; this doesn't mean PHPTAL as a whole will not work with PHP5.2, though.

Is there a way to specify that a test will only work for a certain PHP version?

kornelski commented 11 years ago

Since this relies on new syntax in PHP5.3, it'll have to be inside eval() and the test can check phpversion() and call $this->markTestSkipped().

ajcrites commented 11 years ago

I don't understand what you mean about eval(). What has to be inside eval() and where?

kornelski commented 11 years ago
if (false) {
    $var->foo = function(){}
}

will give syntax error on PHP 5.2, because it looks at entire file first and won't understand closure syntax.

if (false) {
    eval('$var->foo = function(){}');
}

will work in both 5.2 and 5.3.

This way you can put closures in the test.

ajcrites commented 11 years ago

Okay, I understand now, thanks.

ajcrites commented 11 years ago

I'm trying to install 5.2.17 on ubuntu using https://github.com/chh/php-build , but no matter what I try I keep getting configure: error: libjpeg.(a|so) not found. Any suggestions?

kornelski commented 11 years ago

Have you tried apt-get install libjpeg-dev (or libjpeg or libpeg-devel)?

ajcrites commented 11 years ago

Yes I had to do that to be able to install 5.4 and 5.3. I also tried creating a link from /usr/lib/i386-linux-gnu/ to /usr/lib for the .so and .a, but no dice.

kornelski commented 11 years ago

Try ./configure --without-jpeg

ajcrites commented 11 years ago

I still haven't had any luck with that installation, but the changes should be done on the tales_closure branch if someone else would like to confirm that the PHP 5.2.17 tests work. Otherwise I'll create a pull request unless anyone has any comments.

kornelski commented 11 years ago

I think if ($var instanceof Closure) should work in 5.2 (and return false). This will save you from checking PHP version and is better style than is_a().

tanakahisateru commented 11 years ago

I suggest is_object($var) && is_callable($var) because callable object might not in Closure type hierarchy. PHP5.3 let any object be Invokable by magic method.

http://www.php.net/manual/en/language.oop5.magic.php#object.invoke

And it should be tough against future version of PHP which would be shipped some Non-Closure callable object.

I tested below:

class InvokableClass {
    public function __invoke() { return true; }
}

$std = new stdClass;
echo is_callable($std); // =>false

$invokable = new InvokableClass;
echo is_callable($invokable); // =>true
echo ($invokable instanceof Closure) // =>false
ajcrites commented 11 years ago

@tanakahisateru My plan was to only check whether the expression is a closure, and as far as I know this can only be done by checking whether it extends from Closure. If there is another way that would be great,

The reason I chose to avoid using strictly "callability" is because up till now PHPTAL converts provided expressions to PHP's equivalents of primitives (string, boolean, etc.). Consider

class InvokableClass {
    public function __invoke() { return true; }
    public function __toString() { return "true"; }
}

However, if we agree that "if the expression is an object and is invokable it should be invoked" as a standard we can do that. I think that the is_object check satisfies the possibility of a non-object being passed to is_callable, and it returns true for anonymous functions.

ajcrites commented 11 years ago

Created d30117d in a separate branch to implement @tanakahisateru suggestion

kornelski commented 11 years ago

Fixed