User

You are logged in as Anonymous.

Want to log out?

My friend Paul has a cool service called Wonderproxy that lets you test and develop GeoIP-based apps without the normal headaches. If you need to simulate remote, international traffic, you should check it out.

Schizophrenic Methods

Occasionally, it is useful for a developer to determine if a method is being called statically (not in an object context -- Class::method() ), or "not statically" (in an object context -- $object->method()).

This is normally (but incorrectly) done by checking $this:

[php] class Foo { function bar() { echo "bar() called: " . (isset($this) ? 'non-statically' : 'statically'); } } [/php]

Why the "but incorrectly", you might ask?

A few weeks ago, I started maintaining PEAR::Mail_Mime -- it had a lot of reported bugs, and nobody was really taking care of the package. (I'm going to release 1.3.0RC1 within the next couple weeks)

Anyway, without getting too far off-topic, one of the bugs was "Fatal error: Using $this when not in object context."

Basically, the code was checking for $this->mailMimeDecode, but when called statically, $this was unset.

My fix was to check if $this was set, but once committed, Jan Schneider sent me mail telling me that my patch would not work if the method was called statically from within another object.

This hadn't even occurred to me, so I did some testing (and eventually updated the manual).

Here's the scenario:

[php] class A { function foo() { if (isset($this)) { echo '$this is defined ('; echo get_class($this); echo ")\n"; } else { echo "\$this is not defined.\n"; } } } class B { function bar() { A::foo(); } } $a = new A(); $a->foo(); A::foo(); $b = new B(); $b->bar(); B::bar(); [/php]

The output:

[code] $this is defined (a) $this is not defined. $this is defined (b) $this is not defined. [/code]

As you can see (if you have the human parser module installed (-: ), $this is defined, and is the calling object, even when a method is called statically (but from the context of another object).

So (and here's my point), how does a developer determine if a given method is called statically? Here's what I came up with (and is in Mail_Mime - CVS):

[php] $isStatic = !(isset($this) && get_class($this) == __CLASS__); [/php]

It seems a little hackish to be using __CLASS__, but nothing else came to mind, and it works in every test I came up with.

Side note: When I stuck this stuff in the manual, its place in the oop4 docs is pretty good, but in the oop5 docs, I don't like that it's in The Basics but I don't know where else to put it. So, if anyone has a good suggestion, let me know.


22 Responses to Schizophrenic Methods

  1. 14 geoffrey 2005-02-16 03:18

    nice trick.

    why would it be hackish at all to use __CLASS__ ? you do nothing more than using what the language offers :)

  2. 15 ryan king 2005-02-16 03:51

    A bit shorter, perhaps more hacky?
    [code]
    class A
    {
    function A()
    {
    var_dump((@get_class($this) == __CLASS__));
    }
    }

    class B
    {
    function B()
    {
    A::A();
    }
    }

    $b = new B();

    [/code]

  3. 16 Sean Coates 2005-02-16 10:11

    Maybe hackish was a bad choice of word.
    I meant that it's unfortunate that there's no (apparent(?)) in-language way to determine the context of a method call without jumping through hoops.

    S

  4. 17 Sean Coates 2005-02-16 10:13

    @errorsuppression is notoriously slow (look up "ifsetor" on php.internals), so while this is shorter, it's not nearly as efficient.

    (I believe that when using @errorsuppression, PHP (internally) sets the error_reporting to E_NONE on call, and reverts it when the call is finished.. but I could be wrong).

    S

  5. 18 ryan king 2005-02-16 12:20

    I didn't know that about error suppression- probably because I never actually use it. :-)

  6. 19 Greg Beaver 2005-02-16 19:09

    This is why it is ALWAYS a bad idea to get cute with code and try to allow methods to be called both by instances and statically. PHP5 has the right idea when raising E_STRICT on a static call of a non-static method.

    The real solution is to introduce

    staticMethod()
    method()

    for methods that need both formats.

    For instance, PEAR::raiseError() is often used in a context where $this->raiseError() would make more sense. Objects that subclass PEAR always call raiseError() as a local method. If I were to suddenly change this behavior, well

    *bewm*

    byebye BC :)

  7. 20 Sean Coates 2005-02-16 20:09

    Greg,

    This was exactly the reason why I had to hack up Mail_mimeDecode::decode() (and also the reason that second "m" is lowercase).

    I agree, though. When possible, use two different methods. Keep the Schizophrenia to a minimum.

    S

  8. 21 ryan king 2005-02-16 20:29

    I agree that this sort of schizphrenia needs to be avoided. Yet, perhaps there should still be a less hacky way to test for being static. Perhaps something like __STATIC__, which would be a boolean condition?

    (OTOH, this difficulty may well discourage people from this schizo practice).

  9. 22 Richard Heyes 2005-02-19 10:36

    If you're now maintaining Mail_Mime, you should look in CVS under the 2.0 folder; there's the beginning of a re-organisation of the code in there.

  10. 23 Sean Coates 2005-02-19 11:00

    Hey Richard.

    http://cvs.php.net/pear/Mail_Mime/2.0/ seems quite empty to me. Am I just missing something?

    Also, because of PEAR's (silly) naming conventions, it'll have to become Mail_Mime2.

    Thanks for the original codebase, BTW (-:
    S

  11. 24 Richard Heyes 2005-02-19 11:16

    Yeah I had plans to maintain the code elsewhere, so I deleted it. However it never really went anywhere beyond the code in there, so feel free to resurrect the files.

    http://cvs.php.net/pear/Mail_Mime/2.0/?sa=1

    > Thanks for the original codebase, BTW (-:

    Yw.

  12. 25 Sean Coates 2005-02-19 11:29

    Any chance you [url=http://news.php.net/php.pear.dev/36248]know about UTF-8[/url]? (had to ask)

    S

  13. 26 Richard Heyes 2005-02-19 11:43

    No idea I'm afraid. UTF-x make my head spin. :-)

  14. 27 peter 2005-05-17 07:56

    I don't see why the call to foo from B (i.e. A::foo) has $this defined. There should be no instance variables defined within the call, so a reference to $this->variable ought to fail.

  15. 28 Lukasz 2005-06-04 05:04

    You should rather do:

    $static = isset($this)&&is_a($this,__CLASS__);

    That is because, when you deliver a class your statis dispatcher won't work, because get_clas will give you in result name of the child class and the __CLASS__ will still give you the name of the class where you have yours static/dynamic method.

    I hope you understood me.

  16. 29 Sebastian Mendel 2005-07-21 16:13

    it does not work, if the statically called method
    is from the same class as the calling object

    class foo
    {
    function bar()
    {
    if ( isset( $this ) && get_class( $this ) == __CLASS__ )
    {
    return 'not static';
    }
    return 'static';
    }

    function bar2() { return foo::bar(); }
    }

    // returns 'static'
    echo foo::bar();

    // returns 'not static' but should be 'static'
    $foo = new foo;
    echo $foo->bar2();

  17. 30 ia 2005-09-21 10:21

    Strict Standards: Non-static method foo::bar() should not be called statically in /network/webroot/dev/test.php on line 38

  18. 31 Xixulon Gralgrathor 2005-11-09 10:31

    I suppose many of you would not find this 'neat' programming - and I am new at this, so you may be right, but I would really like a way to determine the class of a method that is being called statically.

    for instance:

    [code]
    class One {
    function Stat() {
    echo __CLASS__, ", ", isset($this)?get_class($this):"x", "\n";
    }
    }

    class Two extends One
    {
    }

    Two::Stat();
    [/code]

    returns 'One' and zilch, while I'd like to know where the function call came from and in what format the call should be returned. The only other way I can think of is by actually giving the classname to the statically defined function as an extra parameter, and that would really screw up the metre of my code.

    Anybody have any nice ideas?

  19. 32 Dima 2006-02-16 04:12

    I think must be an error when static method called dynamically.

    [code]class Test
    {
    public static function fnTest()
    { echo __CLASS__,'::',__FUNCTION__,"\n"; }
    }
    $obj= new Test;
    $obj->fnTest(); // IMHO must raise fatal error
    [/code]

  20. 33 japsai 2007-10-19 13:28

    Hi, just wanted to say that schizophrenia and double / mutiple personality issues have very little in common. Therefore, it may come across as disrespectful when you use the name of such a terrible disorder in this context. Not to flame your blog, but it's just a serious issue that should be cleared for many people's sake.

  21. 34 Vladislav Rastrusny 2008-01-18 04:42

    In latest PHP your method of determining static does not work:

    Test();

    MyClass::Test();

    ?>

    $this is always not defined if method is marked as static in spite of being called dynamically.

  22. 35 Santer 2008-02-16 19:32

    Vladislav, you are mistaken.
    This example works correctly in PHP 5.2.5

Leave a Reply



Clicky Web Analytics