Hi,
Started using 5.3 and stumbled into what appears to be a bug with the
__invoke() magic method. It works fine if used in an object for a class
defining __invoke() is stored in a local variable, however when storing said
object as an instance variable, a fatal is raised. See my example below.
<?php
class A
{
public function __invoke()
{
echo CLASS . PHP_EOL;
}
}
class B
{
private $a = null;
public function __construct()
{
$this->a = new A();
$this->a();
}
}
$a = new A();
$a();
$b = new B();
?>
Output:
A
Fatal error: Call to undefined method B::a() in
/Users/quickshiftin/gitRepos/timberline/ct-rip/test-callable.php on line 17
Expected Output:
A
A
thx,
-nathan
Hi!
public function __construct() { $this->a = new A(); $this->a();
Here you are calling method a() of object $this. Such method does not
exist. No bug here. Methods are properties are different things.
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
> Hi!
>
>
> public function __construct()
>> {
>> $this->a = new A();
>> $this->a();
>>
>
> Here you are calling method a() of object $this. Such method does not
> exist. No bug here. Methods are properties are different things.
Stas, thanks for this. Seems that w/ __invoke() in 5.3 if a method property
is_callable then the engine should check for that and call it. I realize
the problem now though, in php instance variables can have the same
identifier as instance methods therefore collisions could exist, for example
class B
{
function __invoke() {}
}
class A
{
public $c = null;
function c() {}
}
$a = new A();
$a->c = new B();
$a->c();
seems i got the notion this would work from javascript, however i also
realize the reason it works there, only a single identifier is allowed which
would map to either a scalar or a function object and in the later case
would be invokable.
the implementation in php make sense to me now.
thanks for your time,
-nathan
public function __construct() { $this->a = new A(); $this->a();
Here you are calling method a() of object $this. Such method does not
exist. No bug here. Methods are properties are different things.
I completely understand why it works the way it does. That said, with
the various new language features such as __invoke() and closures, it
feels like this architectural decision -- separating properties and
methods into their own hash tables -- should be revisited. Most other
dynamic languages do not allow having properties and methods of the same
name precisely to prevent the possibility of collisions -- and to allow
extensions to a class just like the above illustration.
The fact that a lot of folks playing with 5.3 are running into issues
like this and wondering why it doesn't work indicates that the design
needs to change to be more easily predictable. Assigning a closure or
other invokable to a class should "just work" -- developers shouldn't
need to know how the Zend Engine works in order to understand the
behavior and limitations.
Of course, making such a change would open up a number of other issues:
-
BC break for existing codebases that have properties and methods of
the same name. (That's a code smell, anyways, and tools can help
developers refactor to fix such cases.) -
What if method "a" exists, and a developer tries to re-assign it as a
closure or property? JavaScript and Ruby allow this; PHP wouldn't
necessarily need to -- e.g. could raise an exception. Whatever the
choice, just need to cleanly document the behavior. Again, potential
BC break.
I personally think the BC breaks here are worth it -- they make the
behavior more predictable, easier to document, and easier to understand.
--
Matthew Weier O'Phinney
Project Lead | matthew@zend.com
Zend Framework | http://framework.zend.com/
PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
Am 30.09.2010 14:20, schrieb Matthew Weier O'Phinney:
Assigning a closure or
other invokable to a class should "just work" -- developers shouldn't
need to know how the Zend Engine works in order to understand the
behavior and limitations.
Another example is the discussion about the right namespace separator.
:: runs into abiguity because there are different hash tables.
The fact that PHP core developers has chosen initialy :: as
namespace separator illustrates how hard to understand is
the concept of different hash tables.
I would prefer strongly unique identifiers for all types.
--
Thomas Gutbier
thomas.gutbier@anthrotec.de
Hi!
feels like this architectural decision -- separating properties and
methods into their own hash tables -- should be revisited. Most other
Having properties and methods in the same hash would make zero sense,
unless we convert all methods into closures - which I currently don't
feel we need to (though some languages do exactly that).
That, however, does not prevent us from prohibiting methods and vars
with the same name - what prevents us is described below.
needs to change to be more easily predictable. Assigning a closure or
other invokable to a class should "just work" -- developers shouldn't
need to know how the Zend Engine works in order to understand the
behavior and limitations.
That is a question of expectations - if you expect properties and
methods be the same, it "just works", if you don't - it does not.
Currently there are two barriers to making it work:
- Legitimate use-cases are easily done in user-land by using __call,
making all the effort kind of unnecessary. - It would create conflicting semantics in __get/__call which would
hugely complicate matters (e.g.: who gets called first and why? should
is_callable call __get? or maybe __isset? What happens if $this->a is a
string - should that work too? It works for $a... What if you have a
property named __get? etc, etc)
Without overcoming those barriers with some kind of solution I do not
see how we can make it work. Yes, I know it would be nice if we could
"just make it work", but unfortunately it's more complicated that that.
I feel "methods are different from properties" is much easier to
understand (even if one would wish it weren't so, it's still not hard to
comprehend) than "they are the same, except for a dozen of exceptions
and special cases when __get/__call is involved and they also different
from regular variables in these special cases, etc."
- BC break for existing codebases that have properties and methods of
the same name. (That's a code smell, anyways, and tools can help
developers refactor to fix such cases.)
I'm afraid it's not a good approach. If it breaks substantial amount of
code, nobody is going to use it. And then it doesn't matter what you
think about that code because all the work would be for nothing.
- What if method "a" exists, and a developer tries to re-assign it as a
closure or property? JavaScript and Ruby allow this; PHP wouldn't
necessarily need to -- e.g. could raise an exception. Whatever the
choice, just need to cleanly document the behavior. Again, potential
BC break.
I don't like it, for two reasons:
- Current patterns of PHP coding don't allow for raising exceptions
left and right - because people don't expect exceptions to come out of
ordinary things. That means the code would be very fragile - any
$foo->bar=1 turns into a bomb that could explode at any moment. It's
very hard to write robust code if you can't trust even a simple assignment. - What happens if you first assign closure to $this->a and then
integer? If you assigned closure to it it should work like a method,
right? Then it should prohibit assigning integer to it like methods do?
So you have assign-once variable? Quite a weird concept, don't you think?
Summarily, as I said, unfortunately generating consistent model that
would allow this use case to work without rewriting half of PHP is not
as easy as it seems. You (or anybody else) is welcome to try and prove
me wrong with a good RFC, though :) But take care to actually go a
number of steps beyond simplest use cases - that's where the trouble starts.
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
feels like this architectural decision -- separating properties and
methods into their own hash tables -- should be revisited. Most otherHaving properties and methods in the same hash would make zero sense,
unless we convert all methods into closures - which I currently don't
feel we need to (though some languages do exactly that).
That, however, does not prevent us from prohibiting methods and vars
with the same name - what prevents us is described below.needs to change to be more easily predictable. Assigning a closure or
other invokable to a class should "just work" -- developers shouldn't
need to know how the Zend Engine works in order to understand the
behavior and limitations.That is a question of expectations - if you expect properties and
methods be the same, it "just works", if you don't - it does not.
Currently there are two barriers to making it work:
- Legitimate use-cases are easily done in user-land by using __call,
making all the effort kind of unnecessary.
Using __call() for this, quite honestly, sucks. You have to define it in
each and every class you write -- leading to code duplication -- or have
all your classes inherit from a common object -- leading to an
inheritance nightmare.
Additionally, there are huge performance implications. The most flexible
variants utilize call_user_func_array()
inside __call(), which, from
profiling and benchmarking I've done, can be around 6x slower than
simply calling a function.
Combine the two situations, and I might as well simply avoid using the
features at all.
- It would create conflicting semantics in __get/__call which would
hugely complicate matters (e.g.: who gets called first and why? should
is_callable call __get? or maybe __isset? What happens if $this-> a is a
string - should that work too? It works for $a... What if you have a
property named __get? etc, etc)
I think these can all be answered.
-
Only dereference objects with __invoke() or closures; don't worry
about other callback types. -
If __get resolves a property that's invokable, dereference it and
invoke it. Anything else, simply treat as was done before -- and
fallback to __call().(That said, I realize where you're heading with this -- you now have
overhead when overloading, as you have to try first with __get then
__call, making resolution harder.) -
is_callable()
should call __get and determine if the return value is
callable. -
Don't allow defining magic methods as properties.
Without overcoming those barriers with some kind of solution I do not
see how we can make it work. Yes, I know it would be nice if we could
"just make it work", but unfortunately it's more complicated that that.
I feel "methods are different from properties" is much easier to
understand (even if one would wish it weren't so, it's still not hard to
comprehend) than "they are the same, except for a dozen of exceptions
and special cases when __get/__call is involved and they also different
from regular variables in these special cases, etc."
Closures and invokable objects blur the lines between properties and
methods, making the "methods are different from properties" mantra more
difficult to understand. My property is invokable -- why can't I invoke
it without first casting it to a temporary variable?
- BC break for existing codebases that have properties and methods of
the same name. (That's a code smell, anyways, and tools can help
developers refactor to fix such cases.)I'm afraid it's not a good approach. If it breaks substantial amount of
code, nobody is going to use it. And then it doesn't matter what you
think about that code because all the work would be for nothing.
It's a pipe dream, I know. :)
- What if method "a" exists, and a developer tries to re-assign it as a
closure or property? JavaScript and Ruby allow this; PHP wouldn't
necessarily need to -- e.g. could raise an exception. Whatever the
choice, just need to cleanly document the behavior. Again, potential
BC break.I don't like it, for two reasons:
- Current patterns of PHP coding don't allow for raising exceptions
left and right - because people don't expect exceptions to come out of
ordinary things. That means the code would be very fragile - any
$foo->bar=1 turns into a bomb that could explode at any moment. It's
very hard to write robust code if you can't trust even a simple assignment.- What happens if you first assign closure to $this->a and then
integer? If you assigned closure to it it should work like a method,
right? Then it should prohibit assigning integer to it like methods do?
So you have assign-once variable? Quite a weird concept, don't you think?
I'd argue that you can assign at any time. It's up to the manual and
instructors to ensure developers follow some best practices ("if you
need to rely on being able to call the closure, define it as a method,"
etc.)
Summarily, as I said, unfortunately generating consistent model that
would allow this use case to work without rewriting half of PHP is not
as easy as it seems. You (or anybody else) is welcome to try and prove
me wrong with a good RFC, though :) But take care to actually go a
number of steps beyond simplest use cases - that's where the trouble starts.
Exactly -- I know a number of the edge cases already.
I'll see if I can find some time after ZendCon to write something up...
and somebody willing to work with me to see if it's possible. (I can
write tests...)
--
Matthew Weier O'Phinney
Project Lead | matthew@zend.com
Zend Framework | http://framework.zend.com/
PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
Hi!
Using __call() for this, quite honestly, sucks. You have to define it in
each and every class you write -- leading to code duplication -- or have
all your classes inherit from a common object -- leading to an
inheritance nightmare.
That's why we (will) have traits :)
Additionally, there are huge performance implications. The most flexible
variants utilizecall_user_func_array()
inside __call(), which, from
profiling and benchmarking I've done, can be around 6x slower than
simply calling a function.
On this level, performance considerations don't matter too much. 1ns and
6ns are not that different, unless you are so CPU-bound that extra
function call breaks it - in which case you should write an extension or
do some caching.
I think these can all be answered.
- Only dereference objects with __invoke() or closures; don't worry
about other callback types.
Why not? It's quite unobvious why object invocation should be so
different from non-object invocation
- If __get resolves a property that's invokable, dereference it and
invoke it. Anything else, simply treat as was done before -- and
fallback to __call().
(That said, I realize where you're heading with this -- you now have
overhead when overloading, as you have to try first with __get then
__call, making resolution harder.)
Exactly. Any __call() should now be preceded with __get() - even though
99% of them would never get anything useful from it. And there would be
some very hard-to-debug bugs when __get would return something
unexpected and you wouldn't really know what ends up being called.
Closures and invokable objects blur the lines between properties and
methods, making the "methods are different from properties" mantra more
difficult to understand. My property is invokable -- why can't I invoke
it without first casting it to a temporary variable?
You can. Just not by using method call syntax. By any other means -
__invoke, call_user_function, etc. - you surely can.
Methods being different from properties is not a mantra, it's a fact. In
Javascript it's the reverse - methods actually are the same as
properties, but in PHP they are not.
--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
- BC break for existing codebases that have properties and methods of
the same name. (That's a code smell, anyways, and tools can help
developers refactor to fix such cases.)
Errrr. No.
Oft-times the property and method have the same name as the a "getter"
and I like it that way.
I don't really want to re-factor my code because you think I should
re-name everything all goofy. :-)
I personally think the BC breaks here are worth it -- they make the
behavior more predictable, easier to document, and easier to
understand.
If a developer can't understand that properties and methods are
different beasts, then I'm not sure they ought to be using OOP at
all...
But I'm an old Lisp hacker, and am more befuddled by JS et al cramming
everything into one bucket than this separation of church and state...
:-)
--
brain cancer update:
http://richardlynch.blogspot.com/search/label/brain%20tumor
Donate:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FS9NLTNEEKWBE