-
-
Save funkatron/1942528 to your computer and use it in GitHub Desktop.
<?php | |
$app->foo = function() { echo 'test'; die;}; | |
$app->foo(); // ERROR | |
$f = $app->foo; | |
$f(); // SUCCESS | |
/********************************************************* | |
Here's a modification to __call that would let you call | |
dynamically assigned methods in the normal fashion. This | |
is of limited uselfulness in PHP 5.3 because you can't | |
reference $this | |
**********************************************************/ | |
<?php | |
class Foo | |
{ | |
public function baz($txt) { | |
echo "BAZ: $txt\n"; | |
} | |
public function __call($name, $arguments) { | |
if (isset($this->{$name}) && $this->{$name} instanceof Closure) { | |
$m = $this->{$name}; | |
return call_user_func_array($m, $arguments); | |
} | |
} | |
} | |
$foo = new Foo(); | |
$foo->bar = function($txt) { echo "BAR: $txt\n"; }; | |
$foo->bar('This method was dynamically added'); | |
$foo->baz('This method was defined in the class'); | |
/********************************************************* | |
Here's a version for PHP 5.4 that binds the scope at | |
call time, letting us use $this within the Closure | |
**********************************************************/ | |
trait Call_Dynamic_Methods { | |
public function __call($name, $arguments) { | |
if (isset($this->{$name}) && $this->{$name} instanceof Closure) { | |
$this->{$name} = $this->{$name}->bindTo($this, $this); | |
return call_user_func_array($this->{$name}, $arguments); | |
} | |
return parent::__call($name, $arguments); | |
} | |
} | |
class Foo54 | |
{ | |
use Call_Dynamic_Methods; | |
public $thing = 'blazzoooo'; | |
public function baz($txt) { | |
echo "BAZ: $txt\n"; | |
} | |
} | |
$foo = new Foo54(); | |
$foo->baz('This method was defined in the class'); | |
$foo->bar = function($txt) { echo "BAR: $txt\n"; }; | |
$foo->bar('This method was dynamically added after instantiation'); | |
$foo->bam = function($txt) { echo "BAM: $txt (" . $this->thing . ")\n"; }; | |
$foo->bam('This method was dynamically added after instantiation, and references | |
$this. It will be bound at __call time to set the scope'); | |
$flar = 'poop'; | |
$foo->bal = function($txt) use ($flar) { echo "BAL: $txt (" . $this->thing . ") ($flar)\n"; }; | |
$flar = 'poop2'; | |
$foo->bal('This will output "poop" and not "poop2"'); |
Interestingly, despite a bunch of fixes surrounding dereferencing in PHP 5.4, the above still does not work. However, if you change to:
$app->foo = array(function() { echo 'test'; };
$app->foo[0]();
It works. But that seems like a silly way to get around it.
yeah, exactly. I had hit on that array ref call before, but it's pretty silly.
Doesn't appear to support use
$app->foo = function($lastname) use ($firstname) {
echo 'My name is ' . $lastname . ', ' . $firstname . PHP_EOL;
};
baileylo: it should. note that use () vars are bound at definition time
$flar = 'poop';
$foo->bal = function($txt) use ($flar) { echo "BAL: $txt (" . $this->thing . ") ($flar)\n"; };
$flar = 'poop2';
$foo->bal('This will output "poop" and not "poop2"');
If you're using 5.4, I'd do that __call() implementation as a trait; otherwise, you end up needing to either have a "base" object implementation all your classes inherit, or cut-and-paste that everywhere. :)
Yep, makes sense to me.
@frunkatron - yeah, I don't know why it wasn't working for me. Just retried and now I feel like a big dumb idiot.
don't worry, I feel that way most of the time.
Wouldn't this open up some meta programming style capabilities? I don't have PHP 5.4 installed, so I wasn't able to test this, but something like: https://gist.github.com/1945478
On a similar note: javascript-like objects in php: https://gist.github.com/1410650
This was an attempt I made roughly 7 months ago:
https://gist.github.com/1180947
Maybe it's useful, though one should add the object's class name as
second argument to bindTo
, to make protected
and private
members
accessible.
If you're interested funkatron - there was actually a conversation on the mailing list (and in the wiki) about this. There are technical reasons why it was done this way. If you can come up with a way to fix it that doesn't result in run-time slowdown, patches are welcome (in other words, this wasn't lazy core devs, this was how the hell do we do it without slowing down the engine?)
@aurorarose: That doesn't surprise me -- I imagined it was a limitation of the way PHP works. I'm guessing the overhead I'm introducing in the __call method is not insignificant.
A couple thoughts:
- In many cases, I'm comfortable sacrificing performance for this kind of flexibility. I wish it was something I could choose to use.
- I think the "patches welcome" thing is tough. For one, I and the vast majority of people who have invested in PHP just don't have the necessary skill to offer patches. For two, you know as well as I do that getting something through the gauntlet to be accepted is taxing on a number of levels. It would be significantly easier for me to simply learn a new language (Python, Ruby et al) than to seriously make a run at this.
And maybe in the end, that's what I really need to do. shrug It's not an exciting prospect, though.
Yes, __call introduces a LOT of overhead.
It's kind of hard to say "just add it in I'll take the performance hit" because frankly most people don't feel that way, and even something like an ini switch (ewww) can introduce slowdown. Maintaining a patch might be an option, but then you run into the issue of it not running on a "stock" PHP
However I believe you'll find in python or ruby there are just as many edge cases and weirdnesses as any other language. I once thought as you do "there is a better language out there" ... then I actually used them. Bottom line? They all have their warts - plus with some you run into the issue of being a "designed language" so just adding or changing it can be far more political then you realize ;) However you may find less about another language you want to change (but somehow I doubt it, you hate what you use because you know more about it)
I've seen the C guts of many projects now and I'm sooo amazed, contrary to popular belief there is very little pretty code anywhere in programming ;)
As for "patches welcome" - the skills can be learned. C is really not that hard. Yes working on the language lexer or parser might require some research and some mind bending craziness, but extensions and basic stuff is pretty darn easy. And no you don't have to take a class or even invest that much time.
Side note: PHP is going to be on git after 5.4 (official mirror is here on github already) so doing experimental fixes and patches just got a lot easier as well.
P.S. you'd do people a LOT of good by putting some of these "gotcha" examples in the php documentation - edit.php.net - go go go!
Of course the skills can be learned. But learnability is not the only measure of ease.
I agree, all languages have warts. Some warts matter more than others, depending on the individual. PHP fundamentally is missing something I really want, though – first class functions. I am fluent in at least one language that offers this, and I find it extremely useful. That's precisely what I'm running up against here.
Just for thoroughness - here's the technical reason in a nutshell - remember that properties and methods can have the same names in PHP classes https://gist.github.com/1951282
Appreciate your insights into it, Elizabeth. Ultimately, I think functional languages are a Big Win, and it's always a bummer to come up against the limitations in PHP related to functional programming. Actually making it happen in the existing runtime is, I'm sure, very non-trivial.
is this still the case in the 5.4RC?