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.
S
Side note: When I stuck this stuff in the manual, it's 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.
nice trick.
why would it be hackish at all to use __CLASS__ ? you do nothing more than using what the language offers :)
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]
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
@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
I didn't know that about error suppression- probably because I never actually use it. :-)
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 :)
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
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).
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.
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
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.
Any chance you [url=http://news.php.net/php.pear.dev/36248]know about UTF-8[/url]? (had to ask)
S
No idea I'm afraid. UTF-x make my head spin. :-)
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.
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.
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();
Strict Standards: Non-static method foo::bar() should not be called statically in /network/webroot/dev/test.php on line 38
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?
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]
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.
In latest PHP your method of determining static does not work:
error_reporting(E_ALL);
class MyClass {
static function Test(){
$isStatic = !(isset($this) && get_class($this) == __CLASS__);
var_dump($isStatic);
var_dump($this);
}
}
$a = new MyClass;
$a->Test();
MyClass::Test();
?>
$this is always not defined if method is marked as static in spite of being called dynamically.
Vladislav, you are mistaken.
This example works correctly in PHP 5.2.5