FAQ
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

Search Discussions

  • Stas Malyshev at Sep 29, 2010 at 8:56 pm
    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
  • Nathan Nobbe at Sep 29, 2010 at 9:29 pm

    On Wed, Sep 29, 2010 at 2:56 PM, Stas Malyshev wrote:

    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
  • Matthew Weier O'Phinney at Sep 30, 2010 at 12:20 pm

    On 2010-09-29, Stas Malyshev wrote:
    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
  • Thomas Gutbier at Sep 30, 2010 at 3:30 pm

    Am 30.09.2010 14:20, schrieb Matthew Weier O'Phinney:
    On 2010-09-29, Stas Malyshevwrote:
    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
  • Stas Malyshev at Sep 30, 2010 at 6:34 pm
    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:

    1. Legitimate use-cases are easily done in user-land by using __call,
    making all the effort kind of unnecessary.
    2. 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:
    1. 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.
    2. 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
  • Matthew Weier O'Phinney at Sep 30, 2010 at 7:49 pm

    On 2010-09-30, Stas Malyshev wrote:
    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:

    1. 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.
    2. 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:
    1. 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.
    2. 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
  • Stas Malyshev at Sep 30, 2010 at 8:08 pm
    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 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.
    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
  • Richard Lynch at Oct 3, 2010 at 12:03 pm

    On Thu, September 30, 2010 7:20 am, Matthew Weier O'Phinney wrote:
    On 2010-09-29, Stas Malyshev wrote:
    * 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...
    :-)

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupphp-internals @
categoriesphp
postedSep 29, '10 at 7:44p
activeOct 3, '10 at 12:03p
posts9
users5
websitephp.net

People

Translate

site design / logo © 2022 Grokbase