Skip to content

Instantly share code, notes, and snippets.

@funkatron
Created February 29, 2012 17:10
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save funkatron/1942528 to your computer and use it in GitHub Desktop.
Save funkatron/1942528 to your computer and use it in GitHub Desktop.
Half-done features in PHP, pt 1
<?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"');
@timw4mail
Copy link

is this still the case in the 5.4RC?

@weierophinney
Copy link

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.

@funkatron
Copy link
Author

yeah, exactly. I had hit on that array ref call before, but it's pretty silly.

@baileylo
Copy link

Doesn't appear to support use

$app->foo = function($lastname) use ($firstname) {
  echo 'My name is ' . $lastname . ', '  . $firstname  . PHP_EOL;
};

@funkatron
Copy link
Author

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"');

@weierophinney
Copy link

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. :)

@funkatron
Copy link
Author

Yep, makes sense to me.

@baileylo
Copy link

@frunkatron - yeah, I don't know why it wasn't working for me. Just retried and now I feel like a big dumb idiot.

@funkatron
Copy link
Author

don't worry, I feel that way most of the time.

@ziadoz
Copy link

ziadoz commented Feb 29, 2012

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

@timw4mail
Copy link

On a similar note: javascript-like objects in php: https://gist.github.com/1410650

@CHH
Copy link

CHH commented Mar 1, 2012

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.

@auroraeosrose
Copy link

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?)

@funkatron
Copy link
Author

@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:

  1. In many cases, I'm comfortable sacrificing performance for this kind of flexibility. I wish it was something I could choose to use.
  2. 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.

@auroraeosrose
Copy link

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!

@funkatron
Copy link
Author

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.

@auroraeosrose
Copy link

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

@funkatron
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment